mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-07-07 06:55:23 +02:00
Compare commits
43 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2fe7349889 | ||
|
|
5c1c969873 | ||
|
|
2f6f1b19e7 | ||
|
|
03e04b7bce | ||
|
|
b920e7c0f1 | ||
|
|
15fde820a3 | ||
|
|
64293fce79 | ||
|
|
526f249d0e | ||
|
|
4796b0cf4e | ||
|
|
159a055bc6 | ||
|
|
cfade317f1 | ||
|
|
36ebefff16 | ||
|
|
7cc0603078 | ||
|
|
e0e42ac554 | ||
|
|
e004d8bd52 | ||
|
|
0c0912f606 | ||
|
|
e79f8c4b72 | ||
|
|
26e2a24f63 | ||
|
|
830feabd70 | ||
|
|
122a3d110d | ||
|
|
c05edb313f | ||
|
|
1ec2853862 | ||
|
|
5c2709248c | ||
|
|
bc79074441 | ||
|
|
5ada451916 | ||
|
|
6b0d9240dd | ||
|
|
1e31ebb9c2 | ||
|
|
499022a328 | ||
|
|
2a89be6efc | ||
|
|
412bb9e874 | ||
|
|
6290c217f1 | ||
|
|
4babdd45ea | ||
|
|
24bff96898 | ||
|
|
892f272108 | ||
|
|
fca537ee40 | ||
|
|
ae24aa8be5 | ||
|
|
b74d3995ee | ||
|
|
f7fd77f7e9 | ||
|
|
db8a4e6edf | ||
|
|
fa16cfec2a | ||
|
|
f35d084dd4 | ||
|
|
274daf52c0 | ||
|
|
da52d767eb |
@@ -99,6 +99,10 @@ For detailed documentation, visit [docs.dokploy.com](https://docs.dokploy.com).
|
|||||||
<img src=".github/sponsors/american-cloud.png" alt="AmericanCloud" height="70"/>
|
<img src=".github/sponsors/american-cloud.png" alt="AmericanCloud" height="70"/>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
<a href="https://tolgee.io/?utm_source=github_dokploy&utm_medium=banner&utm_campaign=dokploy" target="_blank" style="display: inline-block; margin-right: 10px;">
|
||||||
|
<img src="https://dokploy.com/tolgee-logo.png" alt="Tolgee" height="80"/>
|
||||||
|
</a>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<!-- Elite Contributors 🥈 -->
|
<!-- Elite Contributors 🥈 -->
|
||||||
|
|
||||||
|
|||||||
@@ -62,6 +62,8 @@ export const ShowDeployments = ({
|
|||||||
|
|
||||||
const { mutateAsync: rollback, isLoading: isRollingBack } =
|
const { mutateAsync: rollback, isLoading: isRollingBack } =
|
||||||
api.rollback.rollback.useMutation();
|
api.rollback.rollback.useMutation();
|
||||||
|
const { mutateAsync: killProcess, isLoading: isKillingProcess } =
|
||||||
|
api.deployment.killProcess.useMutation();
|
||||||
|
|
||||||
const [url, setUrl] = React.useState("");
|
const [url, setUrl] = React.useState("");
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -170,6 +172,32 @@ export const ShowDeployments = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-row items-center gap-2">
|
<div className="flex flex-row items-center gap-2">
|
||||||
|
{deployment.pid && deployment.status === "running" && (
|
||||||
|
<DialogAction
|
||||||
|
title="Kill Process"
|
||||||
|
description="Are you sure you want to kill the process?"
|
||||||
|
type="default"
|
||||||
|
onClick={async () => {
|
||||||
|
await killProcess({
|
||||||
|
deploymentId: deployment.deploymentId,
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
toast.success("Process killed successfully");
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
toast.error("Error killing process");
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
variant="destructive"
|
||||||
|
size="sm"
|
||||||
|
isLoading={isKillingProcess}
|
||||||
|
>
|
||||||
|
Kill Process
|
||||||
|
</Button>
|
||||||
|
</DialogAction>
|
||||||
|
)}
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setActiveLog(deployment);
|
setActiveLog(deployment);
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ export const ShowEnvironment = ({ applicationId }: Props) => {
|
|||||||
currentBuildArgs !== (data?.buildArgs || "");
|
currentBuildArgs !== (data?.buildArgs || "");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (data && !hasChanges) {
|
if (data) {
|
||||||
form.reset({
|
form.reset({
|
||||||
env: data.env || "",
|
env: data.env || "",
|
||||||
buildArgs: data.buildArgs || "",
|
buildArgs: data.buildArgs || "",
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
@@ -79,6 +80,11 @@ export const ShowRollbackSettings = ({ applicationId, children }: Props) => {
|
|||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
Configure how rollbacks work for this application
|
Configure how rollbacks work for this application
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
|
<AlertBlock>
|
||||||
|
Having rollbacks enabled increases storage usage. Be careful with
|
||||||
|
this option. Note that manually cleaning the cache may delete
|
||||||
|
rollback images, making them unavailable for future rollbacks.
|
||||||
|
</AlertBlock>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
|
|||||||
@@ -166,12 +166,16 @@ export const ShowSchedules = ({ id, scheduleType = "application" }: Props) => {
|
|||||||
|
|
||||||
await runManually({
|
await runManually({
|
||||||
scheduleId: schedule.scheduleId,
|
scheduleId: schedule.scheduleId,
|
||||||
}).then(async () => {
|
})
|
||||||
await new Promise((resolve) =>
|
.then(async () => {
|
||||||
setTimeout(resolve, 1500),
|
await new Promise((resolve) =>
|
||||||
);
|
setTimeout(resolve, 1500),
|
||||||
refetchSchedules();
|
);
|
||||||
});
|
refetchSchedules();
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
toast.error("Error running schedule");
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Play className="size-4 transition-colors" />
|
<Play className="size-4 transition-colors" />
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export const ShowNodesModal = ({ serverId }: Props) => {
|
|||||||
Show Swarm Nodes
|
Show Swarm Nodes
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className="sm:max-w-5xl overflow-y-auto max-h-screen ">
|
<DialogContent className="min-w-[70vw] overflow-y-auto max-h-screen">
|
||||||
<div className="grid w-full gap-1">
|
<div className="grid w-full gap-1">
|
||||||
<ShowNodes serverId={serverId} />
|
<ShowNodes serverId={serverId} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ export const ShowNodes = ({ serverId }: Props) => {
|
|||||||
</TableCaption>
|
</TableCaption>
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableHead className="w-[100px]">Hostname</TableHead>
|
<TableHead className="text-left">Hostname</TableHead>
|
||||||
<TableHead className="text-right">Status</TableHead>
|
<TableHead className="text-right">Status</TableHead>
|
||||||
<TableHead className="text-right">Role</TableHead>
|
<TableHead className="text-right">Role</TableHead>
|
||||||
<TableHead className="text-right">Availability</TableHead>
|
<TableHead className="text-right">Availability</TableHead>
|
||||||
@@ -104,7 +104,7 @@ export const ShowNodes = ({ serverId }: Props) => {
|
|||||||
const isManager = node.Spec.Role === "manager";
|
const isManager = node.Spec.Role === "manager";
|
||||||
return (
|
return (
|
||||||
<TableRow key={node.ID}>
|
<TableRow key={node.ID}>
|
||||||
<TableCell className="w-[100px]">
|
<TableCell className="text-left">
|
||||||
{node.Description.Hostname}
|
{node.Description.Hostname}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="text-right">
|
<TableCell className="text-right">
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ export const ShowServers = () => {
|
|||||||
</TableCaption>
|
</TableCaption>
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableHead className="w-[100px]">Name</TableHead>
|
<TableHead className="text-left">Name</TableHead>
|
||||||
{isCloud && (
|
{isCloud && (
|
||||||
<TableHead className="text-center">
|
<TableHead className="text-center">
|
||||||
Status
|
Status
|
||||||
@@ -173,7 +173,7 @@ export const ShowServers = () => {
|
|||||||
const isActive = server.serverStatus === "active";
|
const isActive = server.serverStatus === "active";
|
||||||
return (
|
return (
|
||||||
<TableRow key={server.serverId}>
|
<TableRow key={server.serverId}>
|
||||||
<TableCell className="w-[100px]">
|
<TableCell className="text-left">
|
||||||
{server.name}
|
{server.name}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
{isCloud && (
|
{isCloud && (
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import type * as React from "react";
|
|||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
const badgeVariants = cva(
|
const badgeVariants = cva(
|
||||||
"inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
|
"inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold whitespace-nowrap transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
ALTER TABLE "git_provider" ADD COLUMN "userId" text;--> statement-breakpoint
|
ALTER TABLE "git_provider" ADD COLUMN "userId" text;--> statement-breakpoint
|
||||||
|
|
||||||
-- Update existing git providers to be owned by the organization owner
|
-- Update existing git providers to be owned by the organization owner
|
||||||
-- We need to get the account.user_id for the organization owner
|
-- We can get the owner_id directly from the organization table
|
||||||
UPDATE "git_provider"
|
UPDATE "git_provider"
|
||||||
SET "userId" = (
|
SET "userId" = (
|
||||||
SELECT a.user_id
|
SELECT o."owner_id"
|
||||||
FROM "organization" o
|
FROM "organization" o
|
||||||
JOIN "account" a ON o."owner_id" = a.user_id
|
|
||||||
WHERE o.id = "git_provider"."organizationId"
|
WHERE o.id = "git_provider"."organizationId"
|
||||||
);--> statement-breakpoint
|
);--> statement-breakpoint
|
||||||
|
|
||||||
|
|||||||
1
apps/dokploy/drizzle/0098_conscious_chat.sql
Normal file
1
apps/dokploy/drizzle/0098_conscious_chat.sql
Normal file
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE "deployment" ADD COLUMN "pid" text;
|
||||||
5832
apps/dokploy/drizzle/meta/0098_snapshot.json
Normal file
5832
apps/dokploy/drizzle/meta/0098_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -687,6 +687,13 @@
|
|||||||
"when": 1750567641441,
|
"when": 1750567641441,
|
||||||
"tag": "0097_hard_lizard",
|
"tag": "0097_hard_lizard",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 98,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1751233265357,
|
||||||
|
"tag": "0098_conscious_chat",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -21,6 +21,7 @@ try {
|
|||||||
entryPoints: {
|
entryPoints: {
|
||||||
server: "server/server.ts",
|
server: "server/server.ts",
|
||||||
"reset-password": "reset-password.ts",
|
"reset-password": "reset-password.ts",
|
||||||
|
"reset-2fa": "reset-2fa.ts",
|
||||||
},
|
},
|
||||||
bundle: true,
|
bundle: true,
|
||||||
platform: "node",
|
platform: "node",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "dokploy",
|
"name": "dokploy",
|
||||||
"version": "v0.23.0",
|
"version": "v0.23.6",
|
||||||
"private": true,
|
"private": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@@ -11,6 +11,7 @@
|
|||||||
"build-next": "next build",
|
"build-next": "next build",
|
||||||
"setup": "tsx -r dotenv/config setup.ts && sleep 5 && pnpm run migration:run",
|
"setup": "tsx -r dotenv/config setup.ts && sleep 5 && pnpm run migration:run",
|
||||||
"reset-password": "node -r dotenv/config dist/reset-password.mjs",
|
"reset-password": "node -r dotenv/config dist/reset-password.mjs",
|
||||||
|
"reset-2fa": "node -r dotenv/config dist/reset-2fa.mjs",
|
||||||
"dev": "tsx -r dotenv/config ./server/server.ts --project tsconfig.server.json ",
|
"dev": "tsx -r dotenv/config ./server/server.ts --project tsconfig.server.json ",
|
||||||
"dev-turbopack": "TURBOPACK=1 tsx -r dotenv/config ./server/server.ts --project tsconfig.server.json",
|
"dev-turbopack": "TURBOPACK=1 tsx -r dotenv/config ./server/server.ts --project tsconfig.server.json",
|
||||||
"studio": "drizzle-kit studio --config ./server/db/drizzle.config.ts",
|
"studio": "drizzle-kit studio --config ./server/db/drizzle.config.ts",
|
||||||
|
|||||||
27
apps/dokploy/reset-2fa.ts
Normal file
27
apps/dokploy/reset-2fa.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { findAdmin } from "@dokploy/server";
|
||||||
|
import { db } from "@dokploy/server/db";
|
||||||
|
import { users_temp } from "@dokploy/server/db/schema";
|
||||||
|
import { eq } from "drizzle-orm";
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
const result = await findAdmin();
|
||||||
|
|
||||||
|
const update = await db
|
||||||
|
.update(users_temp)
|
||||||
|
.set({
|
||||||
|
twoFactorEnabled: false,
|
||||||
|
})
|
||||||
|
.where(eq(users_temp.id, result.userId));
|
||||||
|
|
||||||
|
if (update) {
|
||||||
|
console.log("2FA reset successful");
|
||||||
|
} else {
|
||||||
|
console.log("Password reset failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
process.exit(0);
|
||||||
|
} catch (error) {
|
||||||
|
console.log("Error resetting 2FA", error);
|
||||||
|
}
|
||||||
|
})();
|
||||||
@@ -7,16 +7,21 @@ import {
|
|||||||
deployments,
|
deployments,
|
||||||
} from "@/server/db/schema";
|
} from "@/server/db/schema";
|
||||||
import {
|
import {
|
||||||
|
execAsync,
|
||||||
|
execAsyncRemote,
|
||||||
findAllDeploymentsByApplicationId,
|
findAllDeploymentsByApplicationId,
|
||||||
findAllDeploymentsByComposeId,
|
findAllDeploymentsByComposeId,
|
||||||
findAllDeploymentsByServerId,
|
findAllDeploymentsByServerId,
|
||||||
findApplicationById,
|
findApplicationById,
|
||||||
findComposeById,
|
findComposeById,
|
||||||
|
findDeploymentById,
|
||||||
findServerById,
|
findServerById,
|
||||||
|
updateDeploymentStatus,
|
||||||
} from "@dokploy/server";
|
} from "@dokploy/server";
|
||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
import { desc, eq } from "drizzle-orm";
|
import { desc, eq } from "drizzle-orm";
|
||||||
import { createTRPCRouter, protectedProcedure } from "../trpc";
|
import { createTRPCRouter, protectedProcedure } from "../trpc";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
export const deploymentRouter = createTRPCRouter({
|
export const deploymentRouter = createTRPCRouter({
|
||||||
all: protectedProcedure
|
all: protectedProcedure
|
||||||
@@ -72,4 +77,30 @@ export const deploymentRouter = createTRPCRouter({
|
|||||||
|
|
||||||
return deploymentsList;
|
return deploymentsList;
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
killProcess: protectedProcedure
|
||||||
|
.input(
|
||||||
|
z.object({
|
||||||
|
deploymentId: z.string().min(1),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.mutation(async ({ input }) => {
|
||||||
|
const deployment = await findDeploymentById(input.deploymentId);
|
||||||
|
|
||||||
|
if (!deployment.pid) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "BAD_REQUEST",
|
||||||
|
message: "Deployment is not running",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const command = `kill -9 ${deployment.pid}`;
|
||||||
|
if (deployment.schedule?.serverId) {
|
||||||
|
await execAsyncRemote(deployment.schedule.serverId, command);
|
||||||
|
} else {
|
||||||
|
await execAsync(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
await updateDeploymentStatus(deployment.deploymentId, "error");
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -837,6 +837,14 @@ export const settingsRouter = createTRPCRouter({
|
|||||||
getLogCleanupStatus: adminProcedure.query(async () => {
|
getLogCleanupStatus: adminProcedure.query(async () => {
|
||||||
return getLogCleanupStatus();
|
return getLogCleanupStatus();
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
getDokployCloudIps: adminProcedure.query(async () => {
|
||||||
|
if (!IS_CLOUD) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const ips = process.env.DOKPLOY_CLOUD_IPS?.split(",");
|
||||||
|
return ips;
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const getTraefikPorts = async (serverId?: string) => {
|
export const getTraefikPorts = async (serverId?: string) => {
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ export const deployments = pgTable("deployment", {
|
|||||||
description: text("description"),
|
description: text("description"),
|
||||||
status: deploymentStatus("status").default("running"),
|
status: deploymentStatus("status").default("running"),
|
||||||
logPath: text("logPath").notNull(),
|
logPath: text("logPath").notNull(),
|
||||||
|
pid: text("pid"),
|
||||||
applicationId: text("applicationId").references(
|
applicationId: text("applicationId").references(
|
||||||
() => applications.applicationId,
|
() => applications.applicationId,
|
||||||
{ onDelete: "cascade" },
|
{ onDelete: "cascade" },
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import { createInsertSchema } from "drizzle-zod";
|
|||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { deployments } from "./deployment";
|
import { deployments } from "./deployment";
|
||||||
|
import type { Application } from "@dokploy/server/services/application";
|
||||||
|
import type { Project } from "@dokploy/server/services/project";
|
||||||
|
|
||||||
export const rollbacks = pgTable("rollback", {
|
export const rollbacks = pgTable("rollback", {
|
||||||
rollbackId: text("rollbackId")
|
rollbackId: text("rollbackId")
|
||||||
@@ -20,7 +22,7 @@ export const rollbacks = pgTable("rollback", {
|
|||||||
createdAt: text("createdAt")
|
createdAt: text("createdAt")
|
||||||
.notNull()
|
.notNull()
|
||||||
.$defaultFn(() => new Date().toISOString()),
|
.$defaultFn(() => new Date().toISOString()),
|
||||||
fullContext: jsonb("fullContext"),
|
fullContext: jsonb("fullContext").$type<Application & { project: Project }>(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export type Rollback = typeof rollbacks.$inferSelect;
|
export type Rollback = typeof rollbacks.$inferSelect;
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ export const findDeploymentById = async (deploymentId: string) => {
|
|||||||
where: eq(deployments.deploymentId, deploymentId),
|
where: eq(deployments.deploymentId, deploymentId),
|
||||||
with: {
|
with: {
|
||||||
application: true,
|
application: true,
|
||||||
|
schedule: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (!deployment) {
|
if (!deployment) {
|
||||||
@@ -537,9 +538,11 @@ const removeLastTenDeployments = async (
|
|||||||
await removeRollbackById(oldDeployment.rollbackId);
|
await removeRollbackById(oldDeployment.rollbackId);
|
||||||
}
|
}
|
||||||
|
|
||||||
command += `
|
if (logPath !== ".") {
|
||||||
rm -rf ${logPath};
|
command += `
|
||||||
`;
|
rm -rf ${logPath};
|
||||||
|
`;
|
||||||
|
}
|
||||||
await removeDeployment(oldDeployment.deploymentId);
|
await removeDeployment(oldDeployment.deploymentId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -550,7 +553,11 @@ const removeLastTenDeployments = async (
|
|||||||
await removeRollbackById(oldDeployment.rollbackId);
|
await removeRollbackById(oldDeployment.rollbackId);
|
||||||
}
|
}
|
||||||
const logPath = path.join(oldDeployment.logPath);
|
const logPath = path.join(oldDeployment.logPath);
|
||||||
if (existsSync(logPath) && !oldDeployment.errorMessage) {
|
if (
|
||||||
|
existsSync(logPath) &&
|
||||||
|
!oldDeployment.errorMessage &&
|
||||||
|
logPath !== "."
|
||||||
|
) {
|
||||||
await fsPromises.unlink(logPath);
|
await fsPromises.unlink(logPath);
|
||||||
}
|
}
|
||||||
await removeDeployment(oldDeployment.deploymentId);
|
await removeDeployment(oldDeployment.deploymentId);
|
||||||
|
|||||||
@@ -171,7 +171,7 @@ export const createPreviewDeployment = async (
|
|||||||
const runningComment = getIssueComment(
|
const runningComment = getIssueComment(
|
||||||
application.name,
|
application.name,
|
||||||
"initializing",
|
"initializing",
|
||||||
generateDomain,
|
`${application.previewHttps ? "https" : "http"}://${generateDomain}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
const issue = await octokit.rest.issues.createComment({
|
const issue = await octokit.rest.issues.createComment({
|
||||||
|
|||||||
@@ -12,14 +12,16 @@ import type { ApplicationNested } from "../utils/builders";
|
|||||||
import { execAsync, execAsyncRemote } from "../utils/process/execAsync";
|
import { execAsync, execAsyncRemote } from "../utils/process/execAsync";
|
||||||
import type { CreateServiceOptions } from "dockerode";
|
import type { CreateServiceOptions } from "dockerode";
|
||||||
import { findDeploymentById } from "./deployment";
|
import { findDeploymentById } from "./deployment";
|
||||||
|
import { prepareEnvironmentVariables } from "../utils/docker/utils";
|
||||||
|
|
||||||
export const createRollback = async (
|
export const createRollback = async (
|
||||||
input: z.infer<typeof createRollbackSchema>,
|
input: z.infer<typeof createRollbackSchema>,
|
||||||
) => {
|
) => {
|
||||||
await db.transaction(async (tx) => {
|
await db.transaction(async (tx) => {
|
||||||
|
const { fullContext, ...other } = input;
|
||||||
const rollback = await tx
|
const rollback = await tx
|
||||||
.insert(rollbacks)
|
.insert(rollbacks)
|
||||||
.values(input)
|
.values(other)
|
||||||
.returning()
|
.returning()
|
||||||
.then((res) => res[0]);
|
.then((res) => res[0]);
|
||||||
|
|
||||||
@@ -47,7 +49,7 @@ export const createRollback = async (
|
|||||||
.update(rollbacks)
|
.update(rollbacks)
|
||||||
.set({
|
.set({
|
||||||
image: tagImage,
|
image: tagImage,
|
||||||
fullContext: JSON.stringify(rest),
|
fullContext: rest,
|
||||||
})
|
})
|
||||||
.where(eq(rollbacks.rollbackId, rollback.rollbackId));
|
.where(eq(rollbacks.rollbackId, rollback.rollbackId));
|
||||||
|
|
||||||
@@ -150,10 +152,16 @@ export const rollback = async (rollbackId: string) => {
|
|||||||
|
|
||||||
const application = await findApplicationById(deployment.applicationId);
|
const application = await findApplicationById(deployment.applicationId);
|
||||||
|
|
||||||
|
const envVariables = prepareEnvironmentVariables(
|
||||||
|
result?.fullContext?.env || "",
|
||||||
|
result.fullContext?.project?.env || "",
|
||||||
|
);
|
||||||
|
|
||||||
await rollbackApplication(
|
await rollbackApplication(
|
||||||
application.appName,
|
application.appName,
|
||||||
result.image || "",
|
result.image || "",
|
||||||
application.serverId,
|
application.serverId,
|
||||||
|
envVariables,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -161,6 +169,7 @@ const rollbackApplication = async (
|
|||||||
appName: string,
|
appName: string,
|
||||||
image: string,
|
image: string,
|
||||||
serverId?: string | null,
|
serverId?: string | null,
|
||||||
|
env: string[] = [],
|
||||||
) => {
|
) => {
|
||||||
const docker = await getRemoteDocker(serverId);
|
const docker = await getRemoteDocker(serverId);
|
||||||
|
|
||||||
@@ -169,6 +178,7 @@ const rollbackApplication = async (
|
|||||||
TaskTemplate: {
|
TaskTemplate: {
|
||||||
ContainerSpec: {
|
ContainerSpec: {
|
||||||
Image: image,
|
Image: image,
|
||||||
|
Env: env,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -109,7 +109,12 @@ export const updateSchedule = async (
|
|||||||
const handleScript = async (schedule: Schedule) => {
|
const handleScript = async (schedule: Schedule) => {
|
||||||
const { SCHEDULES_PATH } = paths(!!schedule?.serverId);
|
const { SCHEDULES_PATH } = paths(!!schedule?.serverId);
|
||||||
const fullPath = path.join(SCHEDULES_PATH, schedule?.appName || "");
|
const fullPath = path.join(SCHEDULES_PATH, schedule?.appName || "");
|
||||||
const encodedContent = encodeBase64(schedule?.script || "");
|
|
||||||
|
// Add PID and Schedule ID echo by default to all scripts
|
||||||
|
const scriptWithPid = `echo "PID: $$ | Schedule ID: ${schedule.scheduleId}"
|
||||||
|
${schedule?.script || ""}`;
|
||||||
|
|
||||||
|
const encodedContent = encodeBase64(scriptWithPid);
|
||||||
const script = `
|
const script = `
|
||||||
mkdir -p ${fullPath}
|
mkdir -p ${fullPath}
|
||||||
rm -f ${fullPath}/script.sh
|
rm -f ${fullPath}/script.sh
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export const runComposeBackup = async (
|
|||||||
) => {
|
) => {
|
||||||
const { projectId, name } = compose;
|
const { projectId, name } = compose;
|
||||||
const project = await findProjectById(projectId);
|
const project = await findProjectById(projectId);
|
||||||
const { prefix } = backup;
|
const { prefix, databaseType } = backup;
|
||||||
const destination = backup.destination;
|
const destination = backup.destination;
|
||||||
const backupFileName = `${new Date().toISOString()}.dump.gz`;
|
const backupFileName = `${new Date().toISOString()}.dump.gz`;
|
||||||
const bucketDestination = `${normalizeS3Path(prefix)}${backupFileName}`;
|
const bucketDestination = `${normalizeS3Path(prefix)}${backupFileName}`;
|
||||||
@@ -46,9 +46,10 @@ export const runComposeBackup = async (
|
|||||||
await sendDatabaseBackupNotifications({
|
await sendDatabaseBackupNotifications({
|
||||||
applicationName: name,
|
applicationName: name,
|
||||||
projectName: project.name,
|
projectName: project.name,
|
||||||
databaseType: "mongodb",
|
databaseType: getDatabaseType(databaseType),
|
||||||
type: "success",
|
type: "success",
|
||||||
organizationId: project.organizationId,
|
organizationId: project.organizationId,
|
||||||
|
databaseName: backup.database,
|
||||||
});
|
});
|
||||||
|
|
||||||
await updateDeploymentStatus(deployment.deploymentId, "done");
|
await updateDeploymentStatus(deployment.deploymentId, "done");
|
||||||
@@ -57,14 +58,31 @@ export const runComposeBackup = async (
|
|||||||
await sendDatabaseBackupNotifications({
|
await sendDatabaseBackupNotifications({
|
||||||
applicationName: name,
|
applicationName: name,
|
||||||
projectName: project.name,
|
projectName: project.name,
|
||||||
databaseType: "mongodb",
|
databaseType: getDatabaseType(databaseType),
|
||||||
type: "error",
|
type: "error",
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
errorMessage: error?.message || "Error message not provided",
|
errorMessage: error?.message || "Error message not provided",
|
||||||
organizationId: project.organizationId,
|
organizationId: project.organizationId,
|
||||||
|
databaseName: backup.database,
|
||||||
});
|
});
|
||||||
|
|
||||||
await updateDeploymentStatus(deployment.deploymentId, "error");
|
await updateDeploymentStatus(deployment.deploymentId, "error");
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getDatabaseType = (databaseType: BackupSchedule["databaseType"]) => {
|
||||||
|
if (databaseType === "mongo") {
|
||||||
|
return "mongodb";
|
||||||
|
}
|
||||||
|
if (databaseType === "postgres") {
|
||||||
|
return "postgres";
|
||||||
|
}
|
||||||
|
if (databaseType === "mariadb") {
|
||||||
|
return "mariadb";
|
||||||
|
}
|
||||||
|
if (databaseType === "mysql") {
|
||||||
|
return "mysql";
|
||||||
|
}
|
||||||
|
return "mongodb";
|
||||||
|
};
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ export const runMariadbBackup = async (
|
|||||||
databaseType: "mariadb",
|
databaseType: "mariadb",
|
||||||
type: "success",
|
type: "success",
|
||||||
organizationId: project.organizationId,
|
organizationId: project.organizationId,
|
||||||
|
databaseName: backup.database,
|
||||||
});
|
});
|
||||||
await updateDeploymentStatus(deployment.deploymentId, "done");
|
await updateDeploymentStatus(deployment.deploymentId, "done");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -60,6 +61,7 @@ export const runMariadbBackup = async (
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
errorMessage: error?.message || "Error message not provided",
|
errorMessage: error?.message || "Error message not provided",
|
||||||
organizationId: project.organizationId,
|
organizationId: project.organizationId,
|
||||||
|
databaseName: backup.database,
|
||||||
});
|
});
|
||||||
await updateDeploymentStatus(deployment.deploymentId, "error");
|
await updateDeploymentStatus(deployment.deploymentId, "error");
|
||||||
throw error;
|
throw error;
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ export const runMongoBackup = async (mongo: Mongo, backup: BackupSchedule) => {
|
|||||||
databaseType: "mongodb",
|
databaseType: "mongodb",
|
||||||
type: "success",
|
type: "success",
|
||||||
organizationId: project.organizationId,
|
organizationId: project.organizationId,
|
||||||
|
databaseName: backup.database,
|
||||||
});
|
});
|
||||||
await updateDeploymentStatus(deployment.deploymentId, "done");
|
await updateDeploymentStatus(deployment.deploymentId, "done");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -58,6 +59,7 @@ export const runMongoBackup = async (mongo: Mongo, backup: BackupSchedule) => {
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
errorMessage: error?.message || "Error message not provided",
|
errorMessage: error?.message || "Error message not provided",
|
||||||
organizationId: project.organizationId,
|
organizationId: project.organizationId,
|
||||||
|
databaseName: backup.database,
|
||||||
});
|
});
|
||||||
await updateDeploymentStatus(deployment.deploymentId, "error");
|
await updateDeploymentStatus(deployment.deploymentId, "error");
|
||||||
throw error;
|
throw error;
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ export const runMySqlBackup = async (mysql: MySql, backup: BackupSchedule) => {
|
|||||||
databaseType: "mysql",
|
databaseType: "mysql",
|
||||||
type: "success",
|
type: "success",
|
||||||
organizationId: project.organizationId,
|
organizationId: project.organizationId,
|
||||||
|
databaseName: backup.database,
|
||||||
});
|
});
|
||||||
await updateDeploymentStatus(deployment.deploymentId, "done");
|
await updateDeploymentStatus(deployment.deploymentId, "done");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -59,6 +60,7 @@ export const runMySqlBackup = async (mysql: MySql, backup: BackupSchedule) => {
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
errorMessage: error?.message || "Error message not provided",
|
errorMessage: error?.message || "Error message not provided",
|
||||||
organizationId: project.organizationId,
|
organizationId: project.organizationId,
|
||||||
|
databaseName: backup.database,
|
||||||
});
|
});
|
||||||
await updateDeploymentStatus(deployment.deploymentId, "error");
|
await updateDeploymentStatus(deployment.deploymentId, "error");
|
||||||
throw error;
|
throw error;
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ export const runPostgresBackup = async (
|
|||||||
databaseType: "postgres",
|
databaseType: "postgres",
|
||||||
type: "success",
|
type: "success",
|
||||||
organizationId: project.organizationId,
|
organizationId: project.organizationId,
|
||||||
|
databaseName: backup.database,
|
||||||
});
|
});
|
||||||
|
|
||||||
await updateDeploymentStatus(deployment.deploymentId, "done");
|
await updateDeploymentStatus(deployment.deploymentId, "done");
|
||||||
@@ -62,6 +63,7 @@ export const runPostgresBackup = async (
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
errorMessage: error?.message || "Error message not provided",
|
errorMessage: error?.message || "Error message not provided",
|
||||||
organizationId: project.organizationId,
|
organizationId: project.organizationId,
|
||||||
|
databaseName: backup.database,
|
||||||
});
|
});
|
||||||
|
|
||||||
await updateDeploymentStatus(deployment.deploymentId, "error");
|
await updateDeploymentStatus(deployment.deploymentId, "error");
|
||||||
|
|||||||
@@ -193,7 +193,7 @@ const createEnvFile = (compose: ComposeNested) => {
|
|||||||
let envContent = `APP_NAME=${appName}\n`;
|
let envContent = `APP_NAME=${appName}\n`;
|
||||||
envContent += env || "";
|
envContent += env || "";
|
||||||
if (!envContent.includes("DOCKER_CONFIG")) {
|
if (!envContent.includes("DOCKER_CONFIG")) {
|
||||||
envContent += "\nDOCKER_CONFIG=/root/.docker/config.json";
|
envContent += "\nDOCKER_CONFIG=/root/.docker";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (compose.randomize) {
|
if (compose.randomize) {
|
||||||
@@ -223,7 +223,7 @@ export const getCreateEnvFileCommand = (compose: ComposeNested) => {
|
|||||||
let envContent = `APP_NAME=${appName}\n`;
|
let envContent = `APP_NAME=${appName}\n`;
|
||||||
envContent += env || "";
|
envContent += env || "";
|
||||||
if (!envContent.includes("DOCKER_CONFIG")) {
|
if (!envContent.includes("DOCKER_CONFIG")) {
|
||||||
envContent += "\nDOCKER_CONFIG=/root/.docker/config.json";
|
envContent += "\nDOCKER_CONFIG=/root/.docker";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (compose.randomize) {
|
if (compose.randomize) {
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { createWriteStream } from "node:fs";
|
import { createWriteStream } from "node:fs";
|
||||||
import { join } from "node:path";
|
|
||||||
import type { InferResultType } from "@dokploy/server/types/with";
|
import type { InferResultType } from "@dokploy/server/types/with";
|
||||||
import type { CreateServiceOptions } from "dockerode";
|
import type { CreateServiceOptions } from "dockerode";
|
||||||
import { uploadImage, uploadImageRemoteCommand } from "../cluster/upload";
|
import { uploadImage, uploadImageRemoteCommand } from "../cluster/upload";
|
||||||
@@ -211,16 +210,20 @@ export const mechanizeDockerContainer = async (
|
|||||||
|
|
||||||
const getImageName = (application: ApplicationNested) => {
|
const getImageName = (application: ApplicationNested) => {
|
||||||
const { appName, sourceType, dockerImage, registry } = application;
|
const { appName, sourceType, dockerImage, registry } = application;
|
||||||
|
const imageName = `${appName}:latest`;
|
||||||
if (sourceType === "docker") {
|
if (sourceType === "docker") {
|
||||||
return dockerImage || "ERROR-NO-IMAGE-PROVIDED";
|
return dockerImage || "ERROR-NO-IMAGE-PROVIDED";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (registry) {
|
if (registry) {
|
||||||
return join(registry.registryUrl, registry.imagePrefix || "", appName);
|
const { registryUrl, imagePrefix, username } = registry;
|
||||||
|
const registryTag = imagePrefix
|
||||||
|
? `${registryUrl}/${imagePrefix}/${imageName}`
|
||||||
|
: `${registryUrl}/${username}/${imageName}`;
|
||||||
|
return registryTag;
|
||||||
}
|
}
|
||||||
|
|
||||||
return `${appName}:latest`;
|
return imageName;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getAuthConfig = (application: ApplicationNested) => {
|
const getAuthConfig = (application: ApplicationNested) => {
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import type { WriteStream } from "node:fs";
|
import type { WriteStream } from "node:fs";
|
||||||
import path, { join } from "node:path";
|
|
||||||
import type { ApplicationNested } from "../builders";
|
import type { ApplicationNested } from "../builders";
|
||||||
import { spawnAsync } from "../process/spawnAsync";
|
import { spawnAsync } from "../process/spawnAsync";
|
||||||
|
|
||||||
@@ -13,19 +12,22 @@ export const uploadImage = async (
|
|||||||
throw new Error("Registry not found");
|
throw new Error("Registry not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
const { registryUrl, imagePrefix } = registry;
|
const { registryUrl, imagePrefix, username } = registry;
|
||||||
const { appName } = application;
|
const { appName } = application;
|
||||||
const imageName = `${appName}:latest`;
|
const imageName = `${appName}:latest`;
|
||||||
|
|
||||||
const finalURL = registryUrl;
|
const finalURL = registryUrl;
|
||||||
|
|
||||||
const registryTag = path
|
// Build registry tag in correct format: registry.com/owner/image:tag
|
||||||
.join(registryUrl, join(imagePrefix || "", imageName))
|
// For ghcr.io: ghcr.io/username/image:tag
|
||||||
.replace(/\/+/g, "/");
|
// For docker.io: docker.io/username/image:tag
|
||||||
|
const registryTag = imagePrefix
|
||||||
|
? `${registryUrl}/${imagePrefix}/${imageName}`
|
||||||
|
: `${registryUrl}/${username}/${imageName}`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
writeStream.write(
|
writeStream.write(
|
||||||
`📦 [Enabled Registry] Uploading image to ${registry.registryType} | ${imageName} | ${finalURL}\n`,
|
`📦 [Enabled Registry] Uploading image to ${registry.registryType} | ${imageName} | ${finalURL} | ${registryTag}\n`,
|
||||||
);
|
);
|
||||||
const loginCommand = spawnAsync(
|
const loginCommand = spawnAsync(
|
||||||
"docker",
|
"docker",
|
||||||
@@ -67,15 +69,16 @@ export const uploadImageRemoteCommand = (
|
|||||||
throw new Error("Registry not found");
|
throw new Error("Registry not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
const { registryUrl, imagePrefix } = registry;
|
const { registryUrl, imagePrefix, username } = registry;
|
||||||
const { appName } = application;
|
const { appName } = application;
|
||||||
const imageName = `${appName}:latest`;
|
const imageName = `${appName}:latest`;
|
||||||
|
|
||||||
const finalURL = registryUrl;
|
const finalURL = registryUrl;
|
||||||
|
|
||||||
const registryTag = path
|
// Build registry tag in correct format: registry.com/owner/image:tag
|
||||||
.join(registryUrl, join(imagePrefix || "", imageName))
|
const registryTag = imagePrefix
|
||||||
.replace(/\/+/g, "/");
|
? `${registryUrl}/${imagePrefix}/${imageName}`
|
||||||
|
: `${registryUrl}/${username}/${imageName}`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const command = `
|
const command = `
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ export const sendDatabaseBackupNotifications = async ({
|
|||||||
type,
|
type,
|
||||||
errorMessage,
|
errorMessage,
|
||||||
organizationId,
|
organizationId,
|
||||||
|
databaseName,
|
||||||
}: {
|
}: {
|
||||||
projectName: string;
|
projectName: string;
|
||||||
applicationName: string;
|
applicationName: string;
|
||||||
@@ -26,6 +27,7 @@ export const sendDatabaseBackupNotifications = async ({
|
|||||||
type: "error" | "success";
|
type: "error" | "success";
|
||||||
organizationId: string;
|
organizationId: string;
|
||||||
errorMessage?: string;
|
errorMessage?: string;
|
||||||
|
databaseName: string;
|
||||||
}) => {
|
}) => {
|
||||||
const date = new Date();
|
const date = new Date();
|
||||||
const unixDate = ~~(Number(date) / 1000);
|
const unixDate = ~~(Number(date) / 1000);
|
||||||
@@ -90,6 +92,11 @@ export const sendDatabaseBackupNotifications = async ({
|
|||||||
value: databaseType,
|
value: databaseType,
|
||||||
inline: true,
|
inline: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: decorate("`📂`", "Database Name"),
|
||||||
|
value: databaseName,
|
||||||
|
inline: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: decorate("`📅`", "Date"),
|
name: decorate("`📅`", "Date"),
|
||||||
value: `<t:${unixDate}:D>`,
|
value: `<t:${unixDate}:D>`,
|
||||||
@@ -136,6 +143,7 @@ export const sendDatabaseBackupNotifications = async ({
|
|||||||
`${decorate("🛠️", `Project: ${projectName}`)}` +
|
`${decorate("🛠️", `Project: ${projectName}`)}` +
|
||||||
`${decorate("⚙️", `Application: ${applicationName}`)}` +
|
`${decorate("⚙️", `Application: ${applicationName}`)}` +
|
||||||
`${decorate("❔", `Type: ${databaseType}`)}` +
|
`${decorate("❔", `Type: ${databaseType}`)}` +
|
||||||
|
`${decorate("📂", `Database Name: ${databaseName}`)}` +
|
||||||
`${decorate("🕒", `Date: ${date.toLocaleString()}`)}` +
|
`${decorate("🕒", `Date: ${date.toLocaleString()}`)}` +
|
||||||
`${type === "error" && errorMessage ? decorate("❌", `Error:\n${errorMessage}`) : ""}`,
|
`${type === "error" && errorMessage ? decorate("❌", `Error:\n${errorMessage}`) : ""}`,
|
||||||
);
|
);
|
||||||
@@ -150,7 +158,7 @@ export const sendDatabaseBackupNotifications = async ({
|
|||||||
? `\n\n<b>Error:</b>\n<pre>${errorMessage}</pre>`
|
? `\n\n<b>Error:</b>\n<pre>${errorMessage}</pre>`
|
||||||
: "";
|
: "";
|
||||||
|
|
||||||
const messageText = `<b>${statusEmoji} Database Backup ${typeStatus}</b>\n\n<b>Project:</b> ${projectName}\n<b>Application:</b> ${applicationName}\n<b>Type:</b> ${databaseType}\n<b>Date:</b> ${format(date, "PP")}\n<b>Time:</b> ${format(date, "pp")}${isError ? errorMsg : ""}`;
|
const messageText = `<b>${statusEmoji} Database Backup ${typeStatus}</b>\n\n<b>Project:</b> ${projectName}\n<b>Application:</b> ${applicationName}\n<b>Type:</b> ${databaseType}\n<b>Database Name:</b> ${databaseName}\n<b>Date:</b> ${format(date, "PP")}\n<b>Time:</b> ${format(date, "pp")}${isError ? errorMsg : ""}`;
|
||||||
|
|
||||||
await sendTelegramNotification(telegram, messageText);
|
await sendTelegramNotification(telegram, messageText);
|
||||||
}
|
}
|
||||||
@@ -191,6 +199,10 @@ export const sendDatabaseBackupNotifications = async ({
|
|||||||
value: databaseType,
|
value: databaseType,
|
||||||
short: true,
|
short: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: "Database Name",
|
||||||
|
value: databaseName,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: "Time",
|
title: "Time",
|
||||||
value: date.toLocaleString(),
|
value: date.toLocaleString(),
|
||||||
|
|||||||
@@ -2,7 +2,10 @@ import { createWriteStream } from "node:fs";
|
|||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { paths } from "@dokploy/server/constants";
|
import { paths } from "@dokploy/server/constants";
|
||||||
import type { Schedule } from "@dokploy/server/db/schema/schedule";
|
import type { Schedule } from "@dokploy/server/db/schema/schedule";
|
||||||
import { createDeploymentSchedule } from "@dokploy/server/services/deployment";
|
import {
|
||||||
|
createDeploymentSchedule,
|
||||||
|
updateDeployment,
|
||||||
|
} from "@dokploy/server/services/deployment";
|
||||||
import { updateDeploymentStatus } from "@dokploy/server/services/deployment";
|
import { updateDeploymentStatus } from "@dokploy/server/services/deployment";
|
||||||
import { findScheduleById } from "@dokploy/server/services/schedule";
|
import { findScheduleById } from "@dokploy/server/services/schedule";
|
||||||
import { scheduleJob as scheduleJobNode, scheduledJobs } from "node-schedule";
|
import { scheduleJob as scheduleJobNode, scheduledJobs } from "node-schedule";
|
||||||
@@ -113,8 +116,16 @@ export const runCommand = async (scheduleId: string) => {
|
|||||||
await spawnAsync(
|
await spawnAsync(
|
||||||
"bash",
|
"bash",
|
||||||
["-c", "./script.sh"],
|
["-c", "./script.sh"],
|
||||||
(data) => {
|
async (data) => {
|
||||||
if (writeStream.writable) {
|
if (writeStream.writable) {
|
||||||
|
// we need to extract the PID and Schedule ID from the data
|
||||||
|
const pid = data?.match(/PID: (\d+)/)?.[1];
|
||||||
|
|
||||||
|
if (pid) {
|
||||||
|
await updateDeployment(deployment.deploymentId, {
|
||||||
|
pid,
|
||||||
|
});
|
||||||
|
}
|
||||||
writeStream.write(data);
|
writeStream.write(data);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -133,13 +144,21 @@ export const runCommand = async (scheduleId: string) => {
|
|||||||
const command = `
|
const command = `
|
||||||
set -e
|
set -e
|
||||||
echo "Running script" >> ${deployment.logPath};
|
echo "Running script" >> ${deployment.logPath};
|
||||||
bash -c ${fullPath}/script.sh >> ${deployment.logPath} 2>> ${deployment.logPath} || {
|
bash -c ${fullPath}/script.sh 2>&1 | tee -a ${deployment.logPath} || {
|
||||||
echo "❌ Command failed" >> ${deployment.logPath};
|
echo "❌ Command failed" >> ${deployment.logPath};
|
||||||
exit 1;
|
exit 1;
|
||||||
}
|
}
|
||||||
echo "✅ Command executed successfully" >> ${deployment.logPath};
|
echo "✅ Command executed successfully" >> ${deployment.logPath};
|
||||||
`;
|
`;
|
||||||
await execAsyncRemote(serverId, command);
|
await execAsyncRemote(serverId, command, async (data) => {
|
||||||
|
// we need to extract the PID and Schedule ID from the data
|
||||||
|
const pid = data?.match(/PID: (\d+)/)?.[1];
|
||||||
|
if (pid) {
|
||||||
|
await updateDeployment(deployment.deploymentId, {
|
||||||
|
pid,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await updateDeploymentStatus(deployment.deploymentId, "error");
|
await updateDeploymentStatus(deployment.deploymentId, "error");
|
||||||
throw error;
|
throw error;
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
packages:
|
packages:
|
||||||
- "apps/dokploy"
|
|
||||||
- "apps/api"
|
- "apps/api"
|
||||||
|
- "apps/dokploy"
|
||||||
- "apps/monitoring"
|
- "apps/monitoring"
|
||||||
- "apps/schedules"
|
- "apps/schedules"
|
||||||
- "apps/models"
|
|
||||||
- "packages/server"
|
- "packages/server"
|
||||||
|
|||||||
Reference in New Issue
Block a user