Revert "Merge branch 'canary' into kucherenko/canary"

This reverts commit 819822f30b, reversing
changes made to bda9b05134.
This commit is contained in:
Mauricio Siu
2025-03-02 00:26:59 -06:00
parent 819822f30b
commit e6cb6454db
639 changed files with 17202 additions and 82902 deletions

View File

@@ -0,0 +1,117 @@
import type { IncomingMessage, ServerResponse } from "node:http";
import { findAdminByAuthId } from "@dokploy/server/services/admin";
import { findUserByAuthId } from "@dokploy/server/services/user";
import { DrizzlePostgreSQLAdapter } from "@lucia-auth/adapter-drizzle";
import { TimeSpan } from "lucia";
import { Lucia } from "lucia/dist/core.js";
import type { Session, User } from "lucia/dist/core.js";
import { db } from "../db";
import { type DatabaseUser, auth, sessionTable } from "../db/schema";
export const adapter = new DrizzlePostgreSQLAdapter(db, sessionTable, auth);
export const lucia = new Lucia(adapter, {
sessionCookie: {
attributes: {
secure: false,
},
},
sessionExpiresIn: new TimeSpan(1, "d"),
getUserAttributes: (attributes) => {
return {
email: attributes.email,
rol: attributes.rol,
secret: attributes.secret !== null,
adminId: attributes.adminId,
};
},
});
declare module "lucia" {
interface Register {
Lucia: typeof lucia;
DatabaseUserAttributes: Omit<DatabaseUser, "id"> & {
authId: string;
adminId: string;
};
}
}
export type ReturnValidateToken = Promise<{
user: (User & { authId: string; adminId: string }) | null;
session: Session | null;
}>;
export async function validateRequest(
req: IncomingMessage,
res: ServerResponse,
): ReturnValidateToken {
const sessionId = lucia.readSessionCookie(req.headers.cookie ?? "");
if (!sessionId) {
return {
user: null,
session: null,
};
}
const result = await lucia.validateSession(sessionId);
if (result?.session?.fresh) {
res.appendHeader(
"Set-Cookie",
lucia.createSessionCookie(result.session.id).serialize(),
);
}
if (!result.session) {
res.appendHeader(
"Set-Cookie",
lucia.createBlankSessionCookie().serialize(),
);
}
if (result.user) {
try {
if (result.user?.rol === "admin") {
const admin = await findAdminByAuthId(result.user.id);
result.user.adminId = admin.adminId;
} else if (result.user?.rol === "user") {
const userResult = await findUserByAuthId(result.user.id);
result.user.adminId = userResult.adminId;
}
} catch (error) {
return {
user: null,
session: null,
};
}
}
return {
session: result.session,
...((result.user && {
user: {
authId: result.user.id,
email: result.user.email,
rol: result.user.rol,
id: result.user.id,
secret: result.user.secret,
adminId: result.user.adminId,
},
}) || {
user: null,
}),
};
}
export async function validateWebSocketRequest(
req: IncomingMessage,
): Promise<{ user: User; session: Session } | { user: null; session: null }> {
const sessionId = lucia.readSessionCookie(req.headers.cookie ?? "");
if (!sessionId) {
return {
user: null,
session: null,
};
}
const result = await lucia.validateSession(sessionId);
return result;
}

View File

@@ -0,0 +1,99 @@
import type { IncomingMessage } from "node:http";
import { TimeSpan } from "lucia";
import { Lucia } from "lucia/dist/core.js";
import { findAdminByAuthId } from "../services/admin";
import { findUserByAuthId } from "../services/user";
import { type ReturnValidateToken, adapter } from "./auth";
export const luciaToken = new Lucia(adapter, {
sessionCookie: {
attributes: {
secure: false,
},
},
sessionExpiresIn: new TimeSpan(365, "d"),
getUserAttributes: (attributes) => {
return {
email: attributes.email,
rol: attributes.rol,
secret: attributes.secret !== null,
};
},
});
export const validateBearerToken = async (
req: IncomingMessage,
): ReturnValidateToken => {
const authorizationHeader = req.headers.authorization;
const sessionId = luciaToken.readBearerToken(authorizationHeader ?? "");
if (!sessionId) {
return {
user: null,
session: null,
};
}
const result = await luciaToken.validateSession(sessionId);
if (result.user) {
if (result.user?.rol === "admin") {
const admin = await findAdminByAuthId(result.user.id);
result.user.adminId = admin.adminId;
} else if (result.user?.rol === "user") {
const userResult = await findUserByAuthId(result.user.id);
result.user.adminId = userResult.adminId;
}
}
return {
session: result.session,
...((result.user && {
user: {
adminId: result.user.adminId,
authId: result.user.id,
email: result.user.email,
rol: result.user.rol,
id: result.user.id,
secret: result.user.secret,
},
}) || {
user: null,
}),
};
};
export const validateBearerTokenAPI = async (
authorizationHeader: string,
): ReturnValidateToken => {
const sessionId = luciaToken.readBearerToken(authorizationHeader ?? "");
if (!sessionId) {
return {
user: null,
session: null,
};
}
const result = await luciaToken.validateSession(sessionId);
if (result.user) {
if (result.user?.rol === "admin") {
const admin = await findAdminByAuthId(result.user.id);
result.user.adminId = admin.adminId;
} else if (result.user?.rol === "user") {
const userResult = await findUserByAuthId(result.user.id);
result.user.adminId = userResult.adminId;
}
}
return {
session: result.session,
...((result.user && {
user: {
adminId: result.user.adminId,
authId: result.user.id,
email: result.user.email,
rol: result.user.rol,
id: result.user.id,
secret: result.user.secret,
},
}) || {
user: null,
}),
};
};

View File

@@ -1,194 +0,0 @@
import { relations, sql } from "drizzle-orm";
import {
boolean,
integer,
pgTable,
text,
timestamp,
} from "drizzle-orm/pg-core";
import { nanoid } from "nanoid";
import { projects } from "./project";
import { server } from "./server";
import { users_temp } from "./user";
export const account = pgTable("account", {
id: text("id")
.primaryKey()
.$defaultFn(() => nanoid()),
accountId: text("account_id")
.notNull()
.$defaultFn(() => nanoid()),
providerId: text("provider_id").notNull(),
userId: text("user_id")
.notNull()
.references(() => users_temp.id, { onDelete: "cascade" }),
accessToken: text("access_token"),
refreshToken: text("refresh_token"),
idToken: text("id_token"),
accessTokenExpiresAt: timestamp("access_token_expires_at"),
refreshTokenExpiresAt: timestamp("refresh_token_expires_at"),
scope: text("scope"),
password: text("password"),
is2FAEnabled: boolean("is2FAEnabled").notNull().default(false),
createdAt: timestamp("created_at").notNull(),
updatedAt: timestamp("updated_at").notNull(),
resetPasswordToken: text("resetPasswordToken"),
resetPasswordExpiresAt: text("resetPasswordExpiresAt"),
confirmationToken: text("confirmationToken"),
confirmationExpiresAt: text("confirmationExpiresAt"),
});
export const accountRelations = relations(account, ({ one }) => ({
user: one(users_temp, {
fields: [account.userId],
references: [users_temp.id],
}),
}));
export const verification = pgTable("verification", {
id: text("id").primaryKey(),
identifier: text("identifier").notNull(),
value: text("value").notNull(),
expiresAt: timestamp("expires_at").notNull(),
createdAt: timestamp("created_at"),
updatedAt: timestamp("updated_at"),
});
export const organization = pgTable("organization", {
id: text("id")
.primaryKey()
.$defaultFn(() => nanoid()),
name: text("name").notNull(),
slug: text("slug").unique(),
logo: text("logo"),
createdAt: timestamp("created_at").notNull(),
metadata: text("metadata"),
ownerId: text("owner_id")
.notNull()
.references(() => users_temp.id, { onDelete: "cascade" }),
});
export const organizationRelations = relations(
organization,
({ one, many }) => ({
owner: one(users_temp, {
fields: [organization.ownerId],
references: [users_temp.id],
}),
servers: many(server),
projects: many(projects),
members: many(member),
}),
);
export const member = pgTable("member", {
id: text("id")
.primaryKey()
.$defaultFn(() => nanoid()),
organizationId: text("organization_id")
.notNull()
.references(() => organization.id, { onDelete: "cascade" }),
userId: text("user_id")
.notNull()
.references(() => users_temp.id, { onDelete: "cascade" }),
role: text("role").notNull().$type<"owner" | "member" | "admin">(),
createdAt: timestamp("created_at").notNull(),
teamId: text("team_id"),
// Permissions
canCreateProjects: boolean("canCreateProjects").notNull().default(false),
canAccessToSSHKeys: boolean("canAccessToSSHKeys").notNull().default(false),
canCreateServices: boolean("canCreateServices").notNull().default(false),
canDeleteProjects: boolean("canDeleteProjects").notNull().default(false),
canDeleteServices: boolean("canDeleteServices").notNull().default(false),
canAccessToDocker: boolean("canAccessToDocker").notNull().default(false),
canAccessToAPI: boolean("canAccessToAPI").notNull().default(false),
canAccessToGitProviders: boolean("canAccessToGitProviders")
.notNull()
.default(false),
canAccessToTraefikFiles: boolean("canAccessToTraefikFiles")
.notNull()
.default(false),
accessedProjects: text("accesedProjects")
.array()
.notNull()
.default(sql`ARRAY[]::text[]`),
accessedServices: text("accesedServices")
.array()
.notNull()
.default(sql`ARRAY[]::text[]`),
});
export const memberRelations = relations(member, ({ one }) => ({
organization: one(organization, {
fields: [member.organizationId],
references: [organization.id],
}),
user: one(users_temp, {
fields: [member.userId],
references: [users_temp.id],
}),
}));
export const invitation = pgTable("invitation", {
id: text("id").primaryKey(),
organizationId: text("organization_id")
.notNull()
.references(() => organization.id, { onDelete: "cascade" }),
email: text("email").notNull(),
role: text("role").$type<"owner" | "member" | "admin">(),
status: text("status").notNull(),
expiresAt: timestamp("expires_at").notNull(),
inviterId: text("inviter_id")
.notNull()
.references(() => users_temp.id, { onDelete: "cascade" }),
teamId: text("team_id"),
});
export const invitationRelations = relations(invitation, ({ one }) => ({
organization: one(organization, {
fields: [invitation.organizationId],
references: [organization.id],
}),
}));
export const twoFactor = pgTable("two_factor", {
id: text("id").primaryKey(),
secret: text("secret").notNull(),
backupCodes: text("backup_codes").notNull(),
userId: text("user_id")
.notNull()
.references(() => users_temp.id, { onDelete: "cascade" }),
});
export const apikey = pgTable("apikey", {
id: text("id").primaryKey(),
name: text("name"),
start: text("start"),
prefix: text("prefix"),
key: text("key").notNull(),
userId: text("user_id")
.notNull()
.references(() => users_temp.id, { onDelete: "cascade" }),
refillInterval: integer("refill_interval"),
refillAmount: integer("refill_amount"),
lastRefillAt: timestamp("last_refill_at"),
enabled: boolean("enabled"),
rateLimitEnabled: boolean("rate_limit_enabled"),
rateLimitTimeWindow: integer("rate_limit_time_window"),
rateLimitMax: integer("rate_limit_max"),
requestCount: integer("request_count"),
remaining: integer("remaining"),
lastRequest: timestamp("last_request"),
expiresAt: timestamp("expires_at"),
createdAt: timestamp("created_at").notNull(),
updatedAt: timestamp("updated_at").notNull(),
permissions: text("permissions"),
metadata: text("metadata"),
});
export const apikeyRelations = relations(apikey, ({ one }) => ({
user: one(users_temp, {
fields: [apikey.userId],
references: [users_temp.id],
}),
}));

View File

@@ -0,0 +1,121 @@
import { relations } from "drizzle-orm";
import { boolean, integer, pgTable, text } from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod";
import { nanoid } from "nanoid";
import { z } from "zod";
import { ai } from "./ai";
import { auth } from "./auth";
import { certificates } from "./certificate";
import { registry } from "./registry";
import { certificateType } from "./shared";
import { sshKeys } from "./ssh-key";
import { users } from "./user";
export const admins = pgTable("admin", {
adminId: text("adminId")
.notNull()
.primaryKey()
.$defaultFn(() => nanoid()),
serverIp: text("serverIp"),
certificateType: certificateType("certificateType").notNull().default("none"),
host: text("host"),
letsEncryptEmail: text("letsEncryptEmail"),
sshPrivateKey: text("sshPrivateKey"),
enableDockerCleanup: boolean("enableDockerCleanup").notNull().default(false),
enableLogRotation: boolean("enableLogRotation").notNull().default(false),
authId: text("authId")
.notNull()
.references(() => auth.id, { onDelete: "cascade" }),
createdAt: text("createdAt")
.notNull()
.$defaultFn(() => new Date().toISOString()),
stripeCustomerId: text("stripeCustomerId"),
stripeSubscriptionId: text("stripeSubscriptionId"),
serversQuantity: integer("serversQuantity").notNull().default(0),
});
export const adminsRelations = relations(admins, ({ one, many }) => ({
auth: one(auth, {
fields: [admins.authId],
references: [auth.id],
}),
users: many(users),
registry: many(registry),
sshKeys: many(sshKeys),
certificates: many(certificates),
ai: many(ai),
}));
const createSchema = createInsertSchema(admins, {
adminId: z.string(),
enableDockerCleanup: z.boolean().optional(),
sshPrivateKey: z.string().optional(),
certificateType: z.enum(["letsencrypt", "none"]).default("none"),
serverIp: z.string().optional(),
letsEncryptEmail: z.string().optional(),
});
export const apiUpdateAdmin = createSchema.partial();
export const apiSaveSSHKey = createSchema
.pick({
sshPrivateKey: true,
})
.required();
export const apiAssignDomain = createSchema
.pick({
host: true,
certificateType: true,
letsEncryptEmail: true,
})
.required()
.partial({
letsEncryptEmail: true,
});
export const apiUpdateDockerCleanup = createSchema
.pick({
enableDockerCleanup: true,
})
.required()
.extend({
serverId: z.string().optional(),
});
export const apiTraefikConfig = z.object({
traefikConfig: z.string().min(1),
});
export const apiModifyTraefikConfig = z.object({
path: z.string().min(1),
traefikConfig: z.string().min(1),
serverId: z.string().optional(),
});
export const apiReadTraefikConfig = z.object({
path: z.string().min(1),
serverId: z.string().optional(),
});
export const apiEnableDashboard = z.object({
enableDashboard: z.boolean().optional(),
serverId: z.string().optional(),
});
export const apiServerSchema = z
.object({
serverId: z.string().optional(),
})
.optional();
export const apiReadStatsLogs = z.object({
page: z
.object({
pageIndex: z.number(),
pageSize: z.number(),
})
.optional(),
status: z.string().array().optional(),
search: z.string().optional(),
sort: z.object({ id: z.string(), desc: z.boolean() }).optional(),
});

View File

@@ -44,6 +44,7 @@ export const buildType = pgEnum("buildType", [
"static",
]);
// TODO: refactor this types
export interface HealthCheckSwarm {
Test?: string[] | undefined;
Interval?: number | undefined;

View File

@@ -0,0 +1,130 @@
import { getRandomValues } from "node:crypto";
import { relations } from "drizzle-orm";
import { boolean, pgEnum, pgTable, text } from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod";
import { nanoid } from "nanoid";
import { z } from "zod";
import { admins } from "./admin";
import { users } from "./user";
const randomImages = [
"/avatars/avatar-1.png",
"/avatars/avatar-2.png",
"/avatars/avatar-3.png",
"/avatars/avatar-4.png",
"/avatars/avatar-5.png",
"/avatars/avatar-6.png",
"/avatars/avatar-7.png",
"/avatars/avatar-8.png",
"/avatars/avatar-9.png",
"/avatars/avatar-10.png",
"/avatars/avatar-11.png",
"/avatars/avatar-12.png",
];
const generateRandomImage = () => {
return (
randomImages[
// @ts-ignore
getRandomValues(new Uint32Array(1))[0] % randomImages.length
] || "/avatars/avatar-1.png"
);
};
export type DatabaseUser = typeof auth.$inferSelect;
export const roles = pgEnum("Roles", ["admin", "user"]);
export const auth = pgTable("auth", {
id: text("id")
.notNull()
.primaryKey()
.$defaultFn(() => nanoid()),
email: text("email").notNull().unique(),
password: text("password").notNull(),
rol: roles("rol").notNull(),
image: text("image").$defaultFn(() => generateRandomImage()),
secret: text("secret"),
token: text("token"),
is2FAEnabled: boolean("is2FAEnabled").notNull().default(false),
createdAt: text("createdAt")
.notNull()
.$defaultFn(() => new Date().toISOString()),
resetPasswordToken: text("resetPasswordToken"),
resetPasswordExpiresAt: text("resetPasswordExpiresAt"),
confirmationToken: text("confirmationToken"),
confirmationExpiresAt: text("confirmationExpiresAt"),
});
export const authRelations = relations(auth, ({ many }) => ({
admins: many(admins),
users: many(users),
}));
const createSchema = createInsertSchema(auth, {
email: z.string().email(),
password: z.string().min(8),
rol: z.enum(["admin", "user"]),
image: z.string().optional(),
});
export const apiCreateAdmin = createSchema.pick({
email: true,
password: true,
});
export const apiCreateUser = createSchema
.pick({
password: true,
id: true,
token: true,
})
.required()
.extend({
token: z.string().min(1),
});
export const apiLogin = createSchema
.pick({
email: true,
password: true,
})
.required();
export const apiUpdateAuth = createSchema.partial().extend({
email: z.string().nullable(),
password: z.string().nullable(),
image: z.string().optional(),
currentPassword: z.string().nullable(),
});
export const apiUpdateAuthByAdmin = createSchema.partial().extend({
email: z.string().nullable(),
password: z.string().nullable(),
image: z.string().optional(),
id: z.string().min(1),
});
export const apiFindOneAuth = createSchema
.pick({
id: true,
})
.required();
export const apiVerify2FA = createSchema
.extend({
pin: z.string().min(6),
secret: z.string().min(1),
})
.pick({
pin: true,
secret: true,
})
.required();
export const apiVerifyLogin2FA = createSchema
.extend({
pin: z.string().min(6),
})
.pick({
pin: true,
id: true,
})
.required();

View File

@@ -61,5 +61,5 @@ export const apiUpdateBitbucket = createSchema.extend({
name: z.string().min(1),
bitbucketUsername: z.string().optional(),
bitbucketWorkspaceName: z.string().optional(),
organizationId: z.string().optional(),
adminId: z.string().optional(),
});

View File

@@ -3,7 +3,7 @@ import { boolean, pgTable, text } from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod";
import { nanoid } from "nanoid";
import { z } from "zod";
import { organization } from "./account";
import { admins } from "./admin";
import { server } from "./server";
import { generateAppName } from "./utils";
@@ -20,24 +20,27 @@ export const certificates = pgTable("certificate", {
.$defaultFn(() => generateAppName("certificate"))
.unique(),
autoRenew: boolean("autoRenew"),
organizationId: text("organizationId")
.notNull()
.references(() => organization.id, { onDelete: "cascade" }),
adminId: text("adminId").references(() => admins.adminId, {
onDelete: "cascade",
}),
serverId: text("serverId").references(() => server.serverId, {
onDelete: "cascade",
}),
});
export const certificatesRelations = relations(certificates, ({ one }) => ({
server: one(server, {
fields: [certificates.serverId],
references: [server.serverId],
export const certificatesRelations = relations(
certificates,
({ one, many }) => ({
server: one(server, {
fields: [certificates.serverId],
references: [server.serverId],
}),
admin: one(admins, {
fields: [certificates.adminId],
references: [admins.adminId],
}),
}),
organization: one(organization, {
fields: [certificates.organizationId],
references: [organization.id],
}),
}));
);
export const apiCreateCertificate = createInsertSchema(certificates, {
name: z.string().min(1),

View File

@@ -69,7 +69,6 @@ export const compose = pgTable("compose", {
composePath: text("composePath").notNull().default("./docker-compose.yml"),
suffix: text("suffix").notNull().default(""),
randomize: boolean("randomize").notNull().default(false),
isolatedDeployment: boolean("isolatedDeployment").notNull().default(false),
composeStatus: applicationStatus("composeStatus").notNull().default("idle"),
projectId: text("projectId")
.notNull()

View File

@@ -1,7 +0,0 @@
import { pgGenerate } from "drizzle-dbml-generator"; // Using Postgres for this example
import * as schema from "./index";
const out = "./schema.dbml";
const relational = true;
pgGenerate({ schema, out, relational });

View File

@@ -1,4 +1,4 @@
import { relations } from "drizzle-orm";
import { is, relations } from "drizzle-orm";
import {
type AnyPgColumn,
boolean,
@@ -47,7 +47,6 @@ export const deployments = pgTable("deployment", {
createdAt: text("createdAt")
.notNull()
.$defaultFn(() => new Date().toISOString()),
errorMessage: text("errorMessage"),
});
export const deploymentsRelations = relations(deployments, ({ one }) => ({

View File

@@ -3,7 +3,7 @@ import { pgTable, text } from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod";
import { nanoid } from "nanoid";
import { z } from "zod";
import { organization } from "./account";
import { admins } from "./admin";
import { backups } from "./backups";
export const destinations = pgTable("destination", {
@@ -17,19 +17,20 @@ export const destinations = pgTable("destination", {
secretAccessKey: text("secretAccessKey").notNull(),
bucket: text("bucket").notNull(),
region: text("region").notNull(),
// maybe it can be null
endpoint: text("endpoint").notNull(),
organizationId: text("organizationId")
adminId: text("adminId")
.notNull()
.references(() => organization.id, { onDelete: "cascade" }),
.references(() => admins.adminId, { onDelete: "cascade" }),
});
export const destinationsRelations = relations(
destinations,
({ many, one }) => ({
backups: many(backups),
organization: one(organization, {
fields: [destinations.organizationId],
references: [organization.id],
admin: one(admins, {
fields: [destinations.adminId],
references: [admins.adminId],
}),
}),
);

View File

@@ -3,7 +3,7 @@ import { pgEnum, pgTable, text } from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod";
import { nanoid } from "nanoid";
import { z } from "zod";
import { organization } from "./account";
import { admins } from "./admin";
import { bitbucket } from "./bitbucket";
import { github } from "./github";
import { gitlab } from "./gitlab";
@@ -24,12 +24,12 @@ export const gitProvider = pgTable("git_provider", {
createdAt: text("createdAt")
.notNull()
.$defaultFn(() => new Date().toISOString()),
organizationId: text("organizationId")
.notNull()
.references(() => organization.id, { onDelete: "cascade" }),
adminId: text("adminId").references(() => admins.adminId, {
onDelete: "cascade",
}),
});
export const gitProviderRelations = relations(gitProvider, ({ one }) => ({
export const gitProviderRelations = relations(gitProvider, ({ one, many }) => ({
github: one(github, {
fields: [gitProvider.gitProviderId],
references: [github.gitProviderId],
@@ -42,9 +42,9 @@ export const gitProviderRelations = relations(gitProvider, ({ one }) => ({
fields: [gitProvider.gitProviderId],
references: [bitbucket.gitProviderId],
}),
organization: one(organization, {
fields: [gitProvider.organizationId],
references: [organization.id],
admin: one(admins, {
fields: [gitProvider.adminId],
references: [admins.adminId],
}),
}));

View File

@@ -1,6 +1,8 @@
export * from "./application";
export * from "./postgres";
export * from "./user";
export * from "./admin";
export * from "./auth";
export * from "./project";
export * from "./domain";
export * from "./mariadb";
@@ -29,4 +31,3 @@ export * from "./server";
export * from "./utils";
export * from "./preview-deployments";
export * from "./ai";
export * from "./account";

View File

@@ -3,7 +3,7 @@ import { boolean, integer, pgEnum, pgTable, text } from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod";
import { nanoid } from "nanoid";
import { z } from "zod";
import { organization } from "./account";
import { admins } from "./admin";
export const notificationType = pgEnum("notificationType", [
"slack",
@@ -24,7 +24,6 @@ export const notifications = pgTable("notification", {
databaseBackup: boolean("databaseBackup").notNull().default(false),
dokployRestart: boolean("dokployRestart").notNull().default(false),
dockerCleanup: boolean("dockerCleanup").notNull().default(false),
serverThreshold: boolean("serverThreshold").notNull().default(false),
notificationType: notificationType("notificationType").notNull(),
createdAt: text("createdAt")
.notNull()
@@ -44,9 +43,9 @@ export const notifications = pgTable("notification", {
gotifyId: text("gotifyId").references(() => gotify.gotifyId, {
onDelete: "cascade",
}),
organizationId: text("organizationId")
.notNull()
.references(() => organization.id, { onDelete: "cascade" }),
adminId: text("adminId").references(() => admins.adminId, {
onDelete: "cascade",
}),
});
export const slack = pgTable("slack", {
@@ -65,7 +64,6 @@ export const telegram = pgTable("telegram", {
.$defaultFn(() => nanoid()),
botToken: text("botToken").notNull(),
chatId: text("chatId").notNull(),
messageThreadId: text("messageThreadId"),
});
export const discord = pgTable("discord", {
@@ -122,9 +120,9 @@ export const notificationsRelations = relations(notifications, ({ one }) => ({
fields: [notifications.gotifyId],
references: [gotify.gotifyId],
}),
organization: one(organization, {
fields: [notifications.organizationId],
references: [organization.id],
admin: one(admins, {
fields: [notifications.adminId],
references: [admins.adminId],
}),
}));
@@ -138,7 +136,6 @@ export const apiCreateSlack = notificationsSchema
name: true,
appDeploy: true,
dockerCleanup: true,
serverThreshold: true,
})
.extend({
webhookUrl: z.string().min(1),
@@ -149,7 +146,7 @@ export const apiCreateSlack = notificationsSchema
export const apiUpdateSlack = apiCreateSlack.partial().extend({
notificationId: z.string().min(1),
slackId: z.string(),
organizationId: z.string().optional(),
adminId: z.string().optional(),
});
export const apiTestSlackConnection = apiCreateSlack.pick({
@@ -165,25 +162,22 @@ export const apiCreateTelegram = notificationsSchema
name: true,
appDeploy: true,
dockerCleanup: true,
serverThreshold: true,
})
.extend({
botToken: z.string().min(1),
chatId: z.string().min(1),
messageThreadId: z.string(),
})
.required();
export const apiUpdateTelegram = apiCreateTelegram.partial().extend({
notificationId: z.string().min(1),
telegramId: z.string().min(1),
organizationId: z.string().optional(),
adminId: z.string().optional(),
});
export const apiTestTelegramConnection = apiCreateTelegram.pick({
botToken: true,
chatId: true,
messageThreadId: true,
});
export const apiCreateDiscord = notificationsSchema
@@ -194,7 +188,6 @@ export const apiCreateDiscord = notificationsSchema
name: true,
appDeploy: true,
dockerCleanup: true,
serverThreshold: true,
})
.extend({
webhookUrl: z.string().min(1),
@@ -205,7 +198,7 @@ export const apiCreateDiscord = notificationsSchema
export const apiUpdateDiscord = apiCreateDiscord.partial().extend({
notificationId: z.string().min(1),
discordId: z.string().min(1),
organizationId: z.string().optional(),
adminId: z.string().optional(),
});
export const apiTestDiscordConnection = apiCreateDiscord
@@ -224,7 +217,6 @@ export const apiCreateEmail = notificationsSchema
name: true,
appDeploy: true,
dockerCleanup: true,
serverThreshold: true,
})
.extend({
smtpServer: z.string().min(1),
@@ -239,7 +231,7 @@ export const apiCreateEmail = notificationsSchema
export const apiUpdateEmail = apiCreateEmail.partial().extend({
notificationId: z.string().min(1),
emailId: z.string().min(1),
organizationId: z.string().optional(),
adminId: z.string().optional(),
});
export const apiTestEmailConnection = apiCreateEmail.pick({
@@ -271,7 +263,7 @@ export const apiCreateGotify = notificationsSchema
export const apiUpdateGotify = apiCreateGotify.partial().extend({
notificationId: z.string().min(1),
gotifyId: z.string().min(1),
organizationId: z.string().optional(),
adminId: z.string().optional(),
});
export const apiTestGotifyConnection = apiCreateGotify

View File

@@ -1,9 +1,10 @@
import { relations } from "drizzle-orm";
import { pgTable, text } from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod";
import { nanoid } from "nanoid";
import { z } from "zod";
import { organization } from "./account";
import { admins } from "./admin";
import { applications } from "./application";
import { compose } from "./compose";
import { mariadb } from "./mariadb";
@@ -22,10 +23,9 @@ export const projects = pgTable("project", {
createdAt: text("createdAt")
.notNull()
.$defaultFn(() => new Date().toISOString()),
organizationId: text("organizationId")
adminId: text("adminId")
.notNull()
.references(() => organization.id, { onDelete: "cascade" }),
.references(() => admins.adminId, { onDelete: "cascade" }),
env: text("env").notNull().default(""),
});
@@ -37,9 +37,9 @@ export const projectRelations = relations(projects, ({ many, one }) => ({
mongo: many(mongo),
redis: many(redis),
compose: many(compose),
organization: one(organization, {
fields: [projects.organizationId],
references: [organization.id],
admin: one(admins, {
fields: [projects.adminId],
references: [admins.adminId],
}),
}));

View File

@@ -3,7 +3,7 @@ import { pgEnum, pgTable, text } from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod";
import { nanoid } from "nanoid";
import { z } from "zod";
import { organization } from "./account";
import { admins } from "./admin";
import { applications } from "./application";
/**
* This is an example of how to use the multi-project schema feature of Drizzle ORM. Use the same
@@ -27,12 +27,16 @@ export const registry = pgTable("registry", {
.notNull()
.$defaultFn(() => new Date().toISOString()),
registryType: registryType("selfHosted").notNull().default("cloud"),
organizationId: text("organizationId")
adminId: text("adminId")
.notNull()
.references(() => organization.id, { onDelete: "cascade" }),
.references(() => admins.adminId, { onDelete: "cascade" }),
});
export const registryRelations = relations(registry, ({ many }) => ({
export const registryRelations = relations(registry, ({ one, many }) => ({
admin: one(admins, {
fields: [registry.adminId],
references: [admins.adminId],
}),
applications: many(applications),
}));
@@ -41,7 +45,7 @@ const createSchema = createInsertSchema(registry, {
username: z.string().min(1),
password: z.string().min(1),
registryUrl: z.string(),
organizationId: z.string().min(1),
adminId: z.string().min(1),
registryId: z.string().min(1),
registryType: z.enum(["cloud"]),
imagePrefix: z.string().nullable().optional(),

View File

@@ -1,872 +0,0 @@
enum applicationStatus {
idle
running
done
error
}
enum buildType {
dockerfile
heroku_buildpacks
paketo_buildpacks
nixpacks
static
}
enum certificateType {
letsencrypt
none
}
enum composeType {
"docker-compose"
stack
}
enum databaseType {
postgres
mariadb
mysql
mongo
}
enum deploymentStatus {
running
done
error
}
enum domainType {
compose
application
preview
}
enum gitProviderType {
github
gitlab
bitbucket
}
enum mountType {
bind
volume
file
}
enum notificationType {
slack
telegram
discord
email
gotify
}
enum protocolType {
tcp
udp
}
enum RegistryType {
selfHosted
cloud
}
enum Roles {
admin
user
}
enum serverStatus {
active
inactive
}
enum serviceType {
application
postgres
mysql
mariadb
mongo
redis
compose
}
enum sourceType {
docker
git
github
gitlab
bitbucket
drop
}
enum sourceTypeCompose {
git
github
gitlab
bitbucket
raw
}
table account {
id text [pk, not null]
account_id text [not null]
provider_id text [not null]
user_id text [not null]
access_token text
refresh_token text
id_token text
access_token_expires_at timestamp
refresh_token_expires_at timestamp
scope text
password text
is2FAEnabled boolean [not null, default: false]
created_at timestamp [not null]
updated_at timestamp [not null]
resetPasswordToken text
resetPasswordExpiresAt text
confirmationToken text
confirmationExpiresAt text
}
table admin {
}
table application {
applicationId text [pk, not null]
name text [not null]
appName text [not null, unique]
description text
env text
previewEnv text
previewBuildArgs text
previewWildcard text
previewPort integer [default: 3000]
previewHttps boolean [not null, default: false]
previewPath text [default: '/']
certificateType certificateType [not null, default: 'none']
previewLimit integer [default: 3]
isPreviewDeploymentsActive boolean [default: false]
buildArgs text
memoryReservation text
memoryLimit text
cpuReservation text
cpuLimit text
title text
enabled boolean
subtitle text
command text
refreshToken text
sourceType sourceType [not null, default: 'github']
repository text
owner text
branch text
buildPath text [default: '/']
autoDeploy boolean
gitlabProjectId integer
gitlabRepository text
gitlabOwner text
gitlabBranch text
gitlabBuildPath text [default: '/']
gitlabPathNamespace text
bitbucketRepository text
bitbucketOwner text
bitbucketBranch text
bitbucketBuildPath text [default: '/']
username text
password text
dockerImage text
registryUrl text
customGitUrl text
customGitBranch text
customGitBuildPath text
customGitSSHKeyId text
dockerfile text
dockerContextPath text
dockerBuildStage text
dropBuildPath text
healthCheckSwarm json
restartPolicySwarm json
placementSwarm json
updateConfigSwarm json
rollbackConfigSwarm json
modeSwarm json
labelsSwarm json
networkSwarm json
replicas integer [not null, default: 1]
applicationStatus applicationStatus [not null, default: 'idle']
buildType buildType [not null, default: 'nixpacks']
herokuVersion text [default: '24']
publishDirectory text
createdAt text [not null]
registryId text
projectId text [not null]
githubId text
gitlabId text
bitbucketId text
serverId text
}
table auth {
id text [pk, not null]
email text [not null, unique]
password text [not null]
rol Roles [not null]
image text
secret text
token text
is2FAEnabled boolean [not null, default: false]
createdAt text [not null]
resetPasswordToken text
resetPasswordExpiresAt text
confirmationToken text
confirmationExpiresAt text
}
table backup {
backupId text [pk, not null]
schedule text [not null]
enabled boolean
database text [not null]
prefix text [not null]
destinationId text [not null]
databaseType databaseType [not null]
postgresId text
mariadbId text
mysqlId text
mongoId text
}
table bitbucket {
bitbucketId text [pk, not null]
bitbucketUsername text
appPassword text
bitbucketWorkspaceName text
gitProviderId text [not null]
}
table certificate {
certificateId text [pk, not null]
name text [not null]
certificateData text [not null]
privateKey text [not null]
certificatePath text [not null, unique]
autoRenew boolean
userId text
serverId text
}
table compose {
composeId text [pk, not null]
name text [not null]
appName text [not null]
description text
env text
composeFile text [not null, default: '']
refreshToken text
sourceType sourceTypeCompose [not null, default: 'github']
composeType composeType [not null, default: 'docker-compose']
repository text
owner text
branch text
autoDeploy boolean
gitlabProjectId integer
gitlabRepository text
gitlabOwner text
gitlabBranch text
gitlabPathNamespace text
bitbucketRepository text
bitbucketOwner text
bitbucketBranch text
customGitUrl text
customGitBranch text
customGitSSHKeyId text
command text [not null, default: '']
composePath text [not null, default: './docker-compose.yml']
suffix text [not null, default: '']
randomize boolean [not null, default: false]
isolatedDeployment boolean [not null, default: false]
composeStatus applicationStatus [not null, default: 'idle']
projectId text [not null]
createdAt text [not null]
githubId text
gitlabId text
bitbucketId text
serverId text
}
table deployment {
deploymentId text [pk, not null]
title text [not null]
description text
status deploymentStatus [default: 'running']
logPath text [not null]
applicationId text
composeId text
serverId text
isPreviewDeployment boolean [default: false]
previewDeploymentId text
createdAt text [not null]
errorMessage text
}
table destination {
destinationId text [pk, not null]
name text [not null]
provider text
accessKey text [not null]
secretAccessKey text [not null]
bucket text [not null]
region text [not null]
endpoint text [not null]
userId text [not null]
}
table discord {
discordId text [pk, not null]
webhookUrl text [not null]
decoration boolean
}
table domain {
domainId text [pk, not null]
host text [not null]
https boolean [not null, default: false]
port integer [default: 3000]
path text [default: '/']
serviceName text
domainType domainType [default: 'application']
uniqueConfigKey serial [not null, increment]
createdAt text [not null]
composeId text
applicationId text
previewDeploymentId text
certificateType certificateType [not null, default: 'none']
}
table email {
emailId text [pk, not null]
smtpServer text [not null]
smtpPort integer [not null]
username text [not null]
password text [not null]
fromAddress text [not null]
toAddress text[] [not null]
}
table git_provider {
gitProviderId text [pk, not null]
name text [not null]
providerType gitProviderType [not null, default: 'github']
createdAt text [not null]
userId text
}
table github {
githubId text [pk, not null]
githubAppName text
githubAppId integer
githubClientId text
githubClientSecret text
githubInstallationId text
githubPrivateKey text
githubWebhookSecret text
gitProviderId text [not null]
}
table gitlab {
gitlabId text [pk, not null]
gitlabUrl text [not null, default: 'https://gitlab.com']
application_id text
redirect_uri text
secret text
access_token text
refresh_token text
group_name text
expires_at integer
gitProviderId text [not null]
}
table gotify {
gotifyId text [pk, not null]
serverUrl text [not null]
appToken text [not null]
priority integer [not null, default: 5]
decoration boolean
}
table invitation {
id text [pk, not null]
organization_id text [not null]
email text [not null]
role text
status text [not null]
expires_at timestamp [not null]
inviter_id text [not null]
}
table mariadb {
mariadbId text [pk, not null]
name text [not null]
appName text [not null, unique]
description text
databaseName text [not null]
databaseUser text [not null]
databasePassword text [not null]
rootPassword text [not null]
dockerImage text [not null]
command text
env text
memoryReservation text
memoryLimit text
cpuReservation text
cpuLimit text
externalPort integer
applicationStatus applicationStatus [not null, default: 'idle']
createdAt text [not null]
projectId text [not null]
serverId text
}
table member {
id text [pk, not null]
organization_id text [not null]
user_id text [not null]
role text [not null]
created_at timestamp [not null]
}
table mongo {
mongoId text [pk, not null]
name text [not null]
appName text [not null, unique]
description text
databaseUser text [not null]
databasePassword text [not null]
dockerImage text [not null]
command text
env text
memoryReservation text
memoryLimit text
cpuReservation text
cpuLimit text
externalPort integer
applicationStatus applicationStatus [not null, default: 'idle']
createdAt text [not null]
projectId text [not null]
serverId text
replicaSets boolean [default: false]
}
table mount {
mountId text [pk, not null]
type mountType [not null]
hostPath text
volumeName text
filePath text
content text
serviceType serviceType [not null, default: 'application']
mountPath text [not null]
applicationId text
postgresId text
mariadbId text
mongoId text
mysqlId text
redisId text
composeId text
}
table mysql {
mysqlId text [pk, not null]
name text [not null]
appName text [not null, unique]
description text
databaseName text [not null]
databaseUser text [not null]
databasePassword text [not null]
rootPassword text [not null]
dockerImage text [not null]
command text
env text
memoryReservation text
memoryLimit text
cpuReservation text
cpuLimit text
externalPort integer
applicationStatus applicationStatus [not null, default: 'idle']
createdAt text [not null]
projectId text [not null]
serverId text
}
table notification {
notificationId text [pk, not null]
name text [not null]
appDeploy boolean [not null, default: false]
appBuildError boolean [not null, default: false]
databaseBackup boolean [not null, default: false]
dokployRestart boolean [not null, default: false]
dockerCleanup boolean [not null, default: false]
serverThreshold boolean [not null, default: false]
notificationType notificationType [not null]
createdAt text [not null]
slackId text
telegramId text
discordId text
emailId text
gotifyId text
userId text
}
table organization {
id text [pk, not null]
name text [not null]
slug text [unique]
logo text
created_at timestamp [not null]
metadata text
owner_id text [not null]
}
table port {
portId text [pk, not null]
publishedPort integer [not null]
targetPort integer [not null]
protocol protocolType [not null]
applicationId text [not null]
}
table postgres {
postgresId text [pk, not null]
name text [not null]
appName text [not null, unique]
databaseName text [not null]
databaseUser text [not null]
databasePassword text [not null]
description text
dockerImage text [not null]
command text
env text
memoryReservation text
externalPort integer
memoryLimit text
cpuReservation text
cpuLimit text
applicationStatus applicationStatus [not null, default: 'idle']
createdAt text [not null]
projectId text [not null]
serverId text
}
table preview_deployments {
previewDeploymentId text [pk, not null]
branch text [not null]
pullRequestId text [not null]
pullRequestNumber text [not null]
pullRequestURL text [not null]
pullRequestTitle text [not null]
pullRequestCommentId text [not null]
previewStatus applicationStatus [not null, default: 'idle']
appName text [not null, unique]
applicationId text [not null]
domainId text
createdAt text [not null]
expiresAt text
}
table project {
projectId text [pk, not null]
name text [not null]
description text
createdAt text [not null]
userId text [not null]
env text [not null, default: '']
}
table redirect {
redirectId text [pk, not null]
regex text [not null]
replacement text [not null]
permanent boolean [not null, default: false]
uniqueConfigKey serial [not null, increment]
createdAt text [not null]
applicationId text [not null]
}
table redis {
redisId text [pk, not null]
name text [not null]
appName text [not null, unique]
description text
password text [not null]
dockerImage text [not null]
command text
env text
memoryReservation text
memoryLimit text
cpuReservation text
cpuLimit text
externalPort integer
createdAt text [not null]
applicationStatus applicationStatus [not null, default: 'idle']
projectId text [not null]
serverId text
}
table registry {
registryId text [pk, not null]
registryName text [not null]
imagePrefix text
username text [not null]
password text [not null]
registryUrl text [not null, default: '']
createdAt text [not null]
selfHosted RegistryType [not null, default: 'cloud']
userId text [not null]
}
table security {
securityId text [pk, not null]
username text [not null]
password text [not null]
createdAt text [not null]
applicationId text [not null]
indexes {
(username, applicationId) [name: 'security_username_applicationId_unique', unique]
}
}
table server {
serverId text [pk, not null]
name text [not null]
description text
ipAddress text [not null]
port integer [not null]
username text [not null, default: 'root']
appName text [not null]
enableDockerCleanup boolean [not null, default: false]
createdAt text [not null]
userId text [not null]
serverStatus serverStatus [not null, default: 'active']
command text [not null, default: '']
sshKeyId text
metricsConfig jsonb [not null, default: `{"server":{"type":"Remote","refreshRate":60,"port":4500,"token":"","urlCallback":"","cronJob":"","retentionDays":2,"thresholds":{"cpu":0,"memory":0}},"containers":{"refreshRate":60,"services":{"include":[],"exclude":[]}}}`]
}
table session {
id text [pk, not null]
expires_at timestamp [not null]
token text [not null, unique]
created_at timestamp [not null]
updated_at timestamp [not null]
ip_address text
user_agent text
user_id text [not null]
impersonated_by text
active_organization_id text
}
table slack {
slackId text [pk, not null]
webhookUrl text [not null]
channel text
}
table "ssh-key" {
sshKeyId text [pk, not null]
privateKey text [not null, default: '']
publicKey text [not null]
name text [not null]
description text
createdAt text [not null]
lastUsedAt text
userId text
}
table telegram {
telegramId text [pk, not null]
botToken text [not null]
chatId text [not null]
}
table user {
id text [pk, not null]
name text [not null, default: '']
token text [not null]
isRegistered boolean [not null, default: false]
expirationDate text [not null]
createdAt text [not null]
canCreateProjects boolean [not null, default: false]
canAccessToSSHKeys boolean [not null, default: false]
canCreateServices boolean [not null, default: false]
canDeleteProjects boolean [not null, default: false]
canDeleteServices boolean [not null, default: false]
canAccessToDocker boolean [not null, default: false]
canAccessToAPI boolean [not null, default: false]
canAccessToGitProviders boolean [not null, default: false]
canAccessToTraefikFiles boolean [not null, default: false]
accesedProjects text[] [not null, default: `ARRAY[]::text[]`]
accesedServices text[] [not null, default: `ARRAY[]::text[]`]
email text [not null, unique]
email_verified boolean [not null]
image text
role text
banned boolean
ban_reason text
ban_expires timestamp
updated_at timestamp [not null]
serverIp text
certificateType certificateType [not null, default: 'none']
host text
letsEncryptEmail text
sshPrivateKey text
enableDockerCleanup boolean [not null, default: false]
enableLogRotation boolean [not null, default: false]
enablePaidFeatures boolean [not null, default: false]
metricsConfig jsonb [not null, 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 [not null, default: false]
cleanupCacheOnPreviews boolean [not null, default: false]
cleanupCacheOnCompose boolean [not null, default: false]
stripeCustomerId text
stripeSubscriptionId text
serversQuantity integer [not null, default: 0]
}
table verification {
id text [pk, not null]
identifier text [not null]
value text [not null]
expires_at timestamp [not null]
created_at timestamp
updated_at timestamp
}
ref: mount.applicationId > application.applicationId
ref: mount.postgresId > postgres.postgresId
ref: mount.mariadbId > mariadb.mariadbId
ref: mount.mongoId > mongo.mongoId
ref: mount.mysqlId > mysql.mysqlId
ref: mount.redisId > redis.redisId
ref: mount.composeId > compose.composeId
ref: application.projectId > project.projectId
ref: application.customGitSSHKeyId > "ssh-key".sshKeyId
ref: application.registryId > registry.registryId
ref: application.githubId - github.githubId
ref: application.gitlabId - gitlab.gitlabId
ref: application.bitbucketId - bitbucket.bitbucketId
ref: application.serverId > server.serverId
ref: backup.destinationId > destination.destinationId
ref: backup.postgresId > postgres.postgresId
ref: backup.mariadbId > mariadb.mariadbId
ref: backup.mysqlId > mysql.mysqlId
ref: backup.mongoId > mongo.mongoId
ref: git_provider.gitProviderId - bitbucket.gitProviderId
ref: certificate.serverId > server.serverId
ref: certificate.userId - user.id
ref: compose.projectId > project.projectId
ref: compose.customGitSSHKeyId > "ssh-key".sshKeyId
ref: compose.githubId - github.githubId
ref: compose.gitlabId - gitlab.gitlabId
ref: compose.bitbucketId - bitbucket.bitbucketId
ref: compose.serverId > server.serverId
ref: deployment.applicationId > application.applicationId
ref: deployment.composeId > compose.composeId
ref: deployment.serverId > server.serverId
ref: deployment.previewDeploymentId > preview_deployments.previewDeploymentId
ref: destination.userId - user.id
ref: domain.applicationId > application.applicationId
ref: domain.composeId > compose.composeId
ref: preview_deployments.domainId - domain.domainId
ref: github.gitProviderId - git_provider.gitProviderId
ref: gitlab.gitProviderId - git_provider.gitProviderId
ref: git_provider.userId - user.id
ref: mariadb.projectId > project.projectId
ref: mariadb.serverId > server.serverId
ref: mongo.projectId > project.projectId
ref: mongo.serverId > server.serverId
ref: mysql.projectId > project.projectId
ref: mysql.serverId > server.serverId
ref: notification.slackId - slack.slackId
ref: notification.telegramId - telegram.telegramId
ref: notification.discordId - discord.discordId
ref: notification.emailId - email.emailId
ref: notification.gotifyId - gotify.gotifyId
ref: notification.userId - user.id
ref: port.applicationId > application.applicationId
ref: postgres.projectId > project.projectId
ref: postgres.serverId > server.serverId
ref: preview_deployments.applicationId > application.applicationId
ref: project.userId - user.id
ref: redirect.applicationId > application.applicationId
ref: redis.projectId > project.projectId
ref: redis.serverId > server.serverId
ref: registry.userId - user.id
ref: security.applicationId > application.applicationId
ref: server.userId - user.id
ref: server.sshKeyId > "ssh-key".sshKeyId
ref: "ssh-key".userId - user.id

View File

@@ -1,16 +1,10 @@
import { relations } from "drizzle-orm";
import {
boolean,
integer,
jsonb,
pgEnum,
pgTable,
text,
} from "drizzle-orm/pg-core";
import { boolean, integer, pgEnum, pgTable, text } from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod";
import { nanoid } from "nanoid";
import { z } from "zod";
import { organization } from "./account";
import { admins } from "./admin";
import { applications } from "./application";
import { certificates } from "./certificate";
import { compose } from "./compose";
@@ -39,64 +33,24 @@ export const server = pgTable("server", {
.notNull()
.$defaultFn(() => generateAppName("server")),
enableDockerCleanup: boolean("enableDockerCleanup").notNull().default(false),
createdAt: text("createdAt").notNull(),
organizationId: text("organizationId")
createdAt: text("createdAt")
.notNull()
.references(() => organization.id, { onDelete: "cascade" }),
.$defaultFn(() => new Date().toISOString()),
adminId: text("adminId")
.notNull()
.references(() => admins.adminId, { onDelete: "cascade" }),
serverStatus: serverStatus("serverStatus").notNull().default("active"),
command: text("command").notNull().default(""),
sshKeyId: text("sshKeyId").references(() => sshKeys.sshKeyId, {
onDelete: "set null",
}),
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: "Remote",
refreshRate: 60,
port: 4500,
token: "",
urlCallback: "",
cronJob: "",
retentionDays: 2,
thresholds: {
cpu: 0,
memory: 0,
},
},
containers: {
refreshRate: 60,
services: {
include: [],
exclude: [],
},
},
}),
});
export const serverRelations = relations(server, ({ one, many }) => ({
admin: one(admins, {
fields: [server.adminId],
references: [admins.adminId],
}),
deployments: many(deployments),
sshKey: one(sshKeys, {
fields: [server.sshKeyId],
@@ -110,10 +64,6 @@ export const serverRelations = relations(server, ({ one, many }) => ({
mysql: many(mysql),
postgres: many(postgres),
certificates: many(certificates),
organization: one(organization, {
fields: [server.organizationId],
references: [organization.id],
}),
}));
const createSchema = createInsertSchema(server, {
@@ -159,34 +109,3 @@ export const apiUpdateServer = createSchema
.extend({
command: z.string().optional(),
});
export const apiUpdateServerMonitoring = createSchema
.pick({
serverId: true,
})
.required()
.extend({
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(),
});

View File

@@ -1,18 +1,13 @@
import { pgTable, text, timestamp } from "drizzle-orm/pg-core";
import { users_temp } from "./user";
import { auth } from "./auth";
// OLD TABLE
export const session = pgTable("session_temp", {
export const sessionTable = pgTable("session", {
id: text("id").primaryKey(),
expiresAt: timestamp("expires_at").notNull(),
token: text("token").notNull().unique(),
createdAt: timestamp("created_at").notNull(),
updatedAt: timestamp("updated_at").notNull(),
ipAddress: text("ip_address"),
userAgent: text("user_agent"),
userId: text("user_id")
.notNull()
.references(() => users_temp.id, { onDelete: "cascade" }),
impersonatedBy: text("impersonated_by"),
activeOrganizationId: text("active_organization_id"),
.references(() => auth.id, { onDelete: "cascade" }),
expiresAt: timestamp("expires_at", {
withTimezone: true,
mode: "date",
}).notNull(),
});

View File

@@ -0,0 +1,27 @@
import { pgTable, text } from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod";
import { nanoid } from "nanoid";
import { z } from "zod";
export const source = pgTable("project", {
projectId: text("projectId")
.notNull()
.primaryKey()
.$defaultFn(() => nanoid()),
name: text("name").notNull(),
description: text("description"),
createdAt: text("createdAt")
.notNull()
.$defaultFn(() => new Date().toISOString()),
});
const createSchema = createInsertSchema(source, {
name: z.string().min(1),
description: z.string(),
projectId: z.string(),
});
export const apiCreate = createSchema.pick({
name: true,
description: true,
});

View File

@@ -3,7 +3,7 @@ import { pgTable, text } from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod";
import { nanoid } from "nanoid";
import { sshKeyCreate, sshKeyType } from "../validations";
import { organization } from "./account";
import { admins } from "./admin";
import { applications } from "./application";
import { compose } from "./compose";
import { server } from "./server";
@@ -21,18 +21,18 @@ export const sshKeys = pgTable("ssh-key", {
.notNull()
.$defaultFn(() => new Date().toISOString()),
lastUsedAt: text("lastUsedAt"),
organizationId: text("organizationId")
.notNull()
.references(() => organization.id, { onDelete: "cascade" }),
adminId: text("adminId").references(() => admins.adminId, {
onDelete: "cascade",
}),
});
export const sshKeysRelations = relations(sshKeys, ({ many, one }) => ({
applications: many(applications),
compose: many(compose),
servers: many(server),
organization: one(organization, {
fields: [sshKeys.organizationId],
references: [organization.id],
admin: one(admins, {
fields: [sshKeys.adminId],
references: [admins.adminId],
}),
}));
@@ -48,7 +48,7 @@ export const apiCreateSshKey = createSchema
description: true,
privateKey: true,
publicKey: true,
organizationId: true,
adminId: true,
})
.merge(sshKeyCreate.pick({ privateKey: true }));

View File

@@ -1,18 +1,10 @@
import { relations } from "drizzle-orm";
import {
boolean,
integer,
jsonb,
pgTable,
text,
timestamp,
} from "drizzle-orm/pg-core";
import { relations, sql } from "drizzle-orm";
import { boolean, pgTable, text, timestamp } from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod";
import { nanoid } from "nanoid";
import { z } from "zod";
import { account, organization, apikey } from "./account";
import { projects } from "./project";
import { certificateType } from "./shared";
import { admins } from "./admin";
import { auth } from "./auth";
/**
* This is an example of how to use the multi-project schema feature of Drizzle ORM. Use the same
* database instance for multiple projects.
@@ -20,115 +12,75 @@ import { certificateType } from "./shared";
* @see https://orm.drizzle.team/docs/goodies#multi-project-schema
*/
// OLD TABLE
// TEMP
export const users_temp = pgTable("user_temp", {
id: text("id")
export const users = pgTable("user", {
userId: text("userId")
.notNull()
.primaryKey()
.$defaultFn(() => nanoid()),
name: text("name").notNull().default(""),
token: text("token").notNull(),
isRegistered: boolean("isRegistered").notNull().default(false),
expirationDate: text("expirationDate")
expirationDate: timestamp("expirationDate", {
precision: 3,
mode: "string",
}).notNull(),
createdAt: text("createdAt")
.notNull()
.$defaultFn(() => new Date().toISOString()),
createdAt2: text("createdAt")
.notNull()
.$defaultFn(() => new Date().toISOString()),
createdAt: timestamp("created_at").defaultNow(),
// Auth
twoFactorEnabled: boolean("two_factor_enabled"),
email: text("email").notNull().unique(),
emailVerified: boolean("email_verified").notNull(),
image: text("image"),
banned: boolean("banned"),
banReason: text("ban_reason"),
banExpires: timestamp("ban_expires"),
updatedAt: timestamp("updated_at").notNull(),
// Admin
serverIp: text("serverIp"),
certificateType: certificateType("certificateType").notNull().default("none"),
host: text("host"),
letsEncryptEmail: text("letsEncryptEmail"),
sshPrivateKey: text("sshPrivateKey"),
enableDockerCleanup: boolean("enableDockerCleanup").notNull().default(false),
enableLogRotation: boolean("enableLogRotation").notNull().default(false),
// Metrics
enablePaidFeatures: boolean("enablePaidFeatures").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")
canCreateProjects: boolean("canCreateProjects").notNull().default(false),
canAccessToSSHKeys: boolean("canAccessToSSHKeys").notNull().default(false),
canCreateServices: boolean("canCreateServices").notNull().default(false),
canDeleteProjects: boolean("canDeleteProjects").notNull().default(false),
canDeleteServices: boolean("canDeleteServices").notNull().default(false),
canAccessToDocker: boolean("canAccessToDocker").notNull().default(false),
canAccessToAPI: boolean("canAccessToAPI").notNull().default(false),
canAccessToGitProviders: boolean("canAccessToGitProviders")
.notNull()
.default(false),
cleanupCacheOnPreviews: boolean("cleanupCacheOnPreviews")
canAccessToTraefikFiles: boolean("canAccessToTraefikFiles")
.notNull()
.default(false),
cleanupCacheOnCompose: boolean("cleanupCacheOnCompose")
accessedProjects: text("accesedProjects")
.array()
.notNull()
.default(false),
stripeCustomerId: text("stripeCustomerId"),
stripeSubscriptionId: text("stripeSubscriptionId"),
serversQuantity: integer("serversQuantity").notNull().default(0),
.default(sql`ARRAY[]::text[]`),
accessedServices: text("accesedServices")
.array()
.notNull()
.default(sql`ARRAY[]::text[]`),
adminId: text("adminId")
.notNull()
.references(() => admins.adminId, { onDelete: "cascade" }),
authId: text("authId")
.notNull()
.references(() => auth.id, { onDelete: "cascade" }),
});
export const usersRelations = relations(users_temp, ({ one, many }) => ({
account: one(account, {
fields: [users_temp.id],
references: [account.userId],
export const usersRelations = relations(users, ({ one }) => ({
auth: one(auth, {
fields: [users.authId],
references: [auth.id],
}),
admin: one(admins, {
fields: [users.adminId],
references: [admins.adminId],
}),
organizations: many(organization),
projects: many(projects),
apiKeys: many(apikey),
}));
const createSchema = createInsertSchema(users_temp, {
id: z.string().min(1),
const createSchema = createInsertSchema(users, {
userId: z.string().min(1),
authId: z.string().min(1),
token: z.string().min(1),
isRegistered: z.boolean().optional(),
adminId: z.string(),
accessedProjects: z.array(z.string()).optional(),
accessedServices: z.array(z.string()).optional(),
canCreateProjects: z.boolean().optional(),
canCreateServices: z.boolean().optional(),
canDeleteProjects: z.boolean().optional(),
canDeleteServices: z.boolean().optional(),
canAccessToDocker: z.boolean().optional(),
canAccessToTraefikFiles: z.boolean().optional(),
});
export const apiCreateUserInvitation = createSchema.pick({}).extend({
@@ -137,172 +89,41 @@ export const apiCreateUserInvitation = createSchema.pick({}).extend({
export const apiRemoveUser = createSchema
.pick({
id: true,
authId: true,
})
.required();
export const apiFindOneToken = createSchema
.pick({})
.required()
.extend({
token: z.string().min(1),
});
.pick({
token: true,
})
.required();
export const apiAssignPermissions = createSchema
.pick({
id: true,
// canCreateProjects: true,
// canCreateServices: true,
// canDeleteProjects: true,
// canDeleteServices: true,
// accessedProjects: true,
// accessedServices: true,
// canAccessToTraefikFiles: true,
// canAccessToDocker: true,
// canAccessToAPI: true,
// canAccessToSSHKeys: true,
// canAccessToGitProviders: true,
})
.extend({
accessedProjects: z.array(z.string()).optional(),
accessedServices: z.array(z.string()).optional(),
canCreateProjects: z.boolean().optional(),
canCreateServices: z.boolean().optional(),
canDeleteProjects: z.boolean().optional(),
canDeleteServices: z.boolean().optional(),
canAccessToDocker: z.boolean().optional(),
canAccessToTraefikFiles: z.boolean().optional(),
canAccessToAPI: z.boolean().optional(),
canAccessToSSHKeys: z.boolean().optional(),
canAccessToGitProviders: z.boolean().optional(),
userId: true,
canCreateProjects: true,
canCreateServices: true,
canDeleteProjects: true,
canDeleteServices: true,
accessedProjects: true,
accessedServices: true,
canAccessToTraefikFiles: true,
canAccessToDocker: true,
canAccessToAPI: true,
canAccessToSSHKeys: true,
canAccessToGitProviders: true,
})
.required();
export const apiFindOneUser = createSchema
.pick({
id: true,
userId: true,
})
.required();
export const apiFindOneUserByAuth = createSchema
.pick({
// authId: true,
authId: true,
})
.required();
export const apiSaveSSHKey = createSchema
.pick({
sshPrivateKey: true,
})
.required();
export const apiAssignDomain = createSchema
.pick({
host: true,
certificateType: true,
letsEncryptEmail: true,
})
.required()
.partial({
letsEncryptEmail: true,
});
export const apiUpdateDockerCleanup = createSchema
.pick({
enableDockerCleanup: true,
})
.required()
.extend({
serverId: z.string().optional(),
});
export const apiTraefikConfig = z.object({
traefikConfig: z.string().min(1),
});
export const apiModifyTraefikConfig = z.object({
path: z.string().min(1),
traefikConfig: z.string().min(1),
serverId: z.string().optional(),
});
export const apiReadTraefikConfig = z.object({
path: z.string().min(1),
serverId: z.string().optional(),
});
export const apiEnableDashboard = z.object({
enableDashboard: z.boolean().optional(),
serverId: z.string().optional(),
});
export const apiServerSchema = z
.object({
serverId: z.string().optional(),
})
.optional();
export const apiReadStatsLogs = z.object({
page: z
.object({
pageIndex: z.number(),
pageSize: z.number(),
})
.optional(),
status: z.string().array().optional(),
search: z.string().optional(),
sort: z.object({ id: z.string(), desc: z.boolean() }).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({
password: z.string().optional(),
currentPassword: 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(),
});

View File

@@ -12,6 +12,7 @@ import {
Tailwind,
Text,
} from "@react-email/components";
import * as React from "react";
export type TemplateProps = {
projectName: string;

View File

@@ -12,6 +12,7 @@ import {
Tailwind,
Text,
} from "@react-email/components";
import * as React from "react";
export type TemplateProps = {
projectName: string;

View File

@@ -10,6 +10,7 @@ import {
Tailwind,
Text,
} from "@react-email/components";
import * as React from "react";
export type TemplateProps = {
projectName: string;

View File

@@ -1,5 +1,6 @@
import {
Body,
Button,
Container,
Head,
Heading,
@@ -10,6 +11,7 @@ import {
Tailwind,
Text,
} from "@react-email/components";
import * as React from "react";
export type TemplateProps = {
message: string;

View File

@@ -10,6 +10,7 @@ import {
Tailwind,
Text,
} from "@react-email/components";
import * as React from "react";
export type TemplateProps = {
date: string;

View File

@@ -9,6 +9,7 @@ import {
Preview,
Text,
} from "@react-email/components";
import * as React from "react";
interface NotionMagicLinkEmailProps {
loginCode?: string;

View File

@@ -9,6 +9,7 @@ import {
Section,
Text,
} from "@react-email/components";
import * as React from "react";
interface PlaidVerifyIdentityEmailProps {
validationCode?: string;

View File

@@ -11,6 +11,7 @@ import {
Section,
Text,
} from "@react-email/components";
import * as React from "react";
const baseUrl = process.env.VERCEL_URL!;

View File

@@ -15,6 +15,7 @@ import {
Tailwind,
Text,
} from "@react-email/components";
import * as React from "react";
interface VercelInviteUserEmailProps {
username?: string;

View File

@@ -1,4 +1,7 @@
export * from "./auth/auth";
export * from "./auth/token";
export * from "./auth/random-password";
// export * from "./db";
export * from "./services/admin";
export * from "./services/user";
export * from "./services/project";
@@ -27,6 +30,7 @@ export * from "./services/ssh-key";
export * from "./services/git-provider";
export * from "./services/bitbucket";
export * from "./services/github";
export * from "./services/auth";
export * from "./services/gitlab";
export * from "./services/server";
export * from "./services/application";
@@ -35,7 +39,6 @@ export * from "./setup/config-paths";
export * from "./setup/postgres-setup";
export * from "./setup/redis-setup";
export * from "./setup/server-setup";
export * from "./setup/monitoring-setup";
export * from "./setup/setup";
export * from "./setup/traefik-setup";
export * from "./setup/server-validate";
@@ -54,7 +57,6 @@ export * from "./utils/notifications/database-backup";
export * from "./utils/notifications/dokploy-restart";
export * from "./utils/notifications/utils";
export * from "./utils/notifications/docker-cleanup";
export * from "./utils/notifications/server-threshold";
export * from "./utils/builders/index";
export * from "./utils/builders/compose";
@@ -69,7 +71,6 @@ export * from "./utils/builders/utils";
export * from "./utils/cluster/upload";
export * from "./utils/docker/compose";
export * from "./utils/docker/collision";
export * from "./utils/docker/domain";
export * from "./utils/docker/utils";
export * from "./utils/docker/types";
@@ -109,10 +110,8 @@ export * from "./utils/access-log/types";
export * from "./utils/access-log/utils";
export * from "./constants/index";
export * from "./monitoring/utils";
export * from "./monitoring/utilts";
export * from "./db/validations/domain";
export * from "./db/validations/index";
export * from "./utils/gpu-setup";
export * from "./lib/auth";

View File

@@ -1,304 +0,0 @@
import type { IncomingMessage } from "node:http";
import * as bcrypt from "bcrypt";
import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { organization, twoFactor, apiKey } from "better-auth/plugins";
import { and, desc, eq } from "drizzle-orm";
import { db } from "../db";
import * as schema from "../db/schema";
import { sendEmail } from "../verification/send-verification-email";
import { IS_CLOUD } from "../constants";
const { handler, api } = betterAuth({
database: drizzleAdapter(db, {
provider: "pg",
schema: schema,
}),
logger: {
disabled: process.env.NODE_ENV === "production",
},
appName: "Dokploy",
socialProviders: {
github: {
clientId: process.env.GITHUB_CLIENT_ID as string,
clientSecret: process.env.GITHUB_CLIENT_SECRET as string,
},
google: {
clientId: process.env.GOOGLE_CLIENT_ID as string,
clientSecret: process.env.GOOGLE_CLIENT_SECRET as string,
},
},
emailVerification: {
sendOnSignUp: true,
autoSignInAfterVerification: true,
sendVerificationEmail: async ({ user, url }) => {
if (IS_CLOUD) {
await sendEmail({
email: user.email,
subject: "Verify your email",
text: `
<p>Click the link to verify your email: <a href="${url}">Verify Email</a></p>
`,
});
}
},
},
emailAndPassword: {
enabled: true,
autoSignIn: !IS_CLOUD,
requireEmailVerification: IS_CLOUD,
password: {
async hash(password) {
return bcrypt.hashSync(password, 10);
},
async verify({ hash, password }) {
return bcrypt.compareSync(password, hash);
},
},
sendResetPassword: async ({ user, url }) => {
await sendEmail({
email: user.email,
subject: "Reset your password",
text: `
<p>Click the link to reset your password: <a href="${url}">Reset Password</a></p>
`,
});
},
},
databaseHooks: {
user: {
create: {
after: async (user) => {
const isAdminPresent = await db.query.member.findFirst({
where: eq(schema.member.role, "owner"),
});
if (IS_CLOUD || !isAdminPresent) {
await db.transaction(async (tx) => {
const organization = await tx
.insert(schema.organization)
.values({
name: "My Organization",
ownerId: user.id,
createdAt: new Date(),
})
.returning()
.then((res) => res[0]);
await tx.insert(schema.member).values({
userId: user.id,
organizationId: organization?.id || "",
role: "owner",
createdAt: new Date(),
});
});
}
},
},
},
session: {
create: {
before: async (session) => {
const member = await db.query.member.findFirst({
where: eq(schema.member.userId, session.userId),
orderBy: desc(schema.member.createdAt),
with: {
organization: true,
},
});
return {
data: {
...session,
activeOrganizationId: member?.organization.id,
},
};
},
},
},
},
user: {
modelName: "users_temp",
additionalFields: {
role: {
type: "string",
// required: true,
input: false,
},
ownerId: {
type: "string",
// required: true,
input: false,
},
},
},
plugins: [
apiKey({
enableMetadata: true,
}),
twoFactor(),
organization({
async sendInvitationEmail(data, _request) {
if (IS_CLOUD) {
const host =
process.env.NODE_ENV === "development"
? "http://localhost:3000"
: "https://dokploy.com";
const inviteLink = `${host}/invitation?token=${data.id}`;
await sendEmail({
email: data.email,
subject: "Invitation to join organization",
text: `
<p>You are invited to join ${data.organization.name} on Dokploy. Click the link to accept the invitation: <a href="${inviteLink}">Accept Invitation</a></p>
`,
});
}
},
}),
],
});
export const auth = {
handler,
createApiKey: api.createApiKey,
};
export const validateRequest = async (request: IncomingMessage) => {
const apiKey = request.headers["x-api-key"] as string;
if (apiKey) {
try {
const { valid, key, error } = await api.verifyApiKey({
body: {
key: apiKey,
},
});
if (error) {
throw new Error(error.message || "Error verifying API key");
}
if (!valid || !key) {
return {
session: null,
user: null,
};
}
const apiKeyRecord = await db.query.apikey.findFirst({
where: eq(schema.apikey.id, key.id),
with: {
user: true,
},
});
if (!apiKeyRecord) {
return {
session: null,
user: null,
};
}
const organizationId = JSON.parse(
apiKeyRecord.metadata || "{}",
).organizationId;
if (!organizationId) {
return {
session: null,
user: null,
};
}
const member = await db.query.member.findFirst({
where: and(
eq(schema.member.userId, apiKeyRecord.user.id),
eq(schema.member.organizationId, organizationId),
),
with: {
organization: true,
},
});
const {
id,
name,
email,
emailVerified,
image,
createdAt,
updatedAt,
twoFactorEnabled,
} = apiKeyRecord.user;
const mockSession = {
session: {
user: {
id: apiKeyRecord.user.id,
email: apiKeyRecord.user.email,
name: apiKeyRecord.user.name,
},
activeOrganizationId: organizationId || "",
},
user: {
id,
name,
email,
emailVerified,
image,
createdAt,
updatedAt,
twoFactorEnabled,
role: member?.role || "member",
ownerId: member?.organization.ownerId || apiKeyRecord.user.id,
},
};
return mockSession;
} catch (error) {
console.error("Error verifying API key", error);
return {
session: null,
user: null,
};
}
}
// If no API key, proceed with normal session validation
const session = await api.getSession({
headers: new Headers({
cookie: request.headers.cookie || "",
}),
});
if (!session?.session || !session.user) {
return {
session: null,
user: null,
};
}
if (session?.user) {
const member = await db.query.member.findFirst({
where: and(
eq(schema.member.userId, session.user.id),
eq(
schema.member.organizationId,
session.session.activeOrganizationId || "",
),
),
with: {
organization: true,
},
});
session.user.role = member?.role || "member";
if (member) {
session.user.ownerId = member.organization.ownerId;
} else {
session.user.ownerId = session.user.id;
}
}
return session;
};

View File

@@ -1,19 +1,10 @@
import { promises } from "node:fs";
import type Dockerode from "dockerode";
import osUtils from "node-os-utils";
import { paths } from "../constants";
export interface Container {
BlockIO: string;
CPUPerc: string;
Container: string;
ID: string;
MemPerc: string;
MemUsage: string;
Name: string;
NetIO: string;
}
export const recordAdvancedStats = async (
stats: Container,
stats: Dockerode.ContainerStats,
appName: string,
) => {
const { MONITORING_PATH } = paths();
@@ -21,20 +12,29 @@ export const recordAdvancedStats = async (
await promises.mkdir(path, { recursive: true });
await updateStatsFile(appName, "cpu", stats.CPUPerc);
await updateStatsFile(appName, "memory", {
used: stats.MemUsage.split(" ")[0],
total: stats.MemUsage.split(" ")[2],
});
const cpuPercent = calculateCpuUsagePercent(
stats.cpu_stats,
stats.precpu_stats,
);
const memoryStats = calculateMemoryStats(stats.memory_stats);
const blockIO = calculateBlockIO(stats.blkio_stats);
const networkUsage = calculateNetworkUsage(stats.networks);
await updateStatsFile(appName, "cpu", cpuPercent);
await updateStatsFile(appName, "memory", {
used: memoryStats.used,
free: memoryStats.free,
usedPercentage: memoryStats.usedPercentage,
total: memoryStats.total,
});
await updateStatsFile(appName, "block", {
readMb: stats.BlockIO.split(" ")[0],
writeMb: stats.BlockIO.split(" ")[2],
readMb: blockIO.readMb,
writeMb: blockIO.writeMb,
});
await updateStatsFile(appName, "network", {
inputMb: stats.NetIO.split(" ")[0],
outputMb: stats.NetIO.split(" ")[2],
inputMb: networkUsage.inputMb,
outputMb: networkUsage.outputMb,
});
if (appName === "dokploy") {
@@ -73,7 +73,7 @@ export const readStatsFile = async (
const filePath = `${MONITORING_PATH}/${appName}/${statType}.json`;
const data = await promises.readFile(filePath, "utf-8");
return JSON.parse(data);
} catch (_error) {
} catch (error) {
return [];
}
};
@@ -108,7 +108,7 @@ export const readLastValueStatsFile = async (
const data = await promises.readFile(filePath, "utf-8");
const stats = JSON.parse(data);
return stats[stats.length - 1] || null;
} catch (_error) {
} catch (error) {
return null;
}
};
@@ -122,3 +122,77 @@ export const getLastAdvancedStatsFile = async (appName: string) => {
block: await readLastValueStatsFile(appName, "block"),
};
};
const calculateCpuUsagePercent = (
cpu_stats: Dockerode.ContainerStats["cpu_stats"],
precpu_stats: Dockerode.ContainerStats["precpu_stats"],
) => {
const cpuDelta =
cpu_stats.cpu_usage.total_usage - precpu_stats.cpu_usage.total_usage;
const systemDelta =
cpu_stats.system_cpu_usage - precpu_stats.system_cpu_usage;
const numberCpus =
cpu_stats.online_cpus ||
(cpu_stats.cpu_usage.percpu_usage
? cpu_stats.cpu_usage.percpu_usage.length
: 1);
if (systemDelta > 0 && cpuDelta > 0) {
return (cpuDelta / systemDelta) * numberCpus * 100.0;
}
return 0;
};
const calculateMemoryStats = (
memory_stats: Dockerode.ContainerStats["memory_stats"],
) => {
const usedMemory = memory_stats.usage - (memory_stats.stats.cache || 0);
const availableMemory = memory_stats.limit;
const memoryUsedPercentage = (usedMemory / availableMemory) * 100.0;
return {
used: usedMemory,
free: availableMemory - usedMemory,
usedPercentage: memoryUsedPercentage,
total: availableMemory,
};
};
const calculateBlockIO = (
blkio_stats: Dockerode.ContainerStats["blkio_stats"],
) => {
let readIO = 0;
let writeIO = 0;
if (blkio_stats?.io_service_bytes_recursive) {
for (const io of blkio_stats.io_service_bytes_recursive) {
if (io.op === "read") {
readIO += io.value;
} else if (io.op === "write") {
writeIO += io.value;
}
}
}
return {
readMb: readIO / (1024 * 1024),
writeMb: writeIO / (1024 * 1024),
};
};
const calculateNetworkUsage = (
networks: Dockerode.ContainerStats["networks"],
) => {
let totalRx = 0;
let totalTx = 0;
const stats = Object.keys(networks);
for (const interfaceName of stats) {
const net = networks[interfaceName];
totalRx += net?.rx_bytes || 0;
totalTx += net?.tx_bytes || 0;
}
return {
inputMb: totalRx / (1024 * 1024),
outputMb: totalTx / (1024 * 1024),
};
};

View File

@@ -1,56 +1,108 @@
import { randomBytes } from "node:crypto";
import { db } from "@dokploy/server/db";
import {
invitation,
member,
organization,
users_temp,
admins,
type apiCreateUserInvitation,
auth,
users,
} from "@dokploy/server/db/schema";
import { TRPCError } from "@trpc/server";
import * as bcrypt from "bcrypt";
import { eq } from "drizzle-orm";
import { IS_CLOUD } from "../constants";
export const findUserById = async (userId: string) => {
const user = await db.query.users_temp.findFirst({
where: eq(users_temp.id, userId),
// with: {
// account: true,
// },
export type Admin = typeof admins.$inferSelect;
export const createInvitation = async (
input: typeof apiCreateUserInvitation._type,
adminId: string,
) => {
await db.transaction(async (tx) => {
const result = await tx
.insert(auth)
.values({
email: input.email.toLowerCase(),
rol: "user",
password: bcrypt.hashSync("01231203012312", 10),
})
.returning()
.then((res) => res[0]);
if (!result) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error creating the user",
});
}
const expiresIn24Hours = new Date();
expiresIn24Hours.setDate(expiresIn24Hours.getDate() + 1);
const token = randomBytes(32).toString("hex");
await tx
.insert(users)
.values({
adminId: adminId,
authId: result.id,
token,
expirationDate: expiresIn24Hours.toISOString(),
})
.returning();
});
if (!user) {
throw new TRPCError({
code: "NOT_FOUND",
message: "User not found",
});
}
return user;
};
export const findOrganizationById = async (organizationId: string) => {
const organizationResult = await db.query.organization.findFirst({
where: eq(organization.id, organizationId),
export const findAdminById = async (adminId: string) => {
const admin = await db.query.admins.findFirst({
where: eq(admins.adminId, adminId),
});
return organizationResult;
if (!admin) {
throw new TRPCError({
code: "NOT_FOUND",
message: "Admin not found",
});
}
return admin;
};
export const updateAdmin = async (
authId: string,
adminData: Partial<Admin>,
) => {
const admin = await db
.update(admins)
.set({
...adminData,
})
.where(eq(admins.authId, authId))
.returning()
.then((res) => res[0]);
return admin;
};
export const isAdminPresent = async () => {
const admin = await db.query.member.findFirst({
where: eq(member.role, "owner"),
});
const admin = await db.query.admins.findFirst();
if (!admin) {
return false;
}
return true;
};
export const findAdmin = async () => {
const admin = await db.query.member.findFirst({
where: eq(member.role, "owner"),
export const findAdminByAuthId = async (authId: string) => {
const admin = await db.query.admins.findFirst({
where: eq(admins.authId, authId),
with: {
user: true,
users: true,
},
});
if (!admin) {
throw new TRPCError({
code: "NOT_FOUND",
message: "Admin not found",
});
}
return admin;
};
export const findAdmin = async () => {
const admin = await db.query.admins.findFirst({});
if (!admin) {
throw new TRPCError({
code: "NOT_FOUND",
@@ -61,15 +113,14 @@ export const findAdmin = async () => {
};
export const getUserByToken = async (token: string) => {
const user = await db.query.invitation.findFirst({
where: eq(invitation.id, token),
columns: {
id: true,
email: true,
status: true,
expiresAt: true,
role: true,
inviterId: true,
const user = await db.query.users.findFirst({
where: eq(users.token, token),
with: {
auth: {
columns: {
password: false,
},
},
},
});
@@ -79,23 +130,34 @@ export const getUserByToken = async (token: string) => {
message: "Invitation not found",
});
}
const userAlreadyExists = await db.query.users_temp.findFirst({
where: eq(users_temp.email, user?.email || ""),
});
const { expiresAt, ...rest } = user;
return {
...rest,
isExpired: user.expiresAt < new Date(),
userAlreadyExists: !!userAlreadyExists,
...user,
isExpired: user.isRegistered,
};
};
export const removeUserById = async (userId: string) => {
export const removeUserByAuthId = async (authId: string) => {
await db
.delete(users_temp)
.where(eq(users_temp.id, userId))
.delete(auth)
.where(eq(auth.id, authId))
.returning()
.then((res) => res[0]);
};
export const removeAdminByAuthId = async (authId: string) => {
const admin = await findAdminByAuthId(authId);
if (!admin) return null;
// First delete all associated users
const users = admin.users;
for (const user of users) {
await removeUserByAuthId(user.authId);
}
// Then delete the auth record which will cascade delete the admin
return await db
.delete(auth)
.where(eq(auth.id, authId))
.returning()
.then((res) => res[0]);
};
@@ -106,8 +168,8 @@ export const getDokployUrl = async () => {
}
const admin = await findAdmin();
if (admin.user.host) {
return `https://${admin.user.host}`;
if (admin.host) {
return `https://${admin.host}`;
}
return `http://${admin.user.serverIp}:${process.env.PORT}`;
return `http://${admin.serverIp}:${process.env.PORT}`;
};

View File

@@ -4,8 +4,9 @@ import {
type apiCreateApplication,
applications,
buildAppName,
cleanAppName,
} from "@dokploy/server/db/schema";
import { getAdvancedStats } from "@dokploy/server/monitoring/utils";
import { getAdvancedStats } from "@dokploy/server/monitoring/utilts";
import {
buildApplication,
getBuildCommand,
@@ -27,6 +28,7 @@ import {
getCustomGitCloneCommand,
} from "@dokploy/server/utils/providers/git";
import {
authGithub,
cloneGithubRepository,
getGithubCloneCommand,
} from "@dokploy/server/utils/providers/github";
@@ -173,7 +175,6 @@ export const deployApplication = async ({
descriptionLog: string;
}) => {
const application = await findApplicationById(applicationId);
const buildLink = `${await getDokployUrl()}/dashboard/project/${application.projectId}/services/application/${application.applicationId}?tab=deployments`;
const deployment = await createDeployment({
applicationId: applicationId,
@@ -182,12 +183,6 @@ export const deployApplication = async ({
});
try {
// const admin = await findUserById(application.project.userId);
// if (admin.cleanupCacheApplications) {
// await cleanupFullDocker(application?.serverId);
// }
if (application.sourceType === "github") {
await cloneGithubRepository({
...application,
@@ -217,7 +212,7 @@ export const deployApplication = async ({
applicationName: application.name,
applicationType: "application",
buildLink,
organizationId: application.project.organizationId,
adminId: application.project.adminId,
domains: application.domains,
});
} catch (error) {
@@ -230,7 +225,7 @@ export const deployApplication = async ({
// @ts-ignore
errorMessage: error?.message || "Error building",
buildLink,
organizationId: application.project.organizationId,
adminId: application.project.adminId,
});
throw error;
@@ -249,7 +244,6 @@ export const rebuildApplication = async ({
descriptionLog: string;
}) => {
const application = await findApplicationById(applicationId);
const deployment = await createDeployment({
applicationId: applicationId,
title: titleLog,
@@ -257,11 +251,6 @@ export const rebuildApplication = async ({
});
try {
// const admin = await findUserById(application.project.userId);
// if (admin.cleanupCacheApplications) {
// await cleanupFullDocker(application?.serverId);
// }
if (application.sourceType === "github") {
await buildApplication(application, deployment.logPath);
} else if (application.sourceType === "gitlab") {
@@ -296,7 +285,6 @@ export const deployRemoteApplication = async ({
descriptionLog: string;
}) => {
const application = await findApplicationById(applicationId);
const buildLink = `${await getDokployUrl()}/dashboard/project/${application.projectId}/services/application/${application.applicationId}?tab=deployments`;
const deployment = await createDeployment({
applicationId: applicationId,
@@ -306,11 +294,6 @@ export const deployRemoteApplication = async ({
try {
if (application.serverId) {
// const admin = await findUserById(application.project.userId);
// if (admin.cleanupCacheApplications) {
// await cleanupFullDocker(application?.serverId);
// }
let command = "set -e;";
if (application.sourceType === "github") {
command += await getGithubCloneCommand({
@@ -349,7 +332,7 @@ export const deployRemoteApplication = async ({
applicationName: application.name,
applicationType: "application",
buildLink,
organizationId: application.project.organizationId,
adminId: application.project.adminId,
domains: application.domains,
});
} catch (error) {
@@ -373,9 +356,17 @@ export const deployRemoteApplication = async ({
// @ts-ignore
errorMessage: error?.message || "Error building",
buildLink,
organizationId: application.project.organizationId,
adminId: application.project.adminId,
});
console.log(
"Error on ",
application.buildType,
"/",
application.sourceType,
error,
);
throw error;
}
@@ -394,7 +385,6 @@ export const deployPreviewApplication = async ({
previewDeploymentId: string;
}) => {
const application = await findApplicationById(applicationId);
const deployment = await createDeploymentPreview({
title: titleLog,
description: descriptionLog,
@@ -448,15 +438,9 @@ export const deployPreviewApplication = async ({
body: `### Dokploy Preview Deployment\n\n${buildingComment}`,
});
application.appName = previewDeployment.appName;
application.env = `${application.previewEnv}\nDOKPLOY_DEPLOY_URL=${previewDeployment?.domain}`;
application.env = application.previewEnv;
application.buildArgs = application.previewBuildArgs;
// const admin = await findUserById(application.project.userId);
// if (admin.cleanupCacheOnPreviews) {
// await cleanupFullDocker(application?.serverId);
// }
if (application.sourceType === "github") {
await cloneGithubRepository({
...application,
@@ -466,6 +450,7 @@ export const deployPreviewApplication = async ({
});
await buildApplication(application, deployment.logPath);
}
// 4eef09efc46009187d668cf1c25f768d0bde4f91
const successComment = getIssueComment(
application.name,
"success",
@@ -507,7 +492,6 @@ export const deployRemotePreviewApplication = async ({
previewDeploymentId: string;
}) => {
const application = await findApplicationById(applicationId);
const deployment = await createDeploymentPreview({
title: titleLog,
description: descriptionLog,
@@ -561,21 +545,14 @@ export const deployRemotePreviewApplication = async ({
body: `### Dokploy Preview Deployment\n\n${buildingComment}`,
});
application.appName = previewDeployment.appName;
application.env = `${application.previewEnv}\nDOKPLOY_DEPLOY_URL=${previewDeployment?.domain}`;
application.env = application.previewEnv;
application.buildArgs = application.previewBuildArgs;
if (application.serverId) {
// const admin = await findUserById(application.project.userId);
// if (admin.cleanupCacheOnPreviews) {
// await cleanupFullDocker(application?.serverId);
// }
let command = "set -e;";
if (application.sourceType === "github") {
command += await getGithubCloneCommand({
...application,
appName: previewDeployment.appName,
branch: previewDeployment.branch,
serverId: application.serverId,
logPath: deployment.logPath,
});
@@ -625,7 +602,6 @@ export const rebuildRemoteApplication = async ({
descriptionLog: string;
}) => {
const application = await findApplicationById(applicationId);
const deployment = await createDeployment({
applicationId: applicationId,
title: titleLog,
@@ -634,11 +610,6 @@ export const rebuildRemoteApplication = async ({
try {
if (application.serverId) {
// const admin = await findUserById(application.project.userId);
// if (admin.cleanupCacheApplications) {
// await cleanupFullDocker(application?.serverId);
// }
if (application.sourceType !== "docker") {
let command = "set -e;";
command += getBuildCommand(application, deployment.logPath);

View File

@@ -0,0 +1,184 @@
import { randomBytes } from "node:crypto";
import { db } from "@dokploy/server/db";
import {
admins,
type apiCreateAdmin,
type apiCreateUser,
auth,
users,
} from "@dokploy/server/db/schema";
import { getPublicIpWithFallback } from "@dokploy/server/wss/utils";
import { TRPCError } from "@trpc/server";
import * as bcrypt from "bcrypt";
import { eq } from "drizzle-orm";
import encode from "hi-base32";
import { TOTP } from "otpauth";
import QRCode from "qrcode";
import { IS_CLOUD } from "../constants";
export type Auth = typeof auth.$inferSelect;
export const createAdmin = async (input: typeof apiCreateAdmin._type) => {
return await db.transaction(async (tx) => {
const hashedPassword = bcrypt.hashSync(input.password, 10);
const newAuth = await tx
.insert(auth)
.values({
email: input.email.toLowerCase(),
password: hashedPassword,
rol: "admin",
})
.returning()
.then((res) => res[0]);
if (!newAuth) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error creating the user",
});
}
await tx
.insert(admins)
.values({
authId: newAuth.id,
...(!IS_CLOUD && {
serverIp:
process.env.ADVERTISE_ADDR || (await getPublicIpWithFallback()),
}),
})
.returning();
return newAuth;
});
};
export const createUser = async (input: typeof apiCreateUser._type) => {
return await db.transaction(async (tx) => {
const hashedPassword = bcrypt.hashSync(input.password, 10);
const res = await tx
.update(auth)
.set({
password: hashedPassword,
})
.where(eq(auth.id, input.id))
.returning()
.then((res) => res[0]);
if (!res) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error creating the user",
});
}
const user = await tx
.update(users)
.set({
isRegistered: true,
expirationDate: undefined,
})
.where(eq(users.token, input.token))
.returning()
.then((res) => res[0]);
return user;
});
};
export const findAuthByEmail = async (email: string) => {
const result = await db.query.auth.findFirst({
where: eq(auth.email, email),
});
if (!result) {
throw new TRPCError({
code: "NOT_FOUND",
message: "User not found",
});
}
return result;
};
export const findAuthById = async (authId: string) => {
const result = await db.query.auth.findFirst({
where: eq(auth.id, authId),
columns: {
password: false,
},
});
if (!result) {
throw new TRPCError({
code: "NOT_FOUND",
message: "Auth not found",
});
}
return result;
};
export const updateAuthById = async (
authId: string,
authData: Partial<Auth>,
) => {
const result = await db
.update(auth)
.set({
...authData,
})
.where(eq(auth.id, authId))
.returning();
return result[0];
};
export const generate2FASecret = async (authId: string) => {
const auth = await findAuthById(authId);
const base32_secret = generateBase32Secret();
const totp = new TOTP({
issuer: "Dokploy",
label: `${auth?.email}`,
algorithm: "SHA1",
digits: 6,
secret: base32_secret,
});
const otpauth_url = totp.toString();
const qrUrl = await QRCode.toDataURL(otpauth_url);
return {
qrCodeUrl: qrUrl,
secret: base32_secret,
};
};
export const verify2FA = async (
auth: Omit<Auth, "password">,
secret: string,
pin: string,
) => {
const totp = new TOTP({
issuer: "Dokploy",
label: `${auth?.email}`,
algorithm: "SHA1",
digits: 6,
secret: secret,
});
const delta = totp.validate({ token: pin });
if (delta === null) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Invalid 2FA code",
});
}
return auth;
};
const generateBase32Secret = () => {
const buffer = randomBytes(15);
const base32 = encode.encode(buffer).replace(/=/g, "").substring(0, 24);
return base32;
};

View File

@@ -6,7 +6,7 @@ import { eq } from "drizzle-orm";
export type Backup = typeof backups.$inferSelect;
export type BackupSchedule = Awaited<ReturnType<typeof findBackupById>>;
export type BackupScheduleList = Awaited<ReturnType<typeof findBackupsByDbId>>;
export const createBackup = async (input: typeof apiCreateBackup._type) => {
const newBackup = await db
.insert(backups)
@@ -69,20 +69,3 @@ export const removeBackupById = async (backupId: string) => {
return result[0];
};
export const findBackupsByDbId = async (
id: string,
type: "postgres" | "mysql" | "mariadb" | "mongo",
) => {
const result = await db.query.backups.findMany({
where: eq(backups[`${type}Id`], id),
with: {
postgres: true,
mysql: true,
mariadb: true,
mongo: true,
destination: true,
},
});
return result || [];
};

View File

@@ -12,14 +12,14 @@ export type Bitbucket = typeof bitbucket.$inferSelect;
export const createBitbucket = async (
input: typeof apiCreateBitbucket._type,
organizationId: string,
adminId: string,
) => {
return await db.transaction(async (tx) => {
const newGitProvider = await tx
.insert(gitProvider)
.values({
providerType: "bitbucket",
organizationId: organizationId,
adminId: adminId,
name: input.name,
})
.returning()
@@ -74,12 +74,12 @@ export const updateBitbucket = async (
.where(eq(bitbucket.bitbucketId, bitbucketId))
.returning();
if (input.name || input.organizationId) {
if (input.name || input.adminId) {
await tx
.update(gitProvider)
.set({
name: input.name,
organizationId: input.organizationId,
adminId: input.adminId,
})
.where(eq(gitProvider.gitProviderId, input.gitProviderId))
.returning();

View File

@@ -33,13 +33,13 @@ export const findCertificateById = async (certificateId: string) => {
export const createCertificate = async (
certificateData: z.infer<typeof apiCreateCertificate>,
organizationId: string,
adminId: string,
) => {
const certificate = await db
.insert(certificates)
.values({
...certificateData,
organizationId: organizationId,
adminId: adminId,
})
.returning();

View File

@@ -3,6 +3,7 @@ import { paths } from "@dokploy/server/constants";
import { db } from "@dokploy/server/db";
import { type apiCreateCompose, compose } from "@dokploy/server/db/schema";
import { buildAppName, cleanAppName } from "@dokploy/server/db/schema";
import { generatePassword } from "@dokploy/server/templates/utils";
import {
buildCompose,
getBuildComposeCommand,
@@ -205,7 +206,6 @@ export const deployCompose = async ({
descriptionLog: string;
}) => {
const compose = await findComposeById(composeId);
const buildLink = `${await getDokployUrl()}/dashboard/project/${
compose.projectId
}/services/compose/${compose.composeId}?tab=deployments`;
@@ -216,10 +216,6 @@ export const deployCompose = async ({
});
try {
// const admin = await findUserById(compose.project.userId);
// if (admin.cleanupCacheOnCompose) {
// await cleanupFullDocker(compose?.serverId);
// }
if (compose.sourceType === "github") {
await cloneGithubRepository({
...compose,
@@ -246,7 +242,7 @@ export const deployCompose = async ({
applicationName: compose.name,
applicationType: "compose",
buildLink,
organizationId: compose.project.organizationId,
adminId: compose.project.adminId,
domains: compose.domains,
});
} catch (error) {
@@ -261,7 +257,7 @@ export const deployCompose = async ({
// @ts-ignore
errorMessage: error?.message || "Error building",
buildLink,
organizationId: compose.project.organizationId,
adminId: compose.project.adminId,
});
throw error;
}
@@ -277,7 +273,6 @@ export const rebuildCompose = async ({
descriptionLog: string;
}) => {
const compose = await findComposeById(composeId);
const deployment = await createDeploymentCompose({
composeId: composeId,
title: titleLog,
@@ -285,10 +280,6 @@ export const rebuildCompose = async ({
});
try {
// const admin = await findUserById(compose.project.userId);
// if (admin.cleanupCacheOnCompose) {
// await cleanupFullDocker(compose?.serverId);
// }
if (compose.serverId) {
await getBuildComposeCommand(compose, deployment.logPath);
} else {
@@ -320,7 +311,6 @@ export const deployRemoteCompose = async ({
descriptionLog: string;
}) => {
const compose = await findComposeById(composeId);
const buildLink = `${await getDokployUrl()}/dashboard/project/${
compose.projectId
}/services/compose/${compose.composeId}?tab=deployments`;
@@ -331,10 +321,6 @@ export const deployRemoteCompose = async ({
});
try {
if (compose.serverId) {
// const admin = await findUserById(compose.project.userId);
// if (admin.cleanupCacheOnCompose) {
// await cleanupFullDocker(compose?.serverId);
// }
let command = "set -e;";
if (compose.sourceType === "github") {
@@ -380,7 +366,7 @@ export const deployRemoteCompose = async ({
applicationName: compose.name,
applicationType: "compose",
buildLink,
organizationId: compose.project.organizationId,
adminId: compose.project.adminId,
domains: compose.domains,
});
} catch (error) {
@@ -405,7 +391,7 @@ export const deployRemoteCompose = async ({
// @ts-ignore
errorMessage: error?.message || "Error building",
buildLink,
organizationId: compose.project.organizationId,
adminId: compose.project.adminId,
});
throw error;
}
@@ -421,7 +407,6 @@ export const rebuildRemoteCompose = async ({
descriptionLog: string;
}) => {
const compose = await findComposeById(composeId);
const deployment = await createDeploymentCompose({
composeId: composeId,
title: titleLog,
@@ -429,10 +414,6 @@ export const rebuildRemoteCompose = async ({
});
try {
// const admin = await findUserById(compose.project.userId);
// if (admin.cleanupCacheOnCompose) {
// await cleanupFullDocker(compose?.serverId);
// }
if (compose.serverId) {
await getBuildComposeCommand(compose, deployment.logPath);
}
@@ -557,17 +538,6 @@ export const stopCompose = async (composeId: string) => {
}
}
if (compose.composeType === "stack") {
if (compose.serverId) {
await execAsyncRemote(
compose.serverId,
`docker stack rm ${compose.appName}`,
);
} else {
await execAsync(`docker stack rm ${compose.appName}`);
}
}
await updateCompose(composeId, {
composeStatus: "idle",
});

View File

@@ -12,7 +12,7 @@ import {
import { removeDirectoryIfExistsContent } from "@dokploy/server/utils/filesystem/directory";
import { TRPCError } from "@trpc/server";
import { format } from "date-fns";
import { desc, eq } from "drizzle-orm";
import { and, desc, eq, isNull } from "drizzle-orm";
import {
type Application,
findApplicationById,
@@ -98,17 +98,6 @@ export const createDeployment = async (
}
return deploymentCreate[0];
} catch (error) {
await db
.insert(deployments)
.values({
applicationId: deployment.applicationId,
title: deployment.title || "Deployment",
status: "error",
logPath: "",
description: deployment.description || "",
errorMessage: `An error have occured: ${error instanceof Error ? error.message : error}`,
})
.returning();
await updateApplicationStatus(application.applicationId, "error");
console.log(error);
throw new TRPCError({
@@ -175,17 +164,6 @@ export const createDeploymentPreview = async (
}
return deploymentCreate[0];
} catch (error) {
await db
.insert(deployments)
.values({
previewDeploymentId: deployment.previewDeploymentId,
title: deployment.title || "Deployment",
status: "error",
logPath: "",
description: deployment.description || "",
errorMessage: `An error have occured: ${error instanceof Error ? error.message : error}`,
})
.returning();
await updatePreviewDeployment(deployment.previewDeploymentId, {
previewStatus: "error",
});
@@ -248,17 +226,6 @@ echo "Initializing deployment" >> ${logFilePath};
}
return deploymentCreate[0];
} catch (error) {
await db
.insert(deployments)
.values({
composeId: deployment.composeId,
title: deployment.title || "Deployment",
status: "error",
logPath: "",
description: deployment.description || "",
errorMessage: `An error have occured: ${error instanceof Error ? error.message : error}`,
})
.returning();
await updateCompose(compose.composeId, {
composeStatus: "error",
});
@@ -278,11 +245,9 @@ export const removeDeployment = async (deploymentId: string) => {
.returning();
return deployment[0];
} catch (error) {
const message =
error instanceof Error ? error.message : "Error creating the deployment";
throw new TRPCError({
code: "BAD_REQUEST",
message,
message: "Error deleting this deployment",
});
}
};
@@ -537,11 +502,9 @@ export const createServerDeployment = async (
}
return deploymentCreate[0];
} catch (error) {
const message =
error instanceof Error ? error.message : "Error creating the deployment";
throw new TRPCError({
code: "BAD_REQUEST",
message,
message: "Error creating the deployment",
});
}
};

View File

@@ -10,13 +10,13 @@ export type Destination = typeof destinations.$inferSelect;
export const createDestintation = async (
input: typeof apiCreateDestination._type,
organizationId: string,
adminId: string,
) => {
const newDestination = await db
.insert(destinations)
.values({
...input,
organizationId: organizationId,
adminId: adminId,
})
.returning()
.then((value) => value[0]);
@@ -46,14 +46,14 @@ export const findDestinationById = async (destinationId: string) => {
export const removeDestinationById = async (
destinationId: string,
organizationId: string,
adminId: string,
) => {
const result = await db
.delete(destinations)
.where(
and(
eq(destinations.destinationId, destinationId),
eq(destinations.organizationId, organizationId),
eq(destinations.adminId, adminId),
),
)
.returning();
@@ -73,7 +73,7 @@ export const updateDestinationById = async (
.where(
and(
eq(destinations.destinationId, destinationId),
eq(destinations.organizationId, destinationData.organizationId || ""),
eq(destinations.adminId, destinationData.adminId || ""),
),
)
.returning();

View File

@@ -58,11 +58,7 @@ export const getContainers = async (serverId?: string | null) => {
serverId,
};
})
.filter(
(container) =>
!container.name.includes("dokploy") ||
container.name.includes("dokploy-monitoring"),
);
.filter((container) => !container.name.includes("dokploy"));
return containers;
} catch (error) {
@@ -98,7 +94,7 @@ export const getConfig = async (
const config = JSON.parse(stdout);
return config;
} catch (_error) {}
} catch (error) {}
};
export const getContainersByAppNameMatch = async (
@@ -156,7 +152,7 @@ export const getContainersByAppNameMatch = async (
});
return containers || [];
} catch (_error) {}
} catch (error) {}
return [];
};
@@ -214,7 +210,7 @@ export const getStackContainersByAppName = async (
});
return containers || [];
} catch (_error) {}
} catch (error) {}
return [];
};
@@ -274,7 +270,7 @@ export const getServiceContainersByAppName = async (
});
return containers || [];
} catch (_error) {}
} catch (error) {}
return [];
};
@@ -325,7 +321,7 @@ export const getContainersByAppLabel = async (
});
return containers || [];
} catch (_error) {}
} catch (error) {}
return [];
};
@@ -344,7 +340,7 @@ export const containerRestart = async (containerId: string) => {
const config = JSON.parse(stdout);
return config;
} catch (_error) {}
} catch (error) {}
};
export const getSwarmNodes = async (serverId?: string) => {
@@ -373,7 +369,7 @@ export const getSwarmNodes = async (serverId?: string) => {
.split("\n")
.map((line) => JSON.parse(line));
return nodesArray;
} catch (_error) {}
} catch (error) {}
};
export const getNodeInfo = async (nodeId: string, serverId?: string) => {
@@ -399,7 +395,7 @@ export const getNodeInfo = async (nodeId: string, serverId?: string) => {
const nodeInfo = JSON.parse(stdout);
return nodeInfo;
} catch (_error) {}
} catch (error) {}
};
export const getNodeApplications = async (serverId?: string) => {
@@ -431,7 +427,7 @@ export const getNodeApplications = async (serverId?: string) => {
.filter((service) => !service.Name.startsWith("dokploy-"));
return appArray;
} catch (_error) {}
} catch (error) {}
};
export const getApplicationInfo = async (
@@ -464,5 +460,5 @@ export const getApplicationInfo = async (
.map((line) => JSON.parse(line));
return appArray;
} catch (_error) {}
} catch (error) {}
};

View File

@@ -4,7 +4,7 @@ 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 { findAdmin, findAdminById } from "./admin";
import { findApplicationById } from "./application";
import { findServerById } from "./server";
@@ -40,7 +40,7 @@ export const createDomain = async (input: typeof apiCreateDomain._type) => {
export const generateTraefikMeDomain = async (
appName: string,
userId: string,
adminId: string,
serverId?: string,
) => {
if (serverId) {
@@ -57,7 +57,7 @@ export const generateTraefikMeDomain = async (
projectName: appName,
});
}
const admin = await findUserById(userId);
const admin = await findAdminById(adminId);
return generateRandomDomain({
serverIp: admin?.serverIp || "",
projectName: appName,
@@ -126,6 +126,7 @@ export const updateDomainById = async (
export const removeDomainById = async (domainId: string) => {
await findDomainById(domainId);
// TODO: fix order
const result = await db
.delete(domains)
.where(eq(domains.domainId, domainId))

View File

@@ -12,14 +12,14 @@ import { updatePreviewDeployment } from "./preview-deployment";
export type Github = typeof github.$inferSelect;
export const createGithub = async (
input: typeof apiCreateGithub._type,
organizationId: string,
adminId: string,
) => {
return await db.transaction(async (tx) => {
const newGitProvider = await tx
.insert(gitProvider)
.values({
providerType: "github",
organizationId: organizationId,
adminId: adminId,
name: input.name,
})
.returning()
@@ -119,7 +119,7 @@ export const issueCommentExists = async ({
comment_id: comment_id,
});
return true;
} catch (_error) {
} catch (error) {
return false;
}
};

View File

@@ -1,7 +1,9 @@
import { db } from "@dokploy/server/db";
import {
type apiCreateGitlab,
type bitbucket,
gitProvider,
type github,
gitlab,
} from "@dokploy/server/db/schema";
import { TRPCError } from "@trpc/server";
@@ -11,14 +13,14 @@ export type Gitlab = typeof gitlab.$inferSelect;
export const createGitlab = async (
input: typeof apiCreateGitlab._type,
organizationId: string,
adminId: string,
) => {
return await db.transaction(async (tx) => {
const newGitProvider = await tx
.insert(gitProvider)
.values({
providerType: "gitlab",
organizationId: organizationId,
adminId: adminId,
name: input.name,
})
.returning()

View File

@@ -4,7 +4,7 @@ import {
backups,
mariadb,
} from "@dokploy/server/db/schema";
import { buildAppName } from "@dokploy/server/db/schema";
import { buildAppName, cleanAppName } from "@dokploy/server/db/schema";
import { generatePassword } from "@dokploy/server/templates/utils";
import { buildMariadb } from "@dokploy/server/utils/databases/mariadb";
import { pullImage } from "@dokploy/server/utils/docker/utils";

View File

@@ -1,6 +1,6 @@
import { db } from "@dokploy/server/db";
import { type apiCreateMongo, backups, mongo } from "@dokploy/server/db/schema";
import { buildAppName } from "@dokploy/server/db/schema";
import { buildAppName, cleanAppName } from "@dokploy/server/db/schema";
import { generatePassword } from "@dokploy/server/templates/utils";
import { buildMongo } from "@dokploy/server/utils/databases/mongo";
import { pullImage } from "@dokploy/server/utils/docker/utils";

View File

@@ -64,7 +64,7 @@ export const createMount = async (input: typeof apiCreateMount._type) => {
console.log(error);
throw new TRPCError({
code: "BAD_REQUEST",
message: `Error ${error instanceof Error ? error.message : error}`,
message: "Error creating the mount",
cause: error,
});
}
@@ -91,7 +91,7 @@ export const createFileMount = async (mountId: string) => {
console.log(`Error creating the file mount: ${error}`);
throw new TRPCError({
code: "BAD_REQUEST",
message: `Error creating the mount ${error instanceof Error ? error.message : error}`,
message: "Error creating the mount",
cause: error,
});
}
@@ -123,8 +123,8 @@ export const updateMount = async (
mountId: string,
mountData: Partial<Mount>,
) => {
return await db.transaction(async (tx) => {
const mount = await tx
return await db.transaction(async (transaction) => {
const mount = await db
.update(mounts)
.set({
...mountData,
@@ -211,7 +211,7 @@ export const deleteFileMount = async (mountId: string) => {
} else {
await removeFileOrDirectory(fullPath);
}
} catch (_error) {}
} catch (error) {}
};
export const getBaseFilesPath = async (mountId: string) => {

View File

@@ -24,7 +24,7 @@ export type Notification = typeof notifications.$inferSelect;
export const createSlackNotification = async (
input: typeof apiCreateSlack._type,
organizationId: string,
adminId: string,
) => {
await db.transaction(async (tx) => {
const newSlack = await tx
@@ -54,8 +54,7 @@ export const createSlackNotification = async (
dokployRestart: input.dokployRestart,
dockerCleanup: input.dockerCleanup,
notificationType: "slack",
organizationId: organizationId,
serverThreshold: input.serverThreshold,
adminId: adminId,
})
.returning()
.then((value) => value[0]);
@@ -84,8 +83,7 @@ export const updateSlackNotification = async (
databaseBackup: input.databaseBackup,
dokployRestart: input.dokployRestart,
dockerCleanup: input.dockerCleanup,
organizationId: input.organizationId,
serverThreshold: input.serverThreshold,
adminId: input.adminId,
})
.where(eq(notifications.notificationId, input.notificationId))
.returning()
@@ -114,7 +112,7 @@ export const updateSlackNotification = async (
export const createTelegramNotification = async (
input: typeof apiCreateTelegram._type,
organizationId: string,
adminId: string,
) => {
await db.transaction(async (tx) => {
const newTelegram = await tx
@@ -122,7 +120,6 @@ export const createTelegramNotification = async (
.values({
botToken: input.botToken,
chatId: input.chatId,
messageThreadId: input.messageThreadId,
})
.returning()
.then((value) => value[0]);
@@ -145,8 +142,7 @@ export const createTelegramNotification = async (
dokployRestart: input.dokployRestart,
dockerCleanup: input.dockerCleanup,
notificationType: "telegram",
organizationId: organizationId,
serverThreshold: input.serverThreshold,
adminId: adminId,
})
.returning()
.then((value) => value[0]);
@@ -175,8 +171,7 @@ export const updateTelegramNotification = async (
databaseBackup: input.databaseBackup,
dokployRestart: input.dokployRestart,
dockerCleanup: input.dockerCleanup,
organizationId: input.organizationId,
serverThreshold: input.serverThreshold,
adminId: input.adminId,
})
.where(eq(notifications.notificationId, input.notificationId))
.returning()
@@ -194,7 +189,6 @@ export const updateTelegramNotification = async (
.set({
botToken: input.botToken,
chatId: input.chatId,
messageThreadId: input.messageThreadId,
})
.where(eq(telegram.telegramId, input.telegramId))
.returning()
@@ -206,7 +200,7 @@ export const updateTelegramNotification = async (
export const createDiscordNotification = async (
input: typeof apiCreateDiscord._type,
organizationId: string,
adminId: string,
) => {
await db.transaction(async (tx) => {
const newDiscord = await tx
@@ -236,8 +230,7 @@ export const createDiscordNotification = async (
dokployRestart: input.dokployRestart,
dockerCleanup: input.dockerCleanup,
notificationType: "discord",
organizationId: organizationId,
serverThreshold: input.serverThreshold,
adminId: adminId,
})
.returning()
.then((value) => value[0]);
@@ -266,8 +259,7 @@ export const updateDiscordNotification = async (
databaseBackup: input.databaseBackup,
dokployRestart: input.dokployRestart,
dockerCleanup: input.dockerCleanup,
organizationId: input.organizationId,
serverThreshold: input.serverThreshold,
adminId: input.adminId,
})
.where(eq(notifications.notificationId, input.notificationId))
.returning()
@@ -296,7 +288,7 @@ export const updateDiscordNotification = async (
export const createEmailNotification = async (
input: typeof apiCreateEmail._type,
organizationId: string,
adminId: string,
) => {
await db.transaction(async (tx) => {
const newEmail = await tx
@@ -330,8 +322,7 @@ export const createEmailNotification = async (
dokployRestart: input.dokployRestart,
dockerCleanup: input.dockerCleanup,
notificationType: "email",
organizationId: organizationId,
serverThreshold: input.serverThreshold,
adminId: adminId,
})
.returning()
.then((value) => value[0]);
@@ -360,8 +351,7 @@ export const updateEmailNotification = async (
databaseBackup: input.databaseBackup,
dokployRestart: input.dokployRestart,
dockerCleanup: input.dockerCleanup,
organizationId: input.organizationId,
serverThreshold: input.serverThreshold,
adminId: input.adminId,
})
.where(eq(notifications.notificationId, input.notificationId))
.returning()
@@ -394,7 +384,7 @@ export const updateEmailNotification = async (
export const createGotifyNotification = async (
input: typeof apiCreateGotify._type,
organizationId: string,
adminId: string,
) => {
await db.transaction(async (tx) => {
const newGotify = await tx
@@ -426,7 +416,7 @@ export const createGotifyNotification = async (
dokployRestart: input.dokployRestart,
dockerCleanup: input.dockerCleanup,
notificationType: "gotify",
organizationId: organizationId,
adminId: adminId,
})
.returning()
.then((value) => value[0]);
@@ -455,7 +445,7 @@ export const updateGotifyNotification = async (
databaseBackup: input.databaseBackup,
dokployRestart: input.dokployRestart,
dockerCleanup: input.dockerCleanup,
organizationId: input.organizationId,
adminId: input.adminId,
})
.where(eq(notifications.notificationId, input.notificationId))
.returning()

View File

@@ -4,7 +4,7 @@ import {
backups,
postgres,
} from "@dokploy/server/db/schema";
import { buildAppName } from "@dokploy/server/db/schema";
import { buildAppName, cleanAppName } from "@dokploy/server/db/schema";
import { generatePassword } from "@dokploy/server/templates/utils";
import { buildPostgres } from "@dokploy/server/utils/databases/postgres";
import { pullImage } from "@dokploy/server/utils/docker/utils";

View File

@@ -2,20 +2,23 @@ import { db } from "@dokploy/server/db";
import {
type apiCreatePreviewDeployment,
deployments,
organization,
previewDeployments,
} from "@dokploy/server/db/schema";
import { TRPCError } from "@trpc/server";
import { and, desc, eq } from "drizzle-orm";
import { generatePassword } from "../templates/utils";
import { slugify } from "../setup/server-setup";
import { generatePassword, generateRandomDomain } from "../templates/utils";
import { removeService } from "../utils/docker/utils";
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 { findAdminById } from "./admin";
import { findApplicationById } from "./application";
import { removeDeploymentsByPreviewDeploymentId } from "./deployment";
import {
removeDeployments,
removeDeploymentsByPreviewDeploymentId,
} from "./deployment";
import { createDomain } from "./domain";
import { type Github, getIssueComment } from "./github";
@@ -103,17 +106,13 @@ export const removePreviewDeployment = async (previewDeploymentId: string) => {
for (const operation of cleanupOperations) {
try {
await operation();
} catch (_error) {}
} catch (error) {}
}
return deployment[0];
} catch (error) {
const message =
error instanceof Error
? error.message
: "Error deleting this preview deployment";
throw new TRPCError({
code: "BAD_REQUEST",
message,
message: "Error deleting this preview deployment",
});
}
};
@@ -155,14 +154,11 @@ export const createPreviewDeployment = async (
const application = await findApplicationById(schema.applicationId);
const appName = `preview-${application.appName}-${generatePassword(6)}`;
const org = await db.query.organization.findFirst({
where: eq(organization.id, application.project.organizationId),
});
const generateDomain = await generateWildcardDomain(
application.previewWildcard || "*.traefik.me",
appName,
application.server?.ipAddress || "",
org?.ownerId || "",
application.project.adminId,
);
const octokit = authGithub(application?.github as Github);
@@ -254,7 +250,7 @@ const generateWildcardDomain = async (
baseDomain: string,
appName: string,
serverIp: string,
userId: string,
adminId: string,
): Promise<string> => {
if (!baseDomain.startsWith("*.")) {
throw new Error('The base domain must start with "*."');
@@ -272,7 +268,7 @@ const generateWildcardDomain = async (
}
if (!ip) {
const admin = await findUserById(userId);
const admin = await findAdminById(adminId);
ip = admin?.serverIp || "";
}

View File

@@ -16,13 +16,13 @@ export type Project = typeof projects.$inferSelect;
export const createProject = async (
input: typeof apiCreateProject._type,
organizationId: string,
adminId: string,
) => {
const newProject = await db
.insert(projects)
.values({
...input,
organizationId: organizationId,
adminId: adminId,
})
.returning()
.then((value) => value[0]);

View File

@@ -6,7 +6,7 @@ import {
updateRedirectMiddleware,
} from "@dokploy/server/utils/traefik/redirect";
import { TRPCError } from "@trpc/server";
import { eq } from "drizzle-orm";
import { desc, eq } from "drizzle-orm";
import type { z } from "zod";
import { findApplicationById } from "./application";
export type Redirect = typeof redirects.$inferSelect;
@@ -114,11 +114,9 @@ export const updateRedirectById = async (
return redirect;
} catch (error) {
const message =
error instanceof Error ? error.message : "Error updating this redirect";
throw new TRPCError({
code: "BAD_REQUEST",
message,
message: "Error updating this redirect",
});
}
};

View File

@@ -1,6 +1,6 @@
import { db } from "@dokploy/server/db";
import { type apiCreateRedis, redis } from "@dokploy/server/db/schema";
import { buildAppName } from "@dokploy/server/db/schema";
import { buildAppName, cleanAppName } from "@dokploy/server/db/schema";
import { generatePassword } from "@dokploy/server/templates/utils";
import { buildRedis } from "@dokploy/server/utils/databases/redis";
import { pullImage } from "@dokploy/server/utils/docker/utils";

View File

@@ -12,14 +12,14 @@ export type Registry = typeof registry.$inferSelect;
export const createRegistry = async (
input: typeof apiCreateRegistry._type,
organizationId: string,
adminId: string,
) => {
return await db.transaction(async (tx) => {
const newRegistry = await tx
.insert(registry)
.values({
...input,
organizationId: organizationId,
adminId: adminId,
})
.returning()
.then((value) => value[0]);
@@ -112,11 +112,9 @@ export const updateRegistry = async (
return response;
} catch (error) {
const message =
error instanceof Error ? error.message : "Error updating this registry";
throw new TRPCError({
code: "BAD_REQUEST",
message,
message: "Error updating this registry",
});
}
};
@@ -137,11 +135,9 @@ export const findRegistryById = async (registryId: string) => {
return registryResponse;
};
export const findAllRegistryByOrganizationId = async (
organizationId: string,
) => {
export const findAllRegistryByAdminId = async (adminId: string) => {
const registryResponse = await db.query.registry.findMany({
where: eq(registry.organizationId, organizationId),
where: eq(registry.adminId, adminId),
});
return registryResponse;
};

View File

@@ -76,11 +76,9 @@ export const deleteSecurityById = async (securityId: string) => {
await removeSecurityMiddleware(application, result);
return result;
} catch (error) {
const message =
error instanceof Error ? error.message : "Error removing this security";
throw new TRPCError({
code: "BAD_REQUEST",
message,
message: "Error removing this security",
});
}
};
@@ -100,11 +98,9 @@ export const updateSecurityById = async (
return response[0];
} catch (error) {
const message =
error instanceof Error ? error.message : "Error updating this security";
throw new TRPCError({
code: "BAD_REQUEST",
message,
message: "Error updating this security",
});
}
};

View File

@@ -1,24 +1,19 @@
import { db } from "@dokploy/server/db";
import {
type apiCreateServer,
organization,
server,
} from "@dokploy/server/db/schema";
import { type apiCreateServer, server } from "@dokploy/server/db/schema";
import { TRPCError } from "@trpc/server";
import { eq } from "drizzle-orm";
import { desc, eq } from "drizzle-orm";
export type Server = typeof server.$inferSelect;
export const createServer = async (
input: typeof apiCreateServer._type,
organizationId: string,
adminId: string,
) => {
const newServer = await db
.insert(server)
.values({
...input,
organizationId: organizationId,
createdAt: new Date().toISOString(),
adminId: adminId,
})
.returning()
.then((value) => value[0]);
@@ -50,16 +45,12 @@ export const findServerById = async (serverId: string) => {
return currentServer;
};
export const findServersByUserId = async (userId: string) => {
const orgs = await db.query.organization.findMany({
where: eq(organization.ownerId, userId),
with: {
servers: true,
},
export const findServersByAdminId = async (adminId: string) => {
const servers = await db.query.server.findMany({
where: eq(server.adminId, adminId),
orderBy: desc(server.createdAt),
});
const servers = orgs.flatMap((org) => org.servers);
return servers;
};

View File

@@ -5,6 +5,7 @@ import {
execAsync,
execAsyncRemote,
} from "@dokploy/server/utils/process/execAsync";
// import packageInfo from "../../../package.json";
export interface IUpdateData {
latestVersion: string | null;
@@ -169,6 +170,7 @@ echo "$json_output"
const result = JSON.parse(stdout);
return result;
}
const items = readdirSync(dirPath, { withFileTypes: true });
const stack = [dirPath];
const result: TreeDataItem[] = [];
@@ -211,35 +213,3 @@ echo "$json_output"
}
return result;
};
export const cleanupFullDocker = async (serverId?: string | null) => {
const cleanupImages = "docker image prune --force";
const cleanupVolumes = "docker volume prune --force";
const cleanupContainers = "docker container prune --force";
const cleanupSystem = "docker system prune --force --volumes";
const cleanupBuilder = "docker builder prune --force";
try {
if (serverId) {
await execAsyncRemote(
serverId,
`
${cleanupImages}
${cleanupVolumes}
${cleanupContainers}
${cleanupSystem}
${cleanupBuilder}
`,
);
}
await execAsync(`
${cleanupImages}
${cleanupVolumes}
${cleanupContainers}
${cleanupSystem}
${cleanupBuilder}
`);
} catch (error) {
console.log(error);
}
};

View File

@@ -1,53 +1,80 @@
import { db } from "@dokploy/server/db";
import { apikey, member, users_temp } from "@dokploy/server/db/schema";
import { users } from "@dokploy/server/db/schema";
import { TRPCError } from "@trpc/server";
import { and, eq } from "drizzle-orm";
import { auth } from "../lib/auth";
import { eq } from "drizzle-orm";
export type User = typeof users_temp.$inferSelect;
export type User = typeof users.$inferSelect;
export const addNewProject = async (
userId: string,
projectId: string,
organizationId: string,
) => {
const userR = await findMemberById(userId, organizationId);
await db
.update(member)
.set({
accessedProjects: [...userR.accessedProjects, projectId],
})
.where(
and(eq(member.id, userR.id), eq(member.organizationId, organizationId)),
);
export const findUserById = async (userId: string) => {
const user = await db.query.users.findFirst({
where: eq(users.userId, userId),
});
if (!user) {
throw new TRPCError({
code: "NOT_FOUND",
message: "User not found",
});
}
return user;
};
export const addNewService = async (
userId: string,
serviceId: string,
organizationId: string,
) => {
const userR = await findMemberById(userId, organizationId);
export const findUserByAuthId = async (authId: string) => {
const user = await db.query.users.findFirst({
where: eq(users.authId, authId),
with: {
auth: true,
},
});
if (!user) {
throw new TRPCError({
code: "NOT_FOUND",
message: "User not found",
});
}
return user;
};
export const findUsers = async (adminId: string) => {
const currentUsers = await db.query.users.findMany({
where: eq(users.adminId, adminId),
with: {
auth: {
columns: {
secret: false,
},
},
},
});
return currentUsers;
};
export const addNewProject = async (authId: string, projectId: string) => {
const user = await findUserByAuthId(authId);
await db
.update(member)
.update(users)
.set({
accessedServices: [...userR.accessedServices, serviceId],
accessedProjects: [...user.accessedProjects, projectId],
})
.where(
and(eq(member.id, userR.id), eq(member.organizationId, organizationId)),
);
.where(eq(users.authId, authId));
};
export const addNewService = async (authId: string, serviceId: string) => {
const user = await findUserByAuthId(authId);
await db
.update(users)
.set({
accessedServices: [...user.accessedServices, serviceId],
})
.where(eq(users.authId, authId));
};
export const canPerformCreationService = async (
userId: string,
projectId: string,
organizationId: string,
) => {
const { accessedProjects, canCreateServices } = await findMemberById(
userId,
organizationId,
);
const { accessedProjects, canCreateServices } =
await findUserByAuthId(userId);
const haveAccessToProject = accessedProjects.includes(projectId);
if (canCreateServices && haveAccessToProject) {
@@ -60,9 +87,8 @@ export const canPerformCreationService = async (
export const canPerformAccessService = async (
userId: string,
serviceId: string,
organizationId: string,
) => {
const { accessedServices } = await findMemberById(userId, organizationId);
const { accessedServices } = await findUserByAuthId(userId);
const haveAccessToService = accessedServices.includes(serviceId);
if (haveAccessToService) {
@@ -73,14 +99,11 @@ export const canPerformAccessService = async (
};
export const canPeformDeleteService = async (
userId: string,
authId: string,
serviceId: string,
organizationId: string,
) => {
const { accessedServices, canDeleteServices } = await findMemberById(
userId,
organizationId,
);
const { accessedServices, canDeleteServices } =
await findUserByAuthId(authId);
const haveAccessToService = accessedServices.includes(serviceId);
if (canDeleteServices && haveAccessToService) {
@@ -90,11 +113,8 @@ export const canPeformDeleteService = async (
return false;
};
export const canPerformCreationProject = async (
userId: string,
organizationId: string,
) => {
const { canCreateProjects } = await findMemberById(userId, organizationId);
export const canPerformCreationProject = async (authId: string) => {
const { canCreateProjects } = await findUserByAuthId(authId);
if (canCreateProjects) {
return true;
@@ -103,11 +123,8 @@ export const canPerformCreationProject = async (
return false;
};
export const canPerformDeleteProject = async (
userId: string,
organizationId: string,
) => {
const { canDeleteProjects } = await findMemberById(userId, organizationId);
export const canPerformDeleteProject = async (authId: string) => {
const { canDeleteProjects } = await findUserByAuthId(authId);
if (canDeleteProjects) {
return true;
@@ -117,11 +134,10 @@ export const canPerformDeleteProject = async (
};
export const canPerformAccessProject = async (
userId: string,
authId: string,
projectId: string,
organizationId: string,
) => {
const { accessedProjects } = await findMemberById(userId, organizationId);
const { accessedProjects } = await findUserByAuthId(authId);
const haveAccessToProject = accessedProjects.includes(projectId);
@@ -131,45 +147,26 @@ export const canPerformAccessProject = async (
return false;
};
export const canAccessToTraefikFiles = async (
userId: string,
organizationId: string,
) => {
const { canAccessToTraefikFiles } = await findMemberById(
userId,
organizationId,
);
export const canAccessToTraefikFiles = async (authId: string) => {
const { canAccessToTraefikFiles } = await findUserByAuthId(authId);
return canAccessToTraefikFiles;
};
export const checkServiceAccess = async (
userId: string,
authId: string,
serviceId: string,
organizationId: string,
action = "access" as "access" | "create" | "delete",
) => {
let hasPermission = false;
switch (action) {
case "create":
hasPermission = await canPerformCreationService(
userId,
serviceId,
organizationId,
);
hasPermission = await canPerformCreationService(authId, serviceId);
break;
case "access":
hasPermission = await canPerformAccessService(
userId,
serviceId,
organizationId,
);
hasPermission = await canPerformAccessService(authId, serviceId);
break;
case "delete":
hasPermission = await canPeformDeleteService(
userId,
serviceId,
organizationId,
);
hasPermission = await canPeformDeleteService(authId, serviceId);
break;
default:
hasPermission = false;
@@ -185,7 +182,6 @@ export const checkServiceAccess = async (
export const checkProjectAccess = async (
authId: string,
action: "create" | "delete" | "access",
organizationId: string,
projectId?: string,
) => {
let hasPermission = false;
@@ -194,14 +190,13 @@ export const checkProjectAccess = async (
hasPermission = await canPerformAccessProject(
authId,
projectId as string,
organizationId,
);
break;
case "create":
hasPermission = await canPerformCreationProject(authId, organizationId);
hasPermission = await canPerformCreationProject(authId);
break;
case "delete":
hasPermission = await canPerformDeleteProject(authId, organizationId);
hasPermission = await canPerformDeleteProject(authId);
break;
default:
hasPermission = false;
@@ -213,82 +208,3 @@ export const checkProjectAccess = async (
});
}
};
export const findMemberById = async (
userId: string,
organizationId: string,
) => {
const result = await db.query.member.findFirst({
where: and(
eq(member.userId, userId),
eq(member.organizationId, organizationId),
),
with: {
user: true,
},
});
if (!result) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "Permission denied",
});
}
return result;
};
export const updateUser = async (userId: string, userData: Partial<User>) => {
const user = await db
.update(users_temp)
.set({
...userData,
})
.where(eq(users_temp.id, userId))
.returning()
.then((res) => res[0]);
return user;
};
export const createApiKey = async (
userId: string,
input: {
name: string;
prefix?: string;
expiresIn?: number;
metadata: {
organizationId: string;
};
rateLimitEnabled?: boolean;
rateLimitTimeWindow?: number;
rateLimitMax?: number;
remaining?: number;
refillAmount?: number;
refillInterval?: number;
},
) => {
const apiKey = await auth.createApiKey({
body: {
name: input.name,
expiresIn: input.expiresIn,
prefix: input.prefix,
rateLimitEnabled: input.rateLimitEnabled,
rateLimitTimeWindow: input.rateLimitTimeWindow,
rateLimitMax: input.rateLimitMax,
remaining: input.remaining,
refillAmount: input.refillAmount,
refillInterval: input.refillInterval,
userId,
},
});
if (input.metadata) {
await db
.update(apikey)
.set({
metadata: JSON.stringify(input.metadata),
})
.where(eq(apikey.id, apiKey.id));
}
return apiKey;
};

View File

@@ -1,148 +0,0 @@
import { findServerById } from "@dokploy/server/services/server";
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";
import { getRemoteDocker } from "../utils/servers/remote-docker";
export const setupMonitoring = async (serverId: string) => {
const server = await findServerById(serverId);
const containerName = "dokploy-monitoring";
let imageName = "dokploy/monitoring:latest";
if (
(getDokployImageTag() !== "latest" ||
process.env.NODE_ENV === "development") &&
!IS_CLOUD
) {
imageName = "dokploy/monitoring:canary";
}
const settings: ContainerCreateOptions = {
name: containerName,
Env: [`METRICS_CONFIG=${JSON.stringify(server?.metricsConfig)}`],
Image: imageName,
HostConfig: {
// Memory: 100 * 1024 * 1024, // 100MB en bytes
// PidMode: "host",
// CapAdd: ["NET_ADMIN", "SYS_ADMIN"],
// Privileged: true,
PortBindings: {
[`${server.metricsConfig.server.port}/tcp`]: [
{
HostPort: server.metricsConfig.server.port.toString(),
},
],
},
Binds: [
"/var/run/docker.sock:/var/run/docker.sock:ro",
"/sys:/host/sys:ro",
"/etc/os-release:/etc/os-release:ro",
"/proc:/host/proc:ro",
"/etc/dokploy/monitoring/monitoring.db:/app/monitoring.db",
],
NetworkMode: "host",
},
ExposedPorts: {
[`${server.metricsConfig.server.port}/tcp`]: {},
},
};
const docker = await getRemoteDocker(serverId);
try {
await execAsyncRemote(
serverId,
"mkdir -p /etc/dokploy/monitoring && touch /etc/dokploy/monitoring/monitoring.db",
);
if (serverId) {
await pullRemoteImage(imageName, serverId);
}
// Check if container exists
const container = docker.getContainer(containerName);
try {
await container.inspect();
await container.remove({ force: true });
console.log("Removed existing container");
} catch (_error) {
// Container doesn't exist, continue
}
await docker.createContainer(settings);
const newContainer = docker.getContainer(containerName);
await newContainer.start();
console.log("Monitoring Started ");
} catch (error) {
console.log("Monitoring Not Found: Starting ", error);
}
};
export const setupWebMonitoring = async (userId: string) => {
const user = await findUserById(userId);
const containerName = "dokploy-monitoring";
let imageName = "dokploy/monitoring:latest";
if (
(getDokployImageTag() !== "latest" ||
process.env.NODE_ENV === "development") &&
!IS_CLOUD
) {
imageName = "dokploy/monitoring:canary";
}
const settings: ContainerCreateOptions = {
name: containerName,
Env: [`METRICS_CONFIG=${JSON.stringify(user?.metricsConfig)}`],
Image: imageName,
HostConfig: {
// Memory: 100 * 1024 * 1024, // 100MB en bytes
// PidMode: "host",
// CapAdd: ["NET_ADMIN", "SYS_ADMIN"],
// Privileged: true,
PortBindings: {
[`${user?.metricsConfig?.server?.port}/tcp`]: [
{
HostPort: user?.metricsConfig?.server?.port.toString(),
},
],
},
Binds: [
"/var/run/docker.sock:/var/run/docker.sock:ro",
"/sys:/host/sys:ro",
"/etc/os-release:/etc/os-release:ro",
"/proc:/host/proc:ro",
"/etc/dokploy/monitoring/monitoring.db:/app/monitoring.db",
],
// NetworkMode: "host",
},
ExposedPorts: {
[`${user?.metricsConfig?.server?.port}/tcp`]: {},
},
};
const docker = await getRemoteDocker();
try {
await execAsync(
"mkdir -p /etc/dokploy/monitoring && touch /etc/dokploy/monitoring/monitoring.db",
);
await pullImage(imageName);
const container = docker.getContainer(containerName);
try {
await container.inspect();
await container.remove({ force: true });
console.log("Removed existing container");
} catch (_error) {}
await docker.createContainer(settings);
const newContainer = docker.getContainer(containerName);
await newContainer.start();
console.log("Monitoring Started ");
} catch (error) {
console.log("Monitoring Not Found: Starting ", error);
}
};

View File

@@ -54,16 +54,10 @@ export const initializePostgres = async () => {
version: Number.parseInt(inspect.Version.Index),
...settings,
});
console.log("Postgres Started ✅");
} catch (_) {
try {
await docker.createService(settings);
} catch (error: any) {
if (error?.statusCode !== 409) {
throw error;
}
console.log("Postgres service already exists, continuing...");
}
} catch (error) {
await docker.createService(settings);
console.log("Postgres Not Found: Starting ✅");
}
};

View File

@@ -52,15 +52,8 @@ export const initializeRedis = async () => {
...settings,
});
console.log("Redis Started ✅");
} catch (_) {
try {
await docker.createService(settings);
} catch (error: any) {
if (error?.statusCode !== 409) {
throw error;
}
console.log("Redis service already exists, continuing...");
}
} catch (error) {
await docker.createService(settings);
console.log("Redis Not Found: Starting ✅");
}
};

View File

@@ -89,7 +89,7 @@ export const serverAudit = async (serverId: string) => {
.on("data", (data: string) => {
output += data;
})
.stderr.on("data", (_data) => {});
.stderr.on("data", (data) => {});
});
})
.on("error", (err) => {

View File

@@ -128,7 +128,7 @@ export const serverValidate = async (serverId: string) => {
.on("data", (data: string) => {
output += data;
})
.stderr.on("data", (_data) => {});
.stderr.on("data", (data) => {});
});
})
.on("error", (err) => {

View File

@@ -18,7 +18,7 @@ export const dockerSwarmInitialized = async () => {
await docker.swarmInspect();
return true;
} catch (_e) {
} catch (e) {
return false;
}
};
@@ -41,7 +41,7 @@ export const dockerNetworkInitialized = async () => {
try {
await docker.getNetwork("dokploy-network").inspect();
return true;
} catch (_e) {
} catch (e) {
return false;
}
};

View File

@@ -68,6 +68,9 @@ export const initializeTraefik = async ({
Replicas: 1,
},
},
Labels: {
"traefik.enable": "true",
},
EndpointSpec: {
Ports: [
{
@@ -127,15 +130,8 @@ export const initializeTraefik = async ({
});
console.log("Traefik Started ✅");
} catch (_) {
try {
await docker.createService(settings);
} catch (error: any) {
if (error?.statusCode !== 409) {
throw error;
}
console.log("Traefik service already exists, continuing...");
}
} catch (error) {
await docker.createService(settings);
console.log("Traefik Not Found: Starting ✅");
}
};
@@ -193,12 +189,10 @@ export const getDefaultTraefikConfig = () => {
: {
swarm: {
exposedByDefault: false,
watch: true,
watch: false,
},
docker: {
exposedByDefault: false,
watch: true,
network: "dokploy-network",
},
}),
file: {
@@ -249,12 +243,10 @@ export const getDefaultServerTraefikConfig = () => {
providers: {
swarm: {
exposedByDefault: false,
watch: true,
watch: false,
},
docker: {
exposedByDefault: false,
watch: true,
network: "dokploy-network",
},
file: {
directory: "/etc/dokploy/traefik/dynamic",

View File

@@ -36,7 +36,7 @@ type AnyObj = Record<PropertyKey, unknown>;
type ZodObj<T extends AnyObj> = {
[key in keyof T]: z.ZodType<T[key]>;
};
const _zObject = <T extends AnyObj>(arg: ZodObj<T>) => z.object(arg);
const zObject = <T extends AnyObj>(arg: ZodObj<T>) => z.object(arg);
// const goodDogScheme = zObject<UserWithPosts>({
// // prueba: schema.selectDatabaseSchema,

View File

@@ -1,8 +1,8 @@
import { IS_CLOUD, paths } from "@dokploy/server/constants";
import { updateAdmin } from "@dokploy/server/services/admin";
import { type RotatingFileStream, createStream } from "rotating-file-stream";
import { db } from "../../db";
import { execAsync } from "../process/execAsync";
import { findAdmin } from "@dokploy/server/services/admin";
import { updateUser } from "@dokploy/server/services/user";
class LogRotationManager {
private static instance: LogRotationManager;
@@ -30,16 +30,17 @@ class LogRotationManager {
}
private async getStateFromDB(): Promise<boolean> {
const admin = await findAdmin();
return admin?.user.enableLogRotation ?? false;
const setting = await db.query.admins.findFirst({});
return setting?.enableLogRotation ?? false;
}
private async setStateInDB(active: boolean): Promise<void> {
const admin = await findAdmin();
const admin = await db.query.admins.findFirst({});
if (!admin) {
return;
}
await updateUser(admin.user.id, {
await updateAdmin(admin?.authId, {
enableLogRotation: active,
});
}

View File

@@ -1,3 +1,4 @@
import { findAdmin } from "@dokploy/server/services/admin";
import { getAllServers } from "@dokploy/server/services/server";
import { scheduleJob } from "node-schedule";
import { db } from "../../db/index";
@@ -11,14 +12,13 @@ import { runMariadbBackup } from "./mariadb";
import { runMongoBackup } from "./mongo";
import { runMySqlBackup } from "./mysql";
import { runPostgresBackup } from "./postgres";
import { findAdmin } from "../../services/admin";
export const initCronJobs = async () => {
console.log("Setting up cron jobs....");
const admin = await findAdmin();
if (admin?.user.enableDockerCleanup) {
if (admin?.enableDockerCleanup) {
scheduleJob("docker-cleanup", "0 0 * * *", async () => {
console.log(
`Docker Cleanup ${new Date().toLocaleString()}] Running docker cleanup`,
@@ -26,7 +26,7 @@ export const initCronJobs = async () => {
await cleanUpUnusedImages();
await cleanUpDockerBuilder();
await cleanUpSystemPrune();
await sendDockerCleanupNotifications(admin.user.id);
await sendDockerCleanupNotifications(admin.adminId);
});
}
@@ -43,7 +43,7 @@ export const initCronJobs = async () => {
await cleanUpDockerBuilder(serverId);
await cleanUpSystemPrune(serverId);
await sendDockerCleanupNotifications(
admin.user.id,
admin.adminId,
`Docker cleanup for Server ${name} (${serverId})`,
);
});

View File

@@ -49,7 +49,7 @@ export const runMariadbBackup = async (
projectName: project.name,
databaseType: "mariadb",
type: "success",
organizationId: project.organizationId,
adminId: project.adminId,
});
} catch (error) {
console.log(error);
@@ -60,7 +60,7 @@ export const runMariadbBackup = async (
type: "error",
// @ts-ignore
errorMessage: error?.message || "Error message not provided",
organizationId: project.organizationId,
adminId: project.adminId,
});
throw error;
}

View File

@@ -46,7 +46,7 @@ export const runMongoBackup = async (mongo: Mongo, backup: BackupSchedule) => {
projectName: project.name,
databaseType: "mongodb",
type: "success",
organizationId: project.organizationId,
adminId: project.adminId,
});
} catch (error) {
console.log(error);
@@ -57,7 +57,7 @@ export const runMongoBackup = async (mongo: Mongo, backup: BackupSchedule) => {
type: "error",
// @ts-ignore
errorMessage: error?.message || "Error message not provided",
organizationId: project.organizationId,
adminId: project.adminId,
});
throw error;
}

View File

@@ -1,3 +1,4 @@
import { unlink } from "node:fs/promises";
import path from "node:path";
import type { BackupSchedule } from "@dokploy/server/services/backup";
import type { MySql } from "@dokploy/server/services/mysql";
@@ -45,7 +46,7 @@ export const runMySqlBackup = async (mysql: MySql, backup: BackupSchedule) => {
projectName: project.name,
databaseType: "mysql",
type: "success",
organizationId: project.organizationId,
adminId: project.adminId,
});
} catch (error) {
console.log(error);
@@ -56,7 +57,7 @@ export const runMySqlBackup = async (mysql: MySql, backup: BackupSchedule) => {
type: "error",
// @ts-ignore
errorMessage: error?.message || "Error message not provided",
organizationId: project.organizationId,
adminId: project.adminId,
});
throw error;
}

View File

@@ -49,7 +49,7 @@ export const runPostgresBackup = async (
projectName: project.name,
databaseType: "postgres",
type: "success",
organizationId: project.organizationId,
adminId: project.adminId,
});
} catch (error) {
await sendDatabaseBackupNotifications({
@@ -59,7 +59,7 @@ export const runPostgresBackup = async (
type: "error",
// @ts-ignore
errorMessage: error?.message || "Error message not provided",
organizationId: project.organizationId,
adminId: project.adminId,
});
throw error;

View File

@@ -28,7 +28,7 @@ export const removeScheduleBackup = (backupId: string) => {
};
export const getS3Credentials = (destination: Destination) => {
const { accessKey, secretAccessKey, region, endpoint, provider } =
const { accessKey, secretAccessKey, bucket, region, endpoint, provider } =
destination;
const rcloneFlags = [
`--s3-access-key-id=${accessKey}`,

View File

@@ -12,12 +12,8 @@ import {
writeDomainsToCompose,
writeDomainsToComposeRemote,
} from "../docker/domain";
import {
encodeBase64,
getEnviromentVariablesObject,
prepareEnvironmentVariables,
} from "../docker/utils";
import { execAsync, execAsyncRemote } from "../process/execAsync";
import { encodeBase64, prepareEnvironmentVariables } from "../docker/utils";
import { execAsyncRemote } from "../process/execAsync";
import { spawnAsync } from "../process/spawnAsync";
export type ComposeNested = InferResultType<
@@ -33,19 +29,13 @@ export const buildCompose = async (compose: ComposeNested, logPath: string) => {
await writeDomainsToCompose(compose, domains);
createEnvFile(compose);
if (compose.isolatedDeployment) {
await execAsync(
`docker network inspect ${compose.appName} >/dev/null 2>&1 || docker network create --attachable ${compose.appName}`,
);
}
const logContent = `
App Name: ${appName}
Build Compose 🐳
Detected: ${mounts.length} mounts 📂
Command: docker ${command}
Source Type: docker ${sourceType}
Compose Type: ${composeType}`;
App Name: ${appName}
Build Compose 🐳
Detected: ${mounts.length} mounts 📂
Command: docker ${command}
Source Type: docker ${sourceType}
Compose Type: ${composeType}`;
const logBox = boxen(logContent, {
padding: {
left: 1,
@@ -56,6 +46,7 @@ export const buildCompose = async (compose: ComposeNested, logPath: string) => {
borderStyle: "double",
});
writeStream.write(`\n${logBox}\n`);
const projectPath = join(COMPOSE_PATH, compose.appName, "code");
await spawnAsync(
@@ -71,19 +62,10 @@ export const buildCompose = async (compose: ComposeNested, logPath: string) => {
env: {
NODE_ENV: process.env.NODE_ENV,
PATH: process.env.PATH,
...(composeType === "stack" && {
...getEnviromentVariablesObject(compose.env, compose.project.env),
}),
},
},
);
if (compose.isolatedDeployment) {
await execAsync(
`docker network connect ${compose.appName} $(docker ps --filter "name=dokploy-traefik" -q) >/dev/null 2>&1`,
).catch(() => {});
}
writeStream.write("Docker Compose Deployed: ✅");
} catch (error) {
writeStream.write(`Error ❌ ${(error as Error).message}`);
@@ -98,11 +80,11 @@ export const getBuildComposeCommand = async (
logPath: string,
) => {
const { COMPOSE_PATH } = paths(true);
const { sourceType, appName, mounts, composeType, domains } = compose;
const { sourceType, appName, mounts, composeType, domains, composePath } =
compose;
const command = createCommand(compose);
const envCommand = getCreateEnvFileCommand(compose);
const projectPath = join(COMPOSE_PATH, compose.appName, "code");
const exportEnvCommand = getExportEnvCommand(compose);
const newCompose = await writeDomainsToComposeRemote(
compose,
@@ -138,10 +120,7 @@ Compose Type: ${composeType} ✅`;
cd "${projectPath}";
${exportEnvCommand}
${compose.isolatedDeployment ? `docker network inspect ${compose.appName} >/dev/null 2>&1 || docker network create --attachable ${compose.appName}` : ""}
docker ${command.split(" ").join(" ")} >> "${logPath}" 2>&1 || { echo "Error: ❌ Docker command failed" >> "${logPath}"; exit 1; }
${compose.isolatedDeployment ? `docker network connect ${compose.appName} $(docker ps --filter "name=dokploy-traefik" -q) >/dev/null 2>&1` : ""}
echo "Docker Compose Deployed: ✅" >> "${logPath}"
} || {
@@ -165,6 +144,7 @@ const sanitizeCommand = (command: string) => {
export const createCommand = (compose: ComposeNested) => {
const { composeType, appName, sourceType } = compose;
if (compose.command) {
return `${sanitizeCommand(compose.command)}`;
}
@@ -239,17 +219,3 @@ touch ${envFilePath};
echo "${encodedContent}" | base64 -d > "${envFilePath}";
`;
};
const getExportEnvCommand = (compose: ComposeNested) => {
if (compose.composeType !== "stack") return "";
const envVars = getEnviromentVariablesObject(
compose.env,
compose.project.env,
);
const exports = Object.entries(envVars)
.map(([key, value]) => `export ${key}=${JSON.stringify(value)}`)
.join("\n");
return exports ? `\n# Export environment variables\n${exports}\n` : "";
};

View File

@@ -197,7 +197,7 @@ export const mechanizeDockerContainer = async (
ForceUpdate: inspect.Spec.TaskTemplate.ForceUpdate + 1,
},
});
} catch (_error) {
} catch (error) {
await docker.createService(settings);
}
};

View File

@@ -91,7 +91,7 @@ export const getNixpacksCommand = (
application: ApplicationNested,
logPath: string,
) => {
const { env, appName, publishDirectory } = application;
const { env, appName, publishDirectory, serverId } = application;
const buildAppDirectory = getBuildAppDirectory(application);
const buildContainerId = `${appName}-${nanoid(10)}`;

View File

@@ -98,7 +98,7 @@ export const buildMariadb = async (mariadb: MariadbNested) => {
version: Number.parseInt(inspect.Version.Index),
...settings,
});
} catch (_error) {
} catch (error) {
await docker.createService(settings);
}
};

View File

@@ -152,7 +152,7 @@ ${command ?? "wait $MONGOD_PID"}`;
version: Number.parseInt(inspect.Version.Index),
...settings,
});
} catch (_error) {
} catch (error) {
await docker.createService(settings);
}
};

View File

@@ -104,7 +104,7 @@ export const buildMysql = async (mysql: MysqlNested) => {
version: Number.parseInt(inspect.Version.Index),
...settings,
});
} catch (_error) {
} catch (error) {
await docker.createService(settings);
}
};

View File

@@ -95,7 +95,7 @@ export const buildRedis = async (redis: RedisNested) => {
version: Number.parseInt(inspect.Version.Index),
...settings,
});
} catch (_error) {
} catch (error) {
await docker.createService(settings);
}
};

View File

@@ -1,46 +0,0 @@
import { findComposeById } from "@dokploy/server/services/compose";
import { dump, load } from "js-yaml";
import { addAppNameToAllServiceNames } from "./collision/root-network";
import { generateRandomHash } from "./compose";
import { addSuffixToAllVolumes } from "./compose/volume";
import type { ComposeSpecification } from "./types";
export const addAppNameToPreventCollision = (
composeData: ComposeSpecification,
appName: string,
): ComposeSpecification => {
let updatedComposeData = { ...composeData };
updatedComposeData = addAppNameToAllServiceNames(updatedComposeData, appName);
updatedComposeData = addSuffixToAllVolumes(updatedComposeData, appName);
return updatedComposeData;
};
export const randomizeIsolatedDeploymentComposeFile = async (
composeId: string,
suffix?: string,
) => {
const compose = await findComposeById(composeId);
const composeFile = compose.composeFile;
const composeData = load(composeFile) as ComposeSpecification;
const randomSuffix = suffix || compose.appName || generateRandomHash();
const newComposeFile = addAppNameToPreventCollision(
composeData,
randomSuffix,
);
return dump(newComposeFile);
};
export const randomizeDeployableSpecificationFile = (
composeSpec: ComposeSpecification,
suffix?: string,
) => {
if (!suffix) {
return composeSpec;
}
const newComposeFile = addAppNameToPreventCollision(composeSpec, suffix);
return newComposeFile;
};

View File

@@ -1,62 +0,0 @@
import _ from "lodash";
import type { ComposeSpecification, DefinitionsService } from "../types";
export const addAppNameToRootNetwork = (
composeData: ComposeSpecification,
appName: string,
): ComposeSpecification => {
const updatedComposeData = { ...composeData };
// Initialize networks if it doesn't exist
if (!updatedComposeData.networks) {
updatedComposeData.networks = {};
}
// Add the new network with the app name
updatedComposeData.networks[appName] = {
name: appName,
external: true,
};
return updatedComposeData;
};
export const addAppNameToServiceNetworks = (
services: { [key: string]: DefinitionsService },
appName: string,
): { [key: string]: DefinitionsService } => {
return _.mapValues(services, (service) => {
if (!service.networks) {
service.networks = [appName];
return service;
}
if (Array.isArray(service.networks)) {
if (!service.networks.includes(appName)) {
service.networks.push(appName);
}
} else {
service.networks[appName] = {};
}
return service;
});
};
export const addAppNameToAllServiceNames = (
composeData: ComposeSpecification,
appName: string,
): ComposeSpecification => {
let updatedComposeData = { ...composeData };
updatedComposeData = addAppNameToRootNetwork(updatedComposeData, appName);
if (updatedComposeData.services) {
updatedComposeData.services = addAppNameToServiceNetworks(
updatedComposeData.services,
appName,
);
}
return updatedComposeData;
};

View File

@@ -26,7 +26,6 @@ import {
createComposeFileRaw,
createComposeFileRawRemote,
} from "../providers/raw";
import { randomizeDeployableSpecificationFile } from "./collision";
import { randomizeSpecificationFile } from "./compose";
import type {
ComposeSpecification,
@@ -109,7 +108,7 @@ export const loadDockerComposeRemote = async (
if (!stdout) return null;
const parsedConfig = load(stdout) as ComposeSpecification;
return parsedConfig;
} catch (_err) {
} catch (err) {
return null;
}
};
@@ -191,13 +190,7 @@ export const addDomainToCompose = async (
return null;
}
if (compose.isolatedDeployment) {
const randomized = randomizeDeployableSpecificationFile(
result,
compose.suffix || compose.appName,
);
result = randomized;
} else if (compose.randomize) {
if (compose.randomize) {
const randomized = randomizeSpecificationFile(result, compose.suffix);
result = randomized;
}
@@ -210,6 +203,9 @@ export const addDomainToCompose = async (
if (!result?.services?.[serviceName]) {
throw new Error(`The service ${serviceName} not found in the compose`);
}
if (!result.services[serviceName].labels) {
result.services[serviceName].labels = [];
}
const httpLabels = await createDomainLabels(appName, domain, "web");
if (https) {
@@ -221,24 +217,7 @@ export const addDomainToCompose = async (
httpLabels.push(...httpsLabels);
}
let labels: DefinitionsService["labels"] = [];
if (compose.composeType === "docker-compose") {
if (!result.services[serviceName].labels) {
result.services[serviceName].labels = [];
}
labels = result.services[serviceName].labels;
} else {
// Stack Case
if (!result.services[serviceName].deploy) {
result.services[serviceName].deploy = {};
}
if (!result.services[serviceName].deploy.labels) {
result.services[serviceName].deploy.labels = [];
}
labels = result.services[serviceName].deploy.labels;
}
const labels = result.services[serviceName].labels;
if (Array.isArray(labels)) {
if (!labels.includes("traefik.enable=true")) {
@@ -247,18 +226,14 @@ export const addDomainToCompose = async (
labels.push(...httpLabels);
}
if (!compose.isolatedDeployment) {
// Add the dokploy-network to the service
result.services[serviceName].networks = addDokployNetworkToService(
result.services[serviceName].networks,
);
}
// Add the dokploy-network to the service
result.services[serviceName].networks = addDokployNetworkToService(
result.services[serviceName].networks,
);
}
// Add dokploy-network to the root of the compose file
if (!compose.isolatedDeployment) {
result.networks = addDokployNetworkToRoot(result.networks);
}
result.networks = addDokployNetworkToRoot(result.networks);
return result;
};

View File

@@ -100,7 +100,7 @@ export const containerExists = async (containerName: string) => {
try {
await container.inspect();
return true;
} catch (_error) {
} catch (error) {
return false;
}
};
@@ -144,11 +144,10 @@ export const getContainerByName = (name: string): Promise<ContainerInfo> => {
};
export const cleanUpUnusedImages = async (serverId?: string) => {
try {
const command = "docker image prune --force";
if (serverId) {
await execAsyncRemote(serverId, command);
await execAsyncRemote(serverId, "docker image prune --all --force");
} else {
await execAsync(command);
await execAsync("docker image prune --all --force");
}
} catch (error) {
console.error(error);
@@ -158,11 +157,10 @@ export const cleanUpUnusedImages = async (serverId?: string) => {
export const cleanStoppedContainers = async (serverId?: string) => {
try {
const command = "docker container prune --force";
if (serverId) {
await execAsyncRemote(serverId, command);
await execAsyncRemote(serverId, "docker container prune --force");
} else {
await execAsync(command);
await execAsync("docker container prune --force");
}
} catch (error) {
console.error(error);
@@ -172,11 +170,10 @@ export const cleanStoppedContainers = async (serverId?: string) => {
export const cleanUpUnusedVolumes = async (serverId?: string) => {
try {
const command = "docker volume prune --force";
if (serverId) {
await execAsyncRemote(serverId, command);
await execAsyncRemote(serverId, "docker volume prune --all --force");
} else {
await execAsync(command);
await execAsync("docker volume prune --all --force");
}
} catch (error) {
console.error(error);
@@ -202,20 +199,21 @@ export const cleanUpInactiveContainers = async () => {
};
export const cleanUpDockerBuilder = async (serverId?: string) => {
const command = "docker builder prune --all --force";
if (serverId) {
await execAsyncRemote(serverId, command);
await execAsyncRemote(serverId, "docker builder prune --all --force");
} else {
await execAsync(command);
await execAsync("docker builder prune --all --force");
}
};
export const cleanUpSystemPrune = async (serverId?: string) => {
const command = "docker system prune --all --force --volumes";
if (serverId) {
await execAsyncRemote(serverId, command);
await execAsyncRemote(
serverId,
"docker system prune --all --force --volumes",
);
} else {
await execAsync(command);
await execAsync("docker system prune --all --force --volumes");
}
};
@@ -240,7 +238,7 @@ export const startServiceRemote = async (serverId: string, appName: string) => {
export const removeService = async (
appName: string,
serverId?: string | null,
_deleteVolumes = false,
deleteVolumes = false,
) => {
try {
const command = `docker service rm ${appName}`;
@@ -278,15 +276,12 @@ export const prepareEnvironmentVariables = (
return resolvedVars;
};
export const getEnviromentVariablesObject = (
input: string | null,
projectEnv?: string | null,
) => {
const envs = prepareEnvironmentVariables(input, projectEnv);
export const prepareBuildArgs = (input: string | null) => {
const pairs = (input ?? "").split("\n");
const jsonObject: Record<string, string> = {};
for (const pair of envs) {
for (const pair of pairs) {
const [key, value] = pair.split("=");
if (key && value) {
jsonObject[key] = value;

View File

@@ -34,7 +34,7 @@ export async function checkGPUStatus(serverId?: string): Promise<GPUInfo> {
...gpuInfo,
...cudaInfo,
};
} catch (_error) {
} catch (error) {
return {
driverInstalled: false,
driverVersion: undefined,
@@ -315,7 +315,7 @@ const setupLocalServer = async (daemonConfig: any) => {
try {
await execAsync(setupCommands);
} catch (_error) {
} catch (error) {
throw new Error(
"Failed to configure GPU support. Please ensure you have sudo privileges and try again.",
);

View File

@@ -18,7 +18,7 @@ interface Props {
applicationType: string;
errorMessage: string;
buildLink: string;
organizationId: string;
adminId: string;
}
export const sendBuildErrorNotifications = async ({
@@ -27,14 +27,14 @@ export const sendBuildErrorNotifications = async ({
applicationType,
errorMessage,
buildLink,
organizationId,
adminId,
}: Props) => {
const date = new Date();
const unixDate = ~~(Number(date) / 1000);
const notificationList = await db.query.notifications.findMany({
where: and(
eq(notifications.appBuildError, true),
eq(notifications.organizationId, organizationId),
eq(notifications.adminId, adminId),
),
with: {
email: true,

View File

@@ -18,7 +18,7 @@ interface Props {
applicationName: string;
applicationType: string;
buildLink: string;
organizationId: string;
adminId: string;
domains: Domain[];
}
@@ -27,7 +27,7 @@ export const sendBuildSuccessNotifications = async ({
applicationName,
applicationType,
buildLink,
organizationId,
adminId,
domains,
}: Props) => {
const date = new Date();
@@ -35,7 +35,7 @@ export const sendBuildSuccessNotifications = async ({
const notificationList = await db.query.notifications.findMany({
where: and(
eq(notifications.appDeploy, true),
eq(notifications.organizationId, organizationId),
eq(notifications.adminId, adminId),
),
with: {
email: true,

View File

@@ -1,3 +1,4 @@
import { error } from "node:console";
import { db } from "@dokploy/server/db";
import { notifications } from "@dokploy/server/db/schema";
import DatabaseBackupEmail from "@dokploy/server/emails/emails/database-backup";
@@ -18,13 +19,13 @@ export const sendDatabaseBackupNotifications = async ({
databaseType,
type,
errorMessage,
organizationId,
adminId,
}: {
projectName: string;
applicationName: string;
databaseType: "postgres" | "mysql" | "mongodb" | "mariadb";
type: "error" | "success";
organizationId: string;
adminId: string;
errorMessage?: string;
}) => {
const date = new Date();
@@ -32,7 +33,7 @@ export const sendDatabaseBackupNotifications = async ({
const notificationList = await db.query.notifications.findMany({
where: and(
eq(notifications.databaseBackup, true),
eq(notifications.organizationId, organizationId),
eq(notifications.adminId, adminId),
),
with: {
email: true,

View File

@@ -13,7 +13,7 @@ import {
} from "./utils";
export const sendDockerCleanupNotifications = async (
organizationId: string,
adminId: string,
message = "Docker cleanup for dokploy",
) => {
const date = new Date();
@@ -21,7 +21,7 @@ export const sendDockerCleanupNotifications = async (
const notificationList = await db.query.notifications.findMany({
where: and(
eq(notifications.dockerCleanup, true),
eq(notifications.organizationId, organizationId),
eq(notifications.adminId, adminId),
),
with: {
email: true,

View File

@@ -1,155 +0,0 @@
import { and, eq } from "drizzle-orm";
import { db } from "../../db";
import { notifications } from "../../db/schema";
import {
sendDiscordNotification,
sendSlackNotification,
sendTelegramNotification,
} from "./utils";
interface ServerThresholdPayload {
Type: "CPU" | "Memory";
Value: number;
Threshold: number;
Message: string;
Timestamp: string;
Token: string;
ServerName: string;
}
export const sendServerThresholdNotifications = async (
organizationId: string,
payload: ServerThresholdPayload,
) => {
const date = new Date(payload.Timestamp);
const unixDate = ~~(Number(date) / 1000);
const notificationList = await db.query.notifications.findMany({
where: and(
eq(notifications.serverThreshold, true),
eq(notifications.organizationId, organizationId),
),
with: {
email: true,
discord: true,
telegram: true,
slack: true,
},
});
const typeEmoji = payload.Type === "CPU" ? "🔲" : "💾";
const typeColor = 0xff0000; // Rojo para indicar alerta
for (const notification of notificationList) {
const { discord, telegram, slack } = notification;
if (discord) {
const decorate = (decoration: string, text: string) =>
`${discord.decoration ? decoration : ""} ${text}`.trim();
await sendDiscordNotification(discord, {
title: decorate(">", `\`⚠️\` Server ${payload.Type} Alert`),
color: typeColor,
fields: [
{
name: decorate("`🏷️`", "Server Name"),
value: payload.ServerName,
inline: true,
},
{
name: decorate("`📅`", "Date"),
value: `<t:${unixDate}:D>`,
inline: true,
},
{
name: decorate("`⌚`", "Time"),
value: `<t:${unixDate}:t>`,
inline: true,
},
{
name: decorate(typeEmoji, "Type"),
value: payload.Type,
inline: true,
},
{
name: decorate("📊", "Current Value"),
value: `${payload.Value.toFixed(2)}%`,
inline: true,
},
{
name: decorate("⚠️", "Threshold"),
value: `${payload.Threshold.toFixed(2)}%`,
inline: true,
},
{
name: decorate("`📜`", "Message"),
value: `\`\`\`${payload.Message}\`\`\``,
},
],
timestamp: date.toISOString(),
footer: {
text: "Dokploy Server Monitoring Alert",
},
});
}
if (telegram) {
await sendTelegramNotification(
telegram,
`
<b>⚠️ Server ${payload.Type} Alert</b>
<b>Server Name:</b> ${payload.ServerName}
<b>Type:</b> ${payload.Type}
<b>Current Value:</b> ${payload.Value.toFixed(2)}%
<b>Threshold:</b> ${payload.Threshold.toFixed(2)}%
<b>Message:</b> ${payload.Message}
<b>Time:</b> ${date.toLocaleString()}
`,
);
}
if (slack) {
const { channel } = slack;
await sendSlackNotification(slack, {
channel: channel,
attachments: [
{
color: "#FF0000",
pretext: `:warning: *Server ${payload.Type} Alert*`,
fields: [
{
title: "Server Name",
value: payload.ServerName,
short: true,
},
{
title: "Type",
value: payload.Type,
short: true,
},
{
title: "Current Value",
value: `${payload.Value.toFixed(2)}%`,
short: true,
},
{
title: "Threshold",
value: `${payload.Threshold.toFixed(2)}%`,
short: true,
},
{
title: "Message",
value: payload.Message,
},
{
title: "Time",
value: date.toLocaleString(),
short: true,
},
],
},
],
});
}
}
};

View File

@@ -68,7 +68,6 @@ export const sendTelegramNotification = async (
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
chat_id: connection.chatId,
message_thread_id: connection.messageThreadId,
text: messageText,
parse_mode: "HTML",
disable_web_page_preview: true,

View File

@@ -27,7 +27,7 @@ export const execAsyncRemote = async (
throw err;
}
stream
.on("close", (code: number, _signal: string) => {
.on("close", (code: number, signal: string) => {
conn.end();
if (code === 0) {
resolve({ stdout, stderr });

View File

@@ -176,6 +176,7 @@ export const getBitbucketCloneCommand = async (
bitbucketBranch,
bitbucketId,
serverId,
bitbucket,
} = entity;
if (!serverId) {

View File

@@ -69,7 +69,6 @@ export const cloneGitRepository = async (
});
}
const { port } = sanitizeRepoPathSSH(customGitUrl);
await spawnAsync(
"git",
[
@@ -92,7 +91,7 @@ export const cloneGitRepository = async (
env: {
...process.env,
...(customGitSSHKeyId && {
GIT_SSH_COMMAND: `ssh -i ${temporalKeyPath}${port ? ` -p ${port}` : ""} -o UserKnownHostsFile=${knownHostsPath}`,
GIT_SSH_COMMAND: `ssh -i ${temporalKeyPath} -o UserKnownHostsFile=${knownHostsPath}`,
}),
},
},
@@ -169,8 +168,7 @@ export const getCustomGitCloneCommand = async (
);
if (customGitSSHKeyId) {
const sshKey = await findSSHKeyById(customGitSSHKeyId);
const { port } = sanitizeRepoPathSSH(customGitUrl);
const gitSshCommand = `ssh -i /tmp/id_rsa${port ? ` -p ${port}` : ""} -o UserKnownHostsFile=${knownHostsPath}`;
const gitSshCommand = `ssh -i /tmp/id_rsa -o UserKnownHostsFile=${knownHostsPath}`;
command.push(
`
echo "${sshKey.privateKey}" > /tmp/id_rsa
@@ -306,7 +304,6 @@ export const cloneGitRawRepository = async (entity: {
});
}
const { port } = sanitizeRepoPathSSH(customGitUrl);
await spawnAsync(
"git",
[
@@ -320,12 +317,12 @@ export const cloneGitRawRepository = async (entity: {
outputPath,
"--progress",
],
(_data) => {},
(data) => {},
{
env: {
...process.env,
...(customGitSSHKeyId && {
GIT_SSH_COMMAND: `ssh -i ${temporalKeyPath}${port ? ` -p ${port}` : ""} -o UserKnownHostsFile=${knownHostsPath}`,
GIT_SSH_COMMAND: `ssh -i ${temporalKeyPath} -o UserKnownHostsFile=${knownHostsPath}`,
}),
},
},
@@ -384,8 +381,7 @@ export const cloneRawGitRepositoryRemote = async (compose: Compose) => {
command.push(`mkdir -p ${outputPath};`);
if (customGitSSHKeyId) {
const sshKey = await findSSHKeyById(customGitSSHKeyId);
const { port } = sanitizeRepoPathSSH(customGitUrl);
const gitSshCommand = `ssh -i /tmp/id_rsa${port ? ` -p ${port}` : ""} -o UserKnownHostsFile=${knownHostsPath}`;
const gitSshCommand = `ssh -i /tmp/id_rsa -o UserKnownHostsFile=${knownHostsPath}`;
command.push(
`
echo "${sshKey.privateKey}" > /tmp/id_rsa

View File

@@ -162,6 +162,8 @@ export const getGitlabCloneCommand = async (
) => {
const {
appName,
gitlabRepository,
gitlabOwner,
gitlabPathNamespace,
gitlabBranch,
gitlabId,
@@ -266,7 +268,7 @@ export const getGitlabRepositories = async (gitlabId?: string) => {
if (groupName) {
return full_path.toLowerCase().includes(groupName) && kind === "group";
}
return kind === "member";
return kind === "user";
});
const mappedRepositories = filteredRepos.map((repo: any) => {
return {
@@ -326,7 +328,14 @@ export const getGitlabBranches = async (input: {
};
export const cloneRawGitlabRepository = async (entity: Compose) => {
const { appName, gitlabBranch, gitlabId, gitlabPathNamespace } = entity;
const {
appName,
gitlabRepository,
gitlabOwner,
gitlabBranch,
gitlabId,
gitlabPathNamespace,
} = entity;
if (!gitlabId) {
throw new TRPCError({
@@ -433,7 +442,7 @@ export const testGitlabConnection = async (
if (groupName) {
return full_path.toLowerCase().includes(groupName) && kind === "group";
}
return kind === "member";
return kind === "user";
});
return filteredRepos.length;

View File

@@ -67,7 +67,7 @@ export const removeTraefikConfig = async (
if (fs.existsSync(configPath)) {
await fs.promises.unlink(configPath);
}
} catch (_error) {}
} catch (error) {}
};
export const removeTraefikConfigRemote = async (
@@ -78,7 +78,7 @@ export const removeTraefikConfigRemote = async (
const { DYNAMIC_TRAEFIK_PATH } = paths(true);
const configPath = path.join(DYNAMIC_TRAEFIK_PATH, `${appName}.yml`);
await execAsyncRemote(serverId, `rm ${configPath}`);
} catch (_error) {}
} catch (error) {}
};
export const loadOrCreateConfig = (appName: string): FileConfig => {
@@ -110,7 +110,7 @@ export const loadOrCreateConfigRemote = async (
http: { routers: {}, services: {} },
};
return parsedConfig;
} catch (_err) {
} catch (err) {
return fileConfig;
}
};
@@ -132,7 +132,7 @@ export const readRemoteConfig = async (serverId: string, appName: string) => {
const { stdout } = await execAsyncRemote(serverId, `cat ${configPath}`);
if (!stdout) return null;
return stdout;
} catch (_err) {
} catch (err) {
return null;
}
};

Some files were not shown because too many files have changed in this diff Show More