mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-06-16 12:45:21 +02:00
Compare commits
2 Commits
v0.24.4
...
1365-creat
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
80d5313dd8 | ||
|
|
da0e726326 |
3
.github/workflows/deploy.yml
vendored
3
.github/workflows/deploy.yml
vendored
@@ -2,8 +2,7 @@ name: Build Docker images
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, canary]
|
||||
workflow_dispatch:
|
||||
branches: ["canary", "main", "feat/monitoring"]
|
||||
|
||||
jobs:
|
||||
build-and-push-cloud-image:
|
||||
|
||||
3
.github/workflows/dokploy.yml
vendored
3
.github/workflows/dokploy.yml
vendored
@@ -2,8 +2,7 @@ name: Dokploy Docker Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, canary]
|
||||
workflow_dispatch:
|
||||
branches: [main, canary, "1061-custom-docker-service-hostname"]
|
||||
|
||||
env:
|
||||
IMAGE_NAME: dokploy/dokploy
|
||||
|
||||
6
.github/workflows/format.yml
vendored
6
.github/workflows/format.yml
vendored
@@ -11,12 +11,12 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup biomeJs
|
||||
uses: biomejs/setup-biome@v2
|
||||
|
||||
- name: Run Biome formatter
|
||||
run: biome format --write
|
||||
run: biome format . --write
|
||||
|
||||
- uses: autofix-ci/action@635ffb0c9798bd160680f18fd73371e355b85f27 # v1.3.2
|
||||
- uses: autofix-ci/action@551dded8c6cc8a1054039c8bc0b8b48c51dfc6ef
|
||||
|
||||
@@ -29,9 +29,5 @@
|
||||
"tsx": "^4.16.2",
|
||||
"typescript": "^5.8.3"
|
||||
},
|
||||
"packageManager": "pnpm@9.12.0",
|
||||
"engines": {
|
||||
"node": "^20.16.0",
|
||||
"pnpm": ">=9.12.0"
|
||||
}
|
||||
"packageManager": "pnpm@9.5.0"
|
||||
}
|
||||
|
||||
@@ -29,7 +29,6 @@ const baseApp: ApplicationNested = {
|
||||
herokuVersion: "",
|
||||
giteaBranch: "",
|
||||
giteaBuildPath: "",
|
||||
previewRequireCollaboratorPermissions: false,
|
||||
giteaId: "",
|
||||
giteaOwner: "",
|
||||
giteaRepository: "",
|
||||
|
||||
@@ -18,7 +18,6 @@ const baseApp: ApplicationNested = {
|
||||
appName: "",
|
||||
autoDeploy: true,
|
||||
enableSubmodules: false,
|
||||
previewRequireCollaboratorPermissions: false,
|
||||
serverId: "",
|
||||
branch: null,
|
||||
dockerBuildStage: "",
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { DateTooltip } from "@/components/shared/date-tooltip";
|
||||
import { DialogAction } from "@/components/shared/dialog-action";
|
||||
import { StatusTooltip } from "@/components/shared/status-tooltip";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
@@ -11,13 +10,14 @@ import {
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { type RouterOutputs, api } from "@/utils/api";
|
||||
import { Clock, Loader2, RefreshCcw, RocketIcon, Settings } from "lucide-react";
|
||||
import { Clock, Loader2, RocketIcon, Settings, RefreshCcw } from "lucide-react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { ShowRollbackSettings } from "../rollbacks/show-rollback-settings";
|
||||
import { CancelQueues } from "./cancel-queues";
|
||||
import { RefreshToken } from "./refresh-token";
|
||||
import { ShowDeployment } from "./show-deployment";
|
||||
import { ShowRollbackSettings } from "../rollbacks/show-rollback-settings";
|
||||
import { DialogAction } from "@/components/shared/dialog-action";
|
||||
import { toast } from "sonner";
|
||||
|
||||
interface Props {
|
||||
id: string;
|
||||
|
||||
@@ -46,7 +46,6 @@ const schema = z
|
||||
previewPath: z.string(),
|
||||
previewCertificateType: z.enum(["letsencrypt", "none", "custom"]),
|
||||
previewCustomCertResolver: z.string().optional(),
|
||||
previewRequireCollaboratorPermissions: z.boolean(),
|
||||
})
|
||||
.superRefine((input, ctx) => {
|
||||
if (
|
||||
@@ -84,7 +83,6 @@ export const ShowPreviewSettings = ({ applicationId }: Props) => {
|
||||
previewHttps: false,
|
||||
previewPath: "/",
|
||||
previewCertificateType: "none",
|
||||
previewRequireCollaboratorPermissions: true,
|
||||
},
|
||||
resolver: zodResolver(schema),
|
||||
});
|
||||
@@ -107,8 +105,6 @@ export const ShowPreviewSettings = ({ applicationId }: Props) => {
|
||||
previewPath: data.previewPath || "/",
|
||||
previewCertificateType: data.previewCertificateType || "none",
|
||||
previewCustomCertResolver: data.previewCustomCertResolver || "",
|
||||
previewRequireCollaboratorPermissions:
|
||||
data.previewRequireCollaboratorPermissions || true,
|
||||
});
|
||||
}
|
||||
}, [data]);
|
||||
@@ -125,8 +121,6 @@ export const ShowPreviewSettings = ({ applicationId }: Props) => {
|
||||
previewPath: formData.previewPath,
|
||||
previewCertificateType: formData.previewCertificateType,
|
||||
previewCustomCertResolver: formData.previewCustomCertResolver,
|
||||
previewRequireCollaboratorPermissions:
|
||||
formData.previewRequireCollaboratorPermissions,
|
||||
})
|
||||
.then(() => {
|
||||
toast.success("Preview Deployments settings updated");
|
||||
@@ -318,37 +312,6 @@ export const ShowPreviewSettings = ({ applicationId }: Props) => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-4 lg:grid-cols-2">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="previewRequireCollaboratorPermissions"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-row items-center justify-between p-3 mt-4 border rounded-lg shadow-sm col-span-2">
|
||||
<div className="space-y-0.5">
|
||||
<FormLabel>
|
||||
Require Collaborator Permissions
|
||||
</FormLabel>
|
||||
<FormDescription>
|
||||
Require collaborator permissions to preview
|
||||
deployments, valid roles are:
|
||||
<ul>
|
||||
<li>Admin</li>
|
||||
<li>Maintain</li>
|
||||
<li>Write</li>
|
||||
</ul>
|
||||
</FormDescription>
|
||||
</div>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="env"
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { DrawerLogs } from "@/components/shared/drawer-logs";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
@@ -43,8 +42,9 @@ import { useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
import { formatBytes } from "../../database/backups/restore-backup";
|
||||
import { type LogLine, parseLogs } from "../../docker/logs/utils";
|
||||
import { formatBytes } from "../../database/backups/restore-backup";
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
|
||||
interface Props {
|
||||
id: string;
|
||||
|
||||
@@ -23,8 +23,8 @@ import {
|
||||
Trash2,
|
||||
} from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
import { ShowDeploymentsModal } from "../deployments/show-deployments-modal";
|
||||
import { HandleVolumeBackups } from "./handle-volume-backups";
|
||||
import { ShowDeploymentsModal } from "../deployments/show-deployments-modal";
|
||||
import { RestoreVolumeBackups } from "./restore-volume-backups";
|
||||
|
||||
interface Props {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { UnauthorizedGitProvider } from "@/components/dashboard/application/general/generic/unauthorized-git-provider";
|
||||
import {
|
||||
BitbucketIcon,
|
||||
GitIcon,
|
||||
@@ -12,7 +11,6 @@ import { api } from "@/utils/api";
|
||||
import { CodeIcon, GitBranch, Loader2 } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { ComposeFileEditor } from "../compose-file-editor";
|
||||
import { ShowConvertedCompose } from "../show-converted-compose";
|
||||
import { SaveBitbucketProviderCompose } from "./save-bitbucket-provider-compose";
|
||||
@@ -20,6 +18,8 @@ import { SaveGitProviderCompose } from "./save-git-provider-compose";
|
||||
import { SaveGiteaProviderCompose } from "./save-gitea-provider-compose";
|
||||
import { SaveGithubProviderCompose } from "./save-github-provider-compose";
|
||||
import { SaveGitlabProviderCompose } from "./save-gitlab-provider-compose";
|
||||
import { UnauthorizedGitProvider } from "@/components/dashboard/application/general/generic/unauthorized-git-provider";
|
||||
import { toast } from "sonner";
|
||||
|
||||
type TabState = "github" | "git" | "raw" | "gitlab" | "bitbucket" | "gitea";
|
||||
interface Props {
|
||||
|
||||
@@ -70,7 +70,6 @@ export const HandleDestinations = ({ destinationId }: Props) => {
|
||||
},
|
||||
{
|
||||
enabled: !!destinationId,
|
||||
refetchOnWindowFocus: false,
|
||||
},
|
||||
);
|
||||
const {
|
||||
|
||||
@@ -20,9 +20,7 @@ export const ToggleDockerCleanup = ({ serverId }: Props) => {
|
||||
},
|
||||
);
|
||||
|
||||
const enabled = serverId
|
||||
? server?.enableDockerCleanup
|
||||
: data?.user.enableDockerCleanup;
|
||||
const enabled = data?.user.enableDockerCleanup || server?.enableDockerCleanup;
|
||||
|
||||
const { mutateAsync } = api.settings.updateDockerCleanup.useMutation();
|
||||
|
||||
|
||||
@@ -126,7 +126,7 @@ export const UpdateServer = ({
|
||||
</TooltipProvider>
|
||||
)}
|
||||
</DialogTrigger>
|
||||
<DialogContent className="max-w-lg">
|
||||
<DialogContent className="max-w-lg p-6">
|
||||
<div className="flex items-center justify-between mb-8">
|
||||
<DialogTitle className="text-2xl font-semibold">
|
||||
Web Server Update
|
||||
@@ -253,7 +253,7 @@ export const UpdateServer = ({
|
||||
<ToggleAutoCheckUpdates disabled={isLoading} />
|
||||
</div>
|
||||
|
||||
<div className="space-y-4 flex items-center justify-end mt-4 ">
|
||||
<div className="space-y-4 flex items-center justify-end">
|
||||
<div className="flex items-center gap-2">
|
||||
<Button variant="outline" onClick={() => onOpenChange?.(false)}>
|
||||
Cancel
|
||||
|
||||
@@ -96,7 +96,10 @@ type SingleNavItem = {
|
||||
title: string;
|
||||
url: string;
|
||||
icon?: LucideIcon;
|
||||
isEnabled?: (opts: { auth?: AuthQueryOutput; isCloud: boolean }) => boolean;
|
||||
isEnabled?: (opts: {
|
||||
auth?: AuthQueryOutput;
|
||||
isCloud: boolean;
|
||||
}) => boolean;
|
||||
};
|
||||
|
||||
// NavItem type
|
||||
@@ -122,7 +125,10 @@ type ExternalLink = {
|
||||
name: string;
|
||||
url: string;
|
||||
icon: React.ComponentType<{ className?: string }>;
|
||||
isEnabled?: (opts: { auth?: AuthQueryOutput; isCloud: boolean }) => boolean;
|
||||
isEnabled?: (opts: {
|
||||
auth?: AuthQueryOutput;
|
||||
isCloud: boolean;
|
||||
}) => boolean;
|
||||
};
|
||||
|
||||
// Menu type
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
ALTER TABLE "application" ADD COLUMN "previewRequireCollaboratorPermissions" boolean DEFAULT true;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -722,13 +722,6 @@
|
||||
"when": 1751848685503,
|
||||
"tag": "0102_opposite_grandmaster",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 103,
|
||||
"version": "7",
|
||||
"when": 1752465764072,
|
||||
"tag": "0103_cultured_pestilence",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "dokploy",
|
||||
"version": "v0.24.4",
|
||||
"version": "v0.24.2",
|
||||
"private": true,
|
||||
"license": "Apache-2.0",
|
||||
"type": "module",
|
||||
@@ -187,10 +187,10 @@
|
||||
"ct3aMetadata": {
|
||||
"initVersion": "7.25.2"
|
||||
},
|
||||
"packageManager": "pnpm@9.12.0",
|
||||
"packageManager": "pnpm@9.5.0",
|
||||
"engines": {
|
||||
"node": "^20.16.0",
|
||||
"pnpm": ">=9.12.0"
|
||||
"pnpm": ">=9.5.0"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*": [
|
||||
@@ -198,8 +198,6 @@
|
||||
]
|
||||
},
|
||||
"commitlint": {
|
||||
"extends": [
|
||||
"@commitlint/config-conventional"
|
||||
]
|
||||
"extends": ["@commitlint/config-conventional"]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,10 +5,7 @@ import { myQueue } from "@/server/queues/queueSetup";
|
||||
import { deploy } from "@/server/utils/deploy";
|
||||
import {
|
||||
IS_CLOUD,
|
||||
checkUserRepositoryPermissions,
|
||||
createPreviewDeployment,
|
||||
createSecurityBlockedComment,
|
||||
findGithubById,
|
||||
findPreviewDeploymentByApplicationId,
|
||||
findPreviewDeploymentsByPullRequestId,
|
||||
removePreviewDeployment,
|
||||
@@ -349,18 +346,6 @@ export default async function handler(
|
||||
const deploymentHash = githubBody?.pull_request?.head?.sha;
|
||||
const branch = githubBody?.pull_request?.base?.ref;
|
||||
const owner = githubBody?.repository?.owner?.login;
|
||||
const prAuthor = githubBody?.pull_request?.user?.login;
|
||||
|
||||
// Validate PR author information is present
|
||||
if (!prAuthor) {
|
||||
console.warn(
|
||||
"⚠️ SECURITY: PR author information missing in webhook payload",
|
||||
);
|
||||
res.status(400).json({
|
||||
message: "PR author information missing",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const apps = await db.query.applications.findMany({
|
||||
where: and(
|
||||
@@ -376,72 +361,13 @@ export default async function handler(
|
||||
},
|
||||
});
|
||||
|
||||
// SECURITY: Check collaborator permissions per application setting
|
||||
const secureApps: typeof apps = [];
|
||||
const blockedApps: string[] = [];
|
||||
let userPermission: string | null = null;
|
||||
|
||||
for (const app of apps) {
|
||||
// If the app requires collaborator permissions, verify them
|
||||
if (app.previewRequireCollaboratorPermissions !== false) {
|
||||
try {
|
||||
const githubProvider = await findGithubById(githubResult.githubId);
|
||||
const { hasWriteAccess, permission } =
|
||||
await checkUserRepositoryPermissions(
|
||||
githubProvider,
|
||||
owner,
|
||||
repository,
|
||||
prAuthor,
|
||||
);
|
||||
|
||||
userPermission = permission; // Store permission for comment
|
||||
|
||||
if (!hasWriteAccess) {
|
||||
console.warn(
|
||||
`🚨 SECURITY: Blocked preview deployment for ${app.name} from unauthorized user ${prAuthor} on ${owner}/${repository}. Permission: ${permission || "none"}`,
|
||||
);
|
||||
blockedApps.push(app.name);
|
||||
continue;
|
||||
}
|
||||
|
||||
console.log(
|
||||
`✅ SECURITY: Preview deployment authorized for ${app.name} from user ${prAuthor} on ${owner}/${repository}. Permission: ${permission}`,
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`Error validating PR author permissions for ${app.name}:`,
|
||||
error,
|
||||
);
|
||||
blockedApps.push(app.name);
|
||||
continue; // Skip this app on error
|
||||
}
|
||||
} else {
|
||||
console.warn(
|
||||
`⚠️ SECURITY: Preview deployment for ${app.name} allows deployment from any PR author (security check disabled)`,
|
||||
);
|
||||
}
|
||||
secureApps.push(app);
|
||||
}
|
||||
|
||||
const prBranch = githubBody?.pull_request?.head?.ref;
|
||||
|
||||
const prNumber = githubBody?.pull_request?.number;
|
||||
const prTitle = githubBody?.pull_request?.title;
|
||||
const prURL = githubBody?.pull_request?.html_url;
|
||||
|
||||
// Create security notification comment if any apps were blocked
|
||||
if (blockedApps.length > 0) {
|
||||
await createSecurityBlockedComment({
|
||||
owner,
|
||||
repository,
|
||||
prNumber: Number.parseInt(prNumber),
|
||||
prAuthor,
|
||||
permission: userPermission,
|
||||
githubId: githubResult.githubId,
|
||||
});
|
||||
}
|
||||
|
||||
for (const app of secureApps) {
|
||||
for (const app of apps) {
|
||||
const previewLimit = app?.previewLimit || 0;
|
||||
if (app?.previewDeployments?.length > previewLimit) {
|
||||
continue;
|
||||
|
||||
@@ -37,6 +37,7 @@ import {
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { appRouter } from "@/server/api/root";
|
||||
import { api } from "@/utils/api";
|
||||
import { validateRequest } from "@dokploy/server/lib/auth";
|
||||
|
||||
@@ -33,6 +33,7 @@ import {
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { appRouter } from "@/server/api/root";
|
||||
import { api } from "@/utils/api";
|
||||
import { validateRequest } from "@dokploy/server/lib/auth";
|
||||
|
||||
@@ -28,8 +28,6 @@ import { projectRouter } from "./routers/project";
|
||||
import { redirectsRouter } from "./routers/redirects";
|
||||
import { redisRouter } from "./routers/redis";
|
||||
import { registryRouter } from "./routers/registry";
|
||||
import { rollbackRouter } from "./routers/rollbacks";
|
||||
import { scheduleRouter } from "./routers/schedule";
|
||||
import { securityRouter } from "./routers/security";
|
||||
import { serverRouter } from "./routers/server";
|
||||
import { settingsRouter } from "./routers/settings";
|
||||
@@ -37,6 +35,8 @@ import { sshRouter } from "./routers/ssh-key";
|
||||
import { stripeRouter } from "./routers/stripe";
|
||||
import { swarmRouter } from "./routers/swarm";
|
||||
import { userRouter } from "./routers/user";
|
||||
import { scheduleRouter } from "./routers/schedule";
|
||||
import { rollbackRouter } from "./routers/rollbacks";
|
||||
import { volumeBackupsRouter } from "./routers/volume-backups";
|
||||
/**
|
||||
* This is the primary router for your server.
|
||||
|
||||
@@ -20,8 +20,8 @@ import {
|
||||
} from "@dokploy/server";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { desc, eq } from "drizzle-orm";
|
||||
import { z } from "zod";
|
||||
import { createTRPCRouter, protectedProcedure } from "../trpc";
|
||||
import { z } from "zod";
|
||||
|
||||
export const deploymentRouter = createTRPCRouter({
|
||||
all: protectedProcedure
|
||||
|
||||
@@ -8,9 +8,9 @@ import {
|
||||
getServiceContainersByAppName,
|
||||
getStackContainersByAppName,
|
||||
} from "@dokploy/server";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { z } from "zod";
|
||||
import { createTRPCRouter, protectedProcedure } from "../trpc";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
|
||||
export const containerIdRegex = /^[a-zA-Z0-9.\-_]+$/;
|
||||
|
||||
|
||||
@@ -12,8 +12,8 @@ import {
|
||||
getServiceContainer,
|
||||
updateMount,
|
||||
} from "@dokploy/server";
|
||||
import { z } from "zod";
|
||||
import { createTRPCRouter, protectedProcedure } from "../trpc";
|
||||
import { z } from "zod";
|
||||
|
||||
export const mountRouter = createTRPCRouter({
|
||||
create: protectedProcedure
|
||||
|
||||
@@ -1,13 +1,23 @@
|
||||
import { apiFindAllByApplication } from "@/server/db/schema";
|
||||
import { db } from "@/server/db";
|
||||
import { apiFindAllByApplication, applications } from "@/server/db/schema";
|
||||
import {
|
||||
createPreviewDeployment,
|
||||
findApplicationById,
|
||||
findPreviewDeploymentByApplicationId,
|
||||
findPreviewDeploymentById,
|
||||
findPreviewDeploymentsByApplicationId,
|
||||
findPreviewDeploymentsByPullRequestId,
|
||||
IS_CLOUD,
|
||||
removePreviewDeployment,
|
||||
} from "@dokploy/server";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { z } from "zod";
|
||||
import { createTRPCRouter, protectedProcedure } from "../trpc";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { and } from "drizzle-orm";
|
||||
import { myQueue } from "@/server/queues/queueSetup";
|
||||
import { deploy } from "@/server/utils/deploy";
|
||||
import type { DeploymentJob } from "@/server/queues/queue-types";
|
||||
|
||||
export const previewDeploymentRouter = createTRPCRouter({
|
||||
all: protectedProcedure
|
||||
@@ -59,4 +69,142 @@ export const previewDeploymentRouter = createTRPCRouter({
|
||||
}
|
||||
return previewDeployment;
|
||||
}),
|
||||
create: protectedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
action: z.enum(["opened", "synchronize", "reopened", "closed"]),
|
||||
pullRequestId: z.string(),
|
||||
repository: z.string(),
|
||||
owner: z.string(),
|
||||
branch: z.string(),
|
||||
deploymentHash: z.string(),
|
||||
prBranch: z.string(),
|
||||
prNumber: z.any(),
|
||||
prTitle: z.string(),
|
||||
prURL: z.string(),
|
||||
}),
|
||||
)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const organizationId = ctx.session.activeOrganizationId;
|
||||
const action = input.action;
|
||||
const prId = input.pullRequestId;
|
||||
|
||||
if (action === "closed") {
|
||||
const previewDeploymentResult =
|
||||
await findPreviewDeploymentsByPullRequestId(prId);
|
||||
|
||||
const filteredPreviewDeploymentResult = previewDeploymentResult.filter(
|
||||
(previewDeployment) =>
|
||||
previewDeployment.application.project.organizationId ===
|
||||
organizationId,
|
||||
);
|
||||
|
||||
if (filteredPreviewDeploymentResult.length > 0) {
|
||||
for (const previewDeployment of filteredPreviewDeploymentResult) {
|
||||
try {
|
||||
await removePreviewDeployment(
|
||||
previewDeployment.previewDeploymentId,
|
||||
);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
message: "Preview Deployments Closed",
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
action === "opened" ||
|
||||
action === "synchronize" ||
|
||||
action === "reopened"
|
||||
) {
|
||||
const deploymentHash = input.deploymentHash;
|
||||
|
||||
const prBranch = input.prBranch;
|
||||
const prNumber = input.prNumber;
|
||||
const prTitle = input.prTitle;
|
||||
const prURL = input.prURL;
|
||||
const apps = await db.query.applications.findMany({
|
||||
where: and(
|
||||
eq(applications.sourceType, "github"),
|
||||
eq(applications.repository, input.repository),
|
||||
eq(applications.branch, input.branch),
|
||||
eq(applications.isPreviewDeploymentsActive, true),
|
||||
eq(applications.owner, input.owner),
|
||||
),
|
||||
with: {
|
||||
previewDeployments: true,
|
||||
project: true,
|
||||
},
|
||||
});
|
||||
|
||||
const filteredApps = apps.filter(
|
||||
(app) => app.project.organizationId === organizationId,
|
||||
);
|
||||
|
||||
console.log(filteredApps);
|
||||
|
||||
for (const app of filteredApps) {
|
||||
const previewLimit = app?.previewLimit || 0;
|
||||
if (app?.previewDeployments?.length > previewLimit) {
|
||||
continue;
|
||||
}
|
||||
const previewDeploymentResult =
|
||||
await findPreviewDeploymentByApplicationId(app.applicationId, prId);
|
||||
|
||||
let previewDeploymentId =
|
||||
previewDeploymentResult?.previewDeploymentId || "";
|
||||
|
||||
if (!previewDeploymentResult) {
|
||||
try {
|
||||
const previewDeployment = await createPreviewDeployment({
|
||||
applicationId: app.applicationId as string,
|
||||
branch: prBranch,
|
||||
pullRequestId: prId,
|
||||
pullRequestNumber: prNumber,
|
||||
pullRequestTitle: prTitle,
|
||||
pullRequestURL: prURL,
|
||||
});
|
||||
|
||||
console.log(previewDeployment);
|
||||
previewDeploymentId = previewDeployment.previewDeploymentId;
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
const jobData: DeploymentJob = {
|
||||
applicationId: app.applicationId as string,
|
||||
titleLog: "Preview Deployment",
|
||||
descriptionLog: `Hash: ${deploymentHash}`,
|
||||
type: "deploy",
|
||||
applicationType: "application-preview",
|
||||
server: !!app.serverId,
|
||||
previewDeploymentId,
|
||||
isExternal: true,
|
||||
};
|
||||
|
||||
if (IS_CLOUD && app.serverId) {
|
||||
jobData.serverId = app.serverId;
|
||||
await deploy(jobData);
|
||||
continue;
|
||||
}
|
||||
await myQueue.add(
|
||||
"deployments",
|
||||
{ ...jobData },
|
||||
{
|
||||
removeOnComplete: true,
|
||||
removeOnFail: true,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
message: "Preview Deployments Created",
|
||||
};
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -361,7 +361,6 @@ export const projectRouter = createTRPCRouter({
|
||||
previewDeployments,
|
||||
mounts,
|
||||
appName,
|
||||
refreshToken,
|
||||
...application
|
||||
} = await findApplicationById(id);
|
||||
const newAppName = appName.substring(
|
||||
@@ -604,14 +603,8 @@ export const projectRouter = createTRPCRouter({
|
||||
break;
|
||||
}
|
||||
case "compose": {
|
||||
const {
|
||||
composeId,
|
||||
mounts,
|
||||
domains,
|
||||
appName,
|
||||
refreshToken,
|
||||
...compose
|
||||
} = await findComposeById(id);
|
||||
const { composeId, mounts, domains, appName, ...compose } =
|
||||
await findComposeById(id);
|
||||
|
||||
const newAppName = appName.substring(
|
||||
0,
|
||||
|
||||
@@ -641,16 +641,10 @@ export const settingsRouter = createTRPCRouter({
|
||||
},
|
||||
})
|
||||
.input(
|
||||
z
|
||||
.object({
|
||||
dateRange: z
|
||||
.object({
|
||||
start: z.string().optional(),
|
||||
end: z.string().optional(),
|
||||
})
|
||||
.optional(),
|
||||
})
|
||||
.optional(),
|
||||
z.object({
|
||||
start: z.string().optional(),
|
||||
end: z.string().optional(),
|
||||
}),
|
||||
)
|
||||
.query(async ({ input }) => {
|
||||
if (IS_CLOUD) {
|
||||
|
||||
@@ -4,10 +4,10 @@ import {
|
||||
getNodeInfo,
|
||||
getSwarmNodes,
|
||||
} from "@dokploy/server";
|
||||
import { findServerById } from "@dokploy/server";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { z } from "zod";
|
||||
import { createTRPCRouter, protectedProcedure } from "../trpc";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { findServerById } from "@dokploy/server";
|
||||
import { containerIdRegex } from "./docker";
|
||||
|
||||
export const swarmRouter = createTRPCRouter({
|
||||
|
||||
@@ -1,30 +1,30 @@
|
||||
import { removeJob, schedule, updateJob } from "@/server/utils/backup";
|
||||
import {
|
||||
IS_CLOUD,
|
||||
createVolumeBackup,
|
||||
findVolumeBackupById,
|
||||
removeVolumeBackup,
|
||||
removeVolumeBackupJob,
|
||||
restoreVolume,
|
||||
runVolumeBackup,
|
||||
scheduleVolumeBackup,
|
||||
updateVolumeBackup,
|
||||
removeVolumeBackup,
|
||||
createVolumeBackup,
|
||||
runVolumeBackup,
|
||||
findVolumeBackupById,
|
||||
restoreVolume,
|
||||
scheduleVolumeBackup,
|
||||
removeVolumeBackupJob,
|
||||
} from "@dokploy/server";
|
||||
import { db } from "@dokploy/server/db";
|
||||
import {
|
||||
createVolumeBackupSchema,
|
||||
updateVolumeBackupSchema,
|
||||
volumeBackups,
|
||||
} from "@dokploy/server/db/schema";
|
||||
import { z } from "zod";
|
||||
import { createTRPCRouter, protectedProcedure } from "../trpc";
|
||||
import { db } from "@dokploy/server/db";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { observable } from "@trpc/server/observable";
|
||||
import {
|
||||
execAsyncRemote,
|
||||
execAsyncStream,
|
||||
} from "@dokploy/server/utils/process/execAsync";
|
||||
import { removeJob, schedule, updateJob } from "@/server/utils/backup";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { observable } from "@trpc/server/observable";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { z } from "zod";
|
||||
import { createTRPCRouter, protectedProcedure } from "../trpc";
|
||||
|
||||
export const volumeBackupsRouter = createTRPCRouter({
|
||||
list: protectedProcedure
|
||||
|
||||
@@ -98,6 +98,7 @@ export const deploymentWorker = new Worker(
|
||||
titleLog: job.data.titleLog,
|
||||
descriptionLog: job.data.descriptionLog,
|
||||
previewDeploymentId: job.data.previewDeploymentId,
|
||||
isExternal: job.data.isExternal,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
@@ -107,6 +108,7 @@ export const deploymentWorker = new Worker(
|
||||
titleLog: job.data.titleLog,
|
||||
descriptionLog: job.data.descriptionLog,
|
||||
previewDeploymentId: job.data.previewDeploymentId,
|
||||
isExternal: job.data.isExternal,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ type DeployJob =
|
||||
applicationType: "application-preview";
|
||||
previewDeploymentId: string;
|
||||
serverId?: string;
|
||||
isExternal?: boolean;
|
||||
};
|
||||
|
||||
export type DeploymentJob = DeployJob;
|
||||
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
initializeTraefik,
|
||||
} from "@dokploy/server/setup/traefik-setup";
|
||||
|
||||
import { execAsync } from "@dokploy/server";
|
||||
import { setupDirectories } from "@dokploy/server/setup/config-paths";
|
||||
import { initializePostgres } from "@dokploy/server/setup/postgres-setup";
|
||||
import { initializeRedis } from "@dokploy/server/setup/redis-setup";
|
||||
@@ -13,6 +12,7 @@ import {
|
||||
initializeNetwork,
|
||||
initializeSwarm,
|
||||
} from "@dokploy/server/setup/setup";
|
||||
import { execAsync } from "@dokploy/server";
|
||||
(async () => {
|
||||
try {
|
||||
setupDirectories();
|
||||
|
||||
5
apps/dokploy/types/chatwoot.d.ts
vendored
5
apps/dokploy/types/chatwoot.d.ts
vendored
@@ -13,7 +13,10 @@ declare global {
|
||||
baseDomain?: string;
|
||||
};
|
||||
chatwootSDK?: {
|
||||
run: (config: { websiteToken: string; baseUrl: string }) => void;
|
||||
run: (config: {
|
||||
websiteToken: string;
|
||||
baseUrl: string;
|
||||
}) => void;
|
||||
};
|
||||
$chatwoot?: {
|
||||
setUser: (
|
||||
|
||||
@@ -29,9 +29,5 @@
|
||||
"tsx": "^4.16.2",
|
||||
"typescript": "^5.8.3"
|
||||
},
|
||||
"packageManager": "pnpm@9.12.0",
|
||||
"engines": {
|
||||
"node": "^20.16.0",
|
||||
"pnpm": ">=9.12.0"
|
||||
}
|
||||
"packageManager": "pnpm@9.5.0"
|
||||
}
|
||||
|
||||
31
biome.json
31
biome.json
@@ -1,17 +1,18 @@
|
||||
{
|
||||
"$schema": "./node_modules/@biomejs/biome/configuration_schema.json",
|
||||
"files": {
|
||||
"includes": [
|
||||
"**",
|
||||
"!**/.docker",
|
||||
"!**/.next/**",
|
||||
"!**/dist",
|
||||
"!**/drizzle/**",
|
||||
"!node_modules/**",
|
||||
"!packages/server/package.json"
|
||||
"ignore": [
|
||||
"node_modules/**",
|
||||
".next/**",
|
||||
"drizzle/**",
|
||||
".docker",
|
||||
"dist",
|
||||
"packages/server/package.json"
|
||||
]
|
||||
},
|
||||
"assist": { "actions": { "source": { "organizeImports": "on" } } },
|
||||
"organizeImports": {
|
||||
"enabled": true
|
||||
},
|
||||
"linter": {
|
||||
"rules": {
|
||||
"security": {
|
||||
@@ -29,17 +30,7 @@
|
||||
"noUnusedVariables": "error"
|
||||
},
|
||||
"style": {
|
||||
"noNonNullAssertion": "off",
|
||||
"noParameterAssign": "error",
|
||||
"useAsConstAssertion": "error",
|
||||
"useDefaultParameterLast": "error",
|
||||
"useEnumInitializers": "error",
|
||||
"useSelfClosingElements": "error",
|
||||
"useSingleVarDeclarator": "error",
|
||||
"noUnusedTemplateLiteral": "error",
|
||||
"useNumberNamespace": "error",
|
||||
"noInferrableTypes": "error",
|
||||
"noUselessElse": "error"
|
||||
"noNonNullAssertion": "off"
|
||||
},
|
||||
"suspicious": {
|
||||
"noArrayIndexKey": "off",
|
||||
|
||||
15
package.json
15
package.json
@@ -1,10 +1,7 @@
|
||||
{
|
||||
"name": "dokploy",
|
||||
"private": true,
|
||||
"workspaces": [
|
||||
"apps/*",
|
||||
"packages/*"
|
||||
],
|
||||
"workspaces": ["apps/*", "packages/*"],
|
||||
"scripts": {
|
||||
"dokploy:setup": "pnpm --filter=dokploy run setup",
|
||||
"dokploy:dev": "pnpm --filter=dokploy run dev",
|
||||
@@ -23,7 +20,7 @@
|
||||
"format-and-lint:fix": "biome check . --write"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "2.1.1",
|
||||
"@biomejs/biome": "1.9.4",
|
||||
"@commitlint/cli": "^19.8.1",
|
||||
"@commitlint/config-conventional": "^19.8.1",
|
||||
"@types/node": "^18.19.104",
|
||||
@@ -33,10 +30,10 @@
|
||||
"lint-staged": "^15.5.2",
|
||||
"tsx": "4.16.2"
|
||||
},
|
||||
"packageManager": "pnpm@9.12.0",
|
||||
"packageManager": "pnpm@9.5.0",
|
||||
"engines": {
|
||||
"node": "^20.16.0",
|
||||
"pnpm": ">=9.12.0"
|
||||
"pnpm": ">=9.5.0"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*": [
|
||||
@@ -44,9 +41,7 @@
|
||||
]
|
||||
},
|
||||
"commitlint": {
|
||||
"extends": [
|
||||
"@commitlint/config-conventional"
|
||||
]
|
||||
"extends": ["@commitlint/config-conventional"]
|
||||
},
|
||||
"resolutions": {
|
||||
"@types/react": "18.3.5",
|
||||
|
||||
@@ -105,10 +105,5 @@
|
||||
"tsc-alias": "1.8.10",
|
||||
"tsx": "^4.16.2",
|
||||
"typescript": "^5.8.3"
|
||||
},
|
||||
"packageManager": "pnpm@9.12.0",
|
||||
"engines": {
|
||||
"node": "^20.16.0",
|
||||
"pnpm": ">=9.12.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -131,10 +131,6 @@ export const applications = pgTable("application", {
|
||||
isPreviewDeploymentsActive: boolean("isPreviewDeploymentsActive").default(
|
||||
false,
|
||||
),
|
||||
// Security: Require collaborator permissions for preview deployments
|
||||
previewRequireCollaboratorPermissions: boolean(
|
||||
"previewRequireCollaboratorPermissions",
|
||||
).default(true),
|
||||
rollbackActive: boolean("rollbackActive").default(false),
|
||||
buildArgs: text("buildArgs"),
|
||||
memoryReservation: text("memoryReservation"),
|
||||
@@ -432,7 +428,6 @@ const createSchema = createInsertSchema(applications, {
|
||||
previewHttps: z.boolean().optional(),
|
||||
previewPath: z.string().optional(),
|
||||
previewCertificateType: z.enum(["letsencrypt", "none", "custom"]).optional(),
|
||||
previewRequireCollaboratorPermissions: z.boolean().optional(),
|
||||
watchPaths: z.array(z.string()).optional(),
|
||||
cleanCache: z.boolean().optional(),
|
||||
});
|
||||
|
||||
@@ -13,9 +13,9 @@ import { applications } from "./application";
|
||||
import { backups } from "./backups";
|
||||
import { compose } from "./compose";
|
||||
import { previewDeployments } from "./preview-deployments";
|
||||
import { rollbacks } from "./rollbacks";
|
||||
import { schedules } from "./schedule";
|
||||
import { server } from "./server";
|
||||
import { rollbacks } from "./rollbacks";
|
||||
import { volumeBackups } from "./volume-backups";
|
||||
export const deploymentStatus = pgEnum("deploymentStatus", [
|
||||
"running",
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import type { Application } from "@dokploy/server/services/application";
|
||||
import type { Mount } from "@dokploy/server/services/mount";
|
||||
import type { Port } from "@dokploy/server/services/port";
|
||||
import type { Project } from "@dokploy/server/services/project";
|
||||
import type { Registry } from "@dokploy/server/services/registry";
|
||||
import { relations } from "drizzle-orm";
|
||||
import { jsonb, pgTable, serial, text } from "drizzle-orm/pg-core";
|
||||
import { createInsertSchema } from "drizzle-zod";
|
||||
import { nanoid } from "nanoid";
|
||||
import { z } from "zod";
|
||||
import { deployments } from "./deployment";
|
||||
import type { Application } from "@dokploy/server/services/application";
|
||||
import type { Project } from "@dokploy/server/services/project";
|
||||
import type { Mount } from "@dokploy/server/services/mount";
|
||||
import type { Port } from "@dokploy/server/services/port";
|
||||
import type { Registry } from "@dokploy/server/services/registry";
|
||||
|
||||
export const rollbacks = pgTable("rollback", {
|
||||
rollbackId: text("rollbackId")
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { paths } from "@dokploy/server/constants";
|
||||
import { relations } from "drizzle-orm";
|
||||
import {
|
||||
boolean,
|
||||
@@ -16,6 +15,7 @@ import { backups } from "./backups";
|
||||
import { projects } from "./project";
|
||||
import { schedules } from "./schedule";
|
||||
import { certificateType } from "./shared";
|
||||
import { paths } from "@dokploy/server/constants";
|
||||
/**
|
||||
* This is an example of how to use the multi-project schema feature of Drizzle ORM. Use the same
|
||||
* database instance for multiple projects.
|
||||
|
||||
@@ -3,16 +3,16 @@ import { boolean, integer, pgTable, text } from "drizzle-orm/pg-core";
|
||||
import { createInsertSchema } from "drizzle-zod";
|
||||
import { nanoid } from "nanoid";
|
||||
import { z } from "zod";
|
||||
import { applications } from "./application";
|
||||
import { compose } from "./compose";
|
||||
import { deployments } from "./deployment";
|
||||
import { destinations } from "./destination";
|
||||
import { mariadb } from "./mariadb";
|
||||
import { mongo } from "./mongo";
|
||||
import { serviceType } from "./mount";
|
||||
import { applications } from "./application";
|
||||
import { mongo } from "./mongo";
|
||||
import { mysql } from "./mysql";
|
||||
import { postgres } from "./postgres";
|
||||
import { redis } from "./redis";
|
||||
import { compose } from "./compose";
|
||||
import { postgres } from "./postgres";
|
||||
import { mariadb } from "./mariadb";
|
||||
import { destinations } from "./destination";
|
||||
import { deployments } from "./deployment";
|
||||
import { generateAppName } from "./utils";
|
||||
|
||||
export const volumeBackups = pgTable("volume_backup", {
|
||||
|
||||
@@ -45,7 +45,6 @@ export * from "./setup/traefik-setup";
|
||||
export * from "./setup/server-validate";
|
||||
export * from "./setup/server-audit";
|
||||
export * from "./utils/watch-paths/should-deploy";
|
||||
export * from "./utils/providers/github";
|
||||
export * from "./utils/backups/index";
|
||||
export * from "./utils/backups/mariadb";
|
||||
export * from "./utils/backups/mongo";
|
||||
|
||||
@@ -298,7 +298,11 @@ export const validateRequest = async (request: IncomingMessage) => {
|
||||
|
||||
const mockSession = {
|
||||
session: {
|
||||
userId: apiKeyRecord.user.id,
|
||||
user: {
|
||||
id: apiKeyRecord.user.id,
|
||||
email: apiKeyRecord.user.email,
|
||||
name: apiKeyRecord.user.name,
|
||||
},
|
||||
activeOrganizationId: organizationId || "",
|
||||
},
|
||||
user: {
|
||||
|
||||
@@ -404,11 +404,13 @@ export const deployPreviewApplication = async ({
|
||||
titleLog = "Preview Deployment",
|
||||
descriptionLog = "",
|
||||
previewDeploymentId,
|
||||
isExternal = false,
|
||||
}: {
|
||||
applicationId: string;
|
||||
titleLog: string;
|
||||
descriptionLog: string;
|
||||
previewDeploymentId: string;
|
||||
isExternal?: boolean;
|
||||
}) => {
|
||||
const application = await findApplicationById(applicationId);
|
||||
|
||||
@@ -434,36 +436,39 @@ export const deployPreviewApplication = async ({
|
||||
githubId: application?.githubId || "",
|
||||
};
|
||||
try {
|
||||
const commentExists = await issueCommentExists({
|
||||
...issueParams,
|
||||
});
|
||||
if (!commentExists) {
|
||||
const result = await createPreviewDeploymentComment({
|
||||
if (!isExternal) {
|
||||
const commentExists = await issueCommentExists({
|
||||
...issueParams,
|
||||
previewDomain,
|
||||
appName: previewDeployment.appName,
|
||||
githubId: application?.githubId || "",
|
||||
previewDeploymentId,
|
||||
});
|
||||
|
||||
if (!result) {
|
||||
throw new TRPCError({
|
||||
code: "NOT_FOUND",
|
||||
message: "Pull request comment not found",
|
||||
if (!commentExists) {
|
||||
const result = await createPreviewDeploymentComment({
|
||||
...issueParams,
|
||||
previewDomain,
|
||||
appName: previewDeployment.appName,
|
||||
githubId: application?.githubId || "",
|
||||
previewDeploymentId,
|
||||
});
|
||||
}
|
||||
|
||||
issueParams.comment_id = Number.parseInt(result?.pullRequestCommentId);
|
||||
if (!result) {
|
||||
throw new TRPCError({
|
||||
code: "NOT_FOUND",
|
||||
message: "Pull request comment not found",
|
||||
});
|
||||
}
|
||||
|
||||
issueParams.comment_id = Number.parseInt(result?.pullRequestCommentId);
|
||||
}
|
||||
const buildingComment = getIssueComment(
|
||||
application.name,
|
||||
"running",
|
||||
previewDomain,
|
||||
);
|
||||
await updateIssueComment({
|
||||
...issueParams,
|
||||
body: `### Dokploy Preview Deployment\n\n${buildingComment}`,
|
||||
});
|
||||
}
|
||||
const buildingComment = getIssueComment(
|
||||
application.name,
|
||||
"running",
|
||||
previewDomain,
|
||||
);
|
||||
await updateIssueComment({
|
||||
...issueParams,
|
||||
body: `### Dokploy Preview Deployment\n\n${buildingComment}`,
|
||||
});
|
||||
|
||||
application.appName = previewDeployment.appName;
|
||||
application.env = `${application.previewEnv}\nDOKPLOY_DEPLOY_URL=${previewDeployment?.domain?.host}`;
|
||||
application.buildArgs = application.previewBuildArgs;
|
||||
@@ -477,25 +482,31 @@ export const deployPreviewApplication = async ({
|
||||
});
|
||||
await buildApplication(application, deployment.logPath);
|
||||
}
|
||||
const successComment = getIssueComment(
|
||||
application.name,
|
||||
"success",
|
||||
previewDomain,
|
||||
);
|
||||
await updateIssueComment({
|
||||
...issueParams,
|
||||
body: `### Dokploy Preview Deployment\n\n${successComment}`,
|
||||
});
|
||||
|
||||
if (!isExternal) {
|
||||
const successComment = getIssueComment(
|
||||
application.name,
|
||||
"success",
|
||||
previewDomain,
|
||||
);
|
||||
await updateIssueComment({
|
||||
...issueParams,
|
||||
body: `### Dokploy Preview Deployment\n\n${successComment}`,
|
||||
});
|
||||
}
|
||||
|
||||
await updateDeploymentStatus(deployment.deploymentId, "done");
|
||||
await updatePreviewDeployment(previewDeploymentId, {
|
||||
previewStatus: "done",
|
||||
});
|
||||
} catch (error) {
|
||||
const comment = getIssueComment(application.name, "error", previewDomain);
|
||||
await updateIssueComment({
|
||||
...issueParams,
|
||||
body: `### Dokploy Preview Deployment\n\n${comment}`,
|
||||
});
|
||||
if (!isExternal) {
|
||||
const comment = getIssueComment(application.name, "error", previewDomain);
|
||||
await updateIssueComment({
|
||||
...issueParams,
|
||||
body: `### Dokploy Preview Deployment\n\n${comment}`,
|
||||
});
|
||||
}
|
||||
await updateDeploymentStatus(deployment.deploymentId, "error");
|
||||
await updatePreviewDeployment(previewDeploymentId, {
|
||||
previewStatus: "error",
|
||||
@@ -511,11 +522,13 @@ export const deployRemotePreviewApplication = async ({
|
||||
titleLog = "Preview Deployment",
|
||||
descriptionLog = "",
|
||||
previewDeploymentId,
|
||||
isExternal = false,
|
||||
}: {
|
||||
applicationId: string;
|
||||
titleLog: string;
|
||||
descriptionLog: string;
|
||||
previewDeploymentId: string;
|
||||
isExternal?: boolean;
|
||||
}) => {
|
||||
const application = await findApplicationById(applicationId);
|
||||
|
||||
@@ -541,36 +554,39 @@ export const deployRemotePreviewApplication = async ({
|
||||
githubId: application?.githubId || "",
|
||||
};
|
||||
try {
|
||||
const commentExists = await issueCommentExists({
|
||||
...issueParams,
|
||||
});
|
||||
if (!commentExists) {
|
||||
const result = await createPreviewDeploymentComment({
|
||||
if (!isExternal) {
|
||||
const commentExists = await issueCommentExists({
|
||||
...issueParams,
|
||||
previewDomain,
|
||||
appName: previewDeployment.appName,
|
||||
githubId: application?.githubId || "",
|
||||
previewDeploymentId,
|
||||
});
|
||||
|
||||
if (!result) {
|
||||
throw new TRPCError({
|
||||
code: "NOT_FOUND",
|
||||
message: "Pull request comment not found",
|
||||
if (!commentExists) {
|
||||
const result = await createPreviewDeploymentComment({
|
||||
...issueParams,
|
||||
previewDomain,
|
||||
appName: previewDeployment.appName,
|
||||
githubId: application?.githubId || "",
|
||||
previewDeploymentId,
|
||||
});
|
||||
}
|
||||
|
||||
issueParams.comment_id = Number.parseInt(result?.pullRequestCommentId);
|
||||
if (!result) {
|
||||
throw new TRPCError({
|
||||
code: "NOT_FOUND",
|
||||
message: "Pull request comment not found",
|
||||
});
|
||||
}
|
||||
|
||||
issueParams.comment_id = Number.parseInt(result?.pullRequestCommentId);
|
||||
}
|
||||
const buildingComment = getIssueComment(
|
||||
application.name,
|
||||
"running",
|
||||
previewDomain,
|
||||
);
|
||||
await updateIssueComment({
|
||||
...issueParams,
|
||||
body: `### Dokploy Preview Deployment\n\n${buildingComment}`,
|
||||
});
|
||||
}
|
||||
const buildingComment = getIssueComment(
|
||||
application.name,
|
||||
"running",
|
||||
previewDomain,
|
||||
);
|
||||
await updateIssueComment({
|
||||
...issueParams,
|
||||
body: `### Dokploy Preview Deployment\n\n${buildingComment}`,
|
||||
});
|
||||
|
||||
application.appName = previewDeployment.appName;
|
||||
application.env = `${application.previewEnv}\nDOKPLOY_DEPLOY_URL=${previewDeployment?.domain?.host}`;
|
||||
application.buildArgs = application.previewBuildArgs;
|
||||
@@ -592,25 +608,29 @@ export const deployRemotePreviewApplication = async ({
|
||||
await mechanizeDockerContainer(application);
|
||||
}
|
||||
|
||||
const successComment = getIssueComment(
|
||||
application.name,
|
||||
"success",
|
||||
previewDomain,
|
||||
);
|
||||
await updateIssueComment({
|
||||
...issueParams,
|
||||
body: `### Dokploy Preview Deployment\n\n${successComment}`,
|
||||
});
|
||||
if (!isExternal) {
|
||||
const successComment = getIssueComment(
|
||||
application.name,
|
||||
"success",
|
||||
previewDomain,
|
||||
);
|
||||
await updateIssueComment({
|
||||
...issueParams,
|
||||
body: `### Dokploy Preview Deployment\n\n${successComment}`,
|
||||
});
|
||||
}
|
||||
await updateDeploymentStatus(deployment.deploymentId, "done");
|
||||
await updatePreviewDeployment(previewDeploymentId, {
|
||||
previewStatus: "done",
|
||||
});
|
||||
} catch (error) {
|
||||
const comment = getIssueComment(application.name, "error", previewDomain);
|
||||
await updateIssueComment({
|
||||
...issueParams,
|
||||
body: `### Dokploy Preview Deployment\n\n${comment}`,
|
||||
});
|
||||
if (!isExternal) {
|
||||
const comment = getIssueComment(application.name, "error", previewDomain);
|
||||
await updateIssueComment({
|
||||
...issueParams,
|
||||
body: `### Dokploy Preview Deployment\n\n${comment}`,
|
||||
});
|
||||
}
|
||||
await updateDeploymentStatus(deployment.deploymentId, "error");
|
||||
await updatePreviewDeployment(previewDeploymentId, {
|
||||
previewStatus: "error",
|
||||
|
||||
@@ -31,8 +31,8 @@ import {
|
||||
findPreviewDeploymentById,
|
||||
updatePreviewDeployment,
|
||||
} from "./preview-deployment";
|
||||
import { removeRollbackById } from "./rollbacks";
|
||||
import { findScheduleById } from "./schedule";
|
||||
import { removeRollbackById } from "./rollbacks";
|
||||
import { findVolumeBackupById } from "./volume-backups";
|
||||
|
||||
export type Deployment = typeof deployments.$inferSelect;
|
||||
|
||||
@@ -192,156 +192,3 @@ export const createPreviewDeploymentComment = async ({
|
||||
pullRequestCommentId: `${issue.data.id}`,
|
||||
}).then((response) => response[0]);
|
||||
};
|
||||
|
||||
/**
|
||||
* Generate security notification message for blocked PR deployments
|
||||
*/
|
||||
export const getSecurityBlockedMessage = (
|
||||
prAuthor: string,
|
||||
repositoryName: string,
|
||||
permission: string | null,
|
||||
) => {
|
||||
return `### 🚨 Preview Deployment Blocked - Security Protection
|
||||
|
||||
**Your pull request was blocked from triggering preview deployments**
|
||||
|
||||
#### Why was this blocked?
|
||||
- **User**: \`${prAuthor}\`
|
||||
- **Repository**: \`${repositoryName}\`
|
||||
- **Permission Level**: \`${permission || "none"}\`
|
||||
- **Required Level**: \`write\`, \`maintain\`, or \`admin\`
|
||||
|
||||
#### How to resolve this:
|
||||
|
||||
**Option 1: Get Collaborator Access (Recommended)**
|
||||
Ask a repository maintainer to invite you as a collaborator with **write permissions** or higher.
|
||||
|
||||
**Option 2: Request Permission Override**
|
||||
Ask a repository administrator to disable security validation for this specific application if appropriate.
|
||||
|
||||
#### For Repository Administrators:
|
||||
To disable this security check (⚠️ **not recommended for public repositories**):
|
||||
Enter to preview settings and disable the security check.
|
||||
|
||||
---
|
||||
*This security measure protects against malicious code execution in preview deployments. Only trusted collaborators should have the ability to trigger deployments.*
|
||||
|
||||
<details>
|
||||
<summary>🛡️ Learn more about this security feature</summary>
|
||||
|
||||
This protection prevents unauthorized users from:
|
||||
- Executing malicious code on the deployment server
|
||||
- Accessing environment variables and secrets
|
||||
- Potentially compromising the infrastructure
|
||||
|
||||
Preview deployments are powerful but require trust. Only users with repository write access can trigger them.
|
||||
</details>`;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if a security notification comment already exists on a GitHub PR
|
||||
* This prevents creating duplicate security comments on subsequent pushes
|
||||
*/
|
||||
export const hasExistingSecurityComment = async ({
|
||||
owner,
|
||||
repository,
|
||||
prNumber,
|
||||
githubId,
|
||||
}: {
|
||||
owner: string;
|
||||
repository: string;
|
||||
prNumber: number;
|
||||
githubId: string;
|
||||
}): Promise<boolean> => {
|
||||
try {
|
||||
const github = await findGithubById(githubId);
|
||||
const octokit = authGithub(github);
|
||||
|
||||
// Get all comments for this PR
|
||||
const { data: comments } = await octokit.rest.issues.listComments({
|
||||
owner,
|
||||
repo: repository,
|
||||
issue_number: prNumber,
|
||||
});
|
||||
|
||||
// Check if any comment contains our security notification marker
|
||||
const securityCommentExists = comments.some((comment) =>
|
||||
comment.body?.includes(
|
||||
"🚨 Preview Deployment Blocked - Security Protection",
|
||||
),
|
||||
);
|
||||
|
||||
return securityCommentExists;
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`❌ Failed to check existing comments on PR #${prNumber}:`,
|
||||
error,
|
||||
);
|
||||
// If we can't check, assume no comment exists to avoid blocking functionality
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a security notification comment on a GitHub PR
|
||||
*/
|
||||
export const createSecurityBlockedComment = async ({
|
||||
owner,
|
||||
repository,
|
||||
prNumber,
|
||||
prAuthor,
|
||||
permission,
|
||||
githubId,
|
||||
}: {
|
||||
owner: string;
|
||||
repository: string;
|
||||
prNumber: number;
|
||||
prAuthor: string;
|
||||
permission: string | null;
|
||||
githubId: string;
|
||||
}) => {
|
||||
try {
|
||||
// Check if a security comment already exists to prevent duplicates
|
||||
const commentExists = await hasExistingSecurityComment({
|
||||
owner,
|
||||
repository,
|
||||
prNumber,
|
||||
githubId,
|
||||
});
|
||||
|
||||
if (commentExists) {
|
||||
console.log(
|
||||
`ℹ️ Security notification comment already exists on PR #${prNumber}, skipping duplicate`,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
const github = await findGithubById(githubId);
|
||||
const octokit = authGithub(github);
|
||||
|
||||
const securityMessage = getSecurityBlockedMessage(
|
||||
prAuthor,
|
||||
repository,
|
||||
permission,
|
||||
);
|
||||
|
||||
const issue = await octokit.rest.issues.createComment({
|
||||
owner,
|
||||
repo: repository,
|
||||
issue_number: prNumber,
|
||||
body: securityMessage,
|
||||
});
|
||||
|
||||
console.log(
|
||||
`✅ Security notification comment created on PR #${prNumber}: ${issue.data.html_url}`,
|
||||
);
|
||||
return issue.data;
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`❌ Failed to create security comment on PR #${prNumber}:`,
|
||||
error,
|
||||
);
|
||||
// Don't throw error - security comment is nice-to-have, not critical
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -152,6 +152,7 @@ export const findPreviewDeploymentsByApplicationId = async (
|
||||
|
||||
export const createPreviewDeployment = async (
|
||||
schema: typeof apiCreatePreviewDeployment._type,
|
||||
isExternal = false,
|
||||
) => {
|
||||
const application = await findApplicationById(schema.applicationId);
|
||||
const appName = `preview-${application.appName}-${generatePassword(6)}`;
|
||||
@@ -166,27 +167,32 @@ export const createPreviewDeployment = async (
|
||||
org?.ownerId || "",
|
||||
);
|
||||
|
||||
const octokit = authGithub(application?.github as Github);
|
||||
let issueId = "";
|
||||
if (!isExternal) {
|
||||
const octokit = authGithub(application?.github as Github);
|
||||
|
||||
const runningComment = getIssueComment(
|
||||
application.name,
|
||||
"initializing",
|
||||
`${application.previewHttps ? "https" : "http"}://${generateDomain}`,
|
||||
);
|
||||
const runningComment = getIssueComment(
|
||||
application.name,
|
||||
"initializing",
|
||||
`${application.previewHttps ? "https" : "http"}://${generateDomain}`,
|
||||
);
|
||||
|
||||
const issue = await octokit.rest.issues.createComment({
|
||||
owner: application?.owner || "",
|
||||
repo: application?.repository || "",
|
||||
issue_number: Number.parseInt(schema.pullRequestNumber),
|
||||
body: `### Dokploy Preview Deployment\n\n${runningComment}`,
|
||||
});
|
||||
const issue = await octokit.rest.issues.createComment({
|
||||
owner: application?.owner || "",
|
||||
repo: application?.repository || "",
|
||||
issue_number: Number.parseInt(schema.pullRequestNumber),
|
||||
body: `### Dokploy Preview Deployment\n\n${runningComment}`,
|
||||
});
|
||||
|
||||
issueId = `${issue.data.id}`;
|
||||
}
|
||||
|
||||
const previewDeployment = await db
|
||||
.insert(previewDeployments)
|
||||
.values({
|
||||
...schema,
|
||||
appName: appName,
|
||||
pullRequestCommentId: `${issue.data.id}`,
|
||||
pullRequestCommentId: issueId,
|
||||
})
|
||||
.returning()
|
||||
.then((value) => value[0]);
|
||||
@@ -233,6 +239,13 @@ export const findPreviewDeploymentsByPullRequestId = async (
|
||||
) => {
|
||||
const previewDeploymentResult = await db.query.previewDeployments.findMany({
|
||||
where: eq(previewDeployments.pullRequestId, pullRequestId),
|
||||
with: {
|
||||
application: {
|
||||
with: {
|
||||
project: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return previewDeploymentResult;
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
import type { CreateServiceOptions } from "dockerode";
|
||||
import { eq } from "drizzle-orm";
|
||||
import type { z } from "zod";
|
||||
import { db } from "../db";
|
||||
import {
|
||||
type createRollbackSchema,
|
||||
deployments as deploymentsSchema,
|
||||
rollbacks,
|
||||
deployments as deploymentsSchema,
|
||||
} from "../db/schema";
|
||||
import { type ApplicationNested, getAuthConfig } from "../utils/builders";
|
||||
import type { z } from "zod";
|
||||
import { type Application, findApplicationById } from "./application";
|
||||
import { getRemoteDocker } from "../utils/servers/remote-docker";
|
||||
import { getAuthConfig, type ApplicationNested } from "../utils/builders";
|
||||
import { execAsync, execAsyncRemote } from "../utils/process/execAsync";
|
||||
import type { CreateServiceOptions } from "dockerode";
|
||||
import { findDeploymentById } from "./deployment";
|
||||
import {
|
||||
prepareEnvironmentVariables,
|
||||
calculateResources,
|
||||
generateBindMounts,
|
||||
generateConfigContainer,
|
||||
generateVolumeMounts,
|
||||
prepareEnvironmentVariables,
|
||||
} from "../utils/docker/utils";
|
||||
import { execAsync, execAsyncRemote } from "../utils/process/execAsync";
|
||||
import { getRemoteDocker } from "../utils/servers/remote-docker";
|
||||
import { type Application, findApplicationById } from "./application";
|
||||
import { findDeploymentById } from "./deployment";
|
||||
import type { Project } from "./project";
|
||||
import type { Mount } from "./mount";
|
||||
import type { Port } from "./port";
|
||||
import type { Project } from "./project";
|
||||
|
||||
export const createRollback = async (
|
||||
input: z.infer<typeof createRollbackSchema>,
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { eq } from "drizzle-orm";
|
||||
import type { z } from "zod";
|
||||
import { db } from "../db";
|
||||
import {
|
||||
type createVolumeBackupSchema,
|
||||
type updateVolumeBackupSchema,
|
||||
volumeBackups,
|
||||
} from "../db/schema";
|
||||
import { db } from "../db";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import type { z } from "zod";
|
||||
|
||||
export const findVolumeBackupById = async (volumeBackupId: string) => {
|
||||
const volumeBackup = await db.query.volumeBackups.findFirst({
|
||||
|
||||
@@ -191,9 +191,6 @@ export const createDefaultServerTraefikConfig = () => {
|
||||
|
||||
export const getDefaultTraefikConfig = () => {
|
||||
const configObject: MainTraefikConfig = {
|
||||
global: {
|
||||
sendAnonymousUsage: false,
|
||||
},
|
||||
providers: {
|
||||
...(process.env.NODE_ENV === "development"
|
||||
? {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
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 { generateRandomHash } from "./compose";
|
||||
import type { ComposeSpecification } from "./types";
|
||||
|
||||
export const addAppNameToPreventCollision = (
|
||||
|
||||
@@ -45,49 +45,6 @@ export const getGithubToken = async (
|
||||
return installation.token;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if a GitHub user has write/admin permissions on a repository
|
||||
* This is used to validate PR authors before allowing preview deployments
|
||||
*/
|
||||
export const checkUserRepositoryPermissions = async (
|
||||
githubProvider: Github,
|
||||
owner: string,
|
||||
repo: string,
|
||||
username: string,
|
||||
): Promise<{ hasWriteAccess: boolean; permission: string | null }> => {
|
||||
try {
|
||||
const octokit = authGithub(githubProvider);
|
||||
|
||||
// Check if user is a collaborator with write permissions
|
||||
const { data: permission } =
|
||||
await octokit.rest.repos.getCollaboratorPermissionLevel({
|
||||
owner,
|
||||
repo,
|
||||
username,
|
||||
});
|
||||
|
||||
// Allow only users with 'write', 'admin', or 'maintain' permissions
|
||||
// Currently exists Read, Triage, Write, Maintain, Admin
|
||||
const allowedPermissions = ["write", "admin", "maintain"];
|
||||
const hasWriteAccess = allowedPermissions.includes(permission.permission);
|
||||
|
||||
return {
|
||||
hasWriteAccess,
|
||||
permission: permission.permission,
|
||||
};
|
||||
} catch (error) {
|
||||
// If user is not a collaborator, GitHub API returns 404
|
||||
console.warn(
|
||||
`User ${username} is not a collaborator of ${owner}/${repo}:`,
|
||||
error,
|
||||
);
|
||||
return {
|
||||
hasWriteAccess: false,
|
||||
permission: null,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export const haveGithubRequirements = (githubProvider: Github) => {
|
||||
return !!(
|
||||
githubProvider?.githubAppId &&
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import fs, { writeFileSync } from "node:fs";
|
||||
import { createReadStream } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { createReadStream } from "node:fs";
|
||||
import { createInterface } from "node:readline";
|
||||
import { paths } from "@dokploy/server/constants";
|
||||
import type { Domain } from "@dokploy/server/services/domain";
|
||||
@@ -237,6 +237,7 @@ export const writeTraefikConfigInPath = async (
|
||||
} else {
|
||||
fs.writeFileSync(configPath, traefikConfig, "utf8");
|
||||
}
|
||||
fs.writeFileSync(configPath, traefikConfig, "utf8");
|
||||
} catch (e) {
|
||||
console.error("Error saving the YAML config file:", e);
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { paths } from "@dokploy/server/constants";
|
||||
import type { Domain } from "@dokploy/server/services/domain";
|
||||
import { dump, load } from "js-yaml";
|
||||
import type { ApplicationNested } from "../builders";
|
||||
import { execAsyncRemote } from "../process/execAsync";
|
||||
import { writeTraefikConfigRemote } from "./application";
|
||||
import type { FileConfig } from "./file-types";
|
||||
import type { Domain } from "@dokploy/server/services/domain";
|
||||
|
||||
export const addMiddleware = (config: FileConfig, middlewareName: string) => {
|
||||
if (config.http?.routers) {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import path from "node:path";
|
||||
import { paths } from "@dokploy/server/constants";
|
||||
import { findComposeById } from "@dokploy/server/services/compose";
|
||||
import type { findVolumeBackupById } from "@dokploy/server/services/volume-backups";
|
||||
import { normalizeS3Path } from "../backups/utils";
|
||||
import { getS3Credentials } from "../backups/utils";
|
||||
import path from "node:path";
|
||||
import { paths } from "@dokploy/server/constants";
|
||||
import { findComposeById } from "@dokploy/server/services/compose";
|
||||
|
||||
export const backupVolume = async (
|
||||
volumeBackup: Awaited<ReturnType<typeof findVolumeBackupById>>,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import path from "node:path";
|
||||
import {
|
||||
findApplicationById,
|
||||
findComposeById,
|
||||
@@ -6,6 +5,7 @@ import {
|
||||
getS3Credentials,
|
||||
paths,
|
||||
} from "../..";
|
||||
import path from "node:path";
|
||||
|
||||
export const restoreVolume = async (
|
||||
id: string,
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { findVolumeBackupById } from "@dokploy/server/services/volume-backups";
|
||||
import { scheduleJob, scheduledJobs } from "node-schedule";
|
||||
import {
|
||||
createDeploymentVolumeBackup,
|
||||
execAsync,
|
||||
@@ -7,6 +6,7 @@ import {
|
||||
updateDeploymentStatus,
|
||||
} from "../..";
|
||||
import { backupVolume } from "./backup";
|
||||
import { scheduleJob, scheduledJobs } from "node-schedule";
|
||||
|
||||
export const scheduleVolumeBackup = async (volumeBackupId: string) => {
|
||||
const volumeBackup = await findVolumeBackupById(volumeBackupId);
|
||||
|
||||
74
pnpm-lock.yaml
generated
74
pnpm-lock.yaml
generated
@@ -13,8 +13,8 @@ importers:
|
||||
.:
|
||||
devDependencies:
|
||||
'@biomejs/biome':
|
||||
specifier: 2.1.1
|
||||
version: 2.1.1
|
||||
specifier: 1.9.4
|
||||
version: 1.9.4
|
||||
'@commitlint/cli':
|
||||
specifier: ^19.8.1
|
||||
version: 19.8.1(@types/node@18.19.104)(typescript@5.8.3)
|
||||
@@ -933,55 +933,55 @@ packages:
|
||||
'@better-fetch/fetch@1.1.18':
|
||||
resolution: {integrity: sha512-rEFOE1MYIsBmoMJtQbl32PGHHXuG2hDxvEd7rUHE0vCBoFQVSDqaVs9hkZEtHCxRoY+CljXKFCOuJ8uxqw1LcA==}
|
||||
|
||||
'@biomejs/biome@2.1.1':
|
||||
resolution: {integrity: sha512-HFGYkxG714KzG+8tvtXCJ1t1qXQMzgWzfvQaUjxN6UeKv+KvMEuliInnbZLJm6DXFXwqVi6446EGI0sGBLIYng==}
|
||||
'@biomejs/biome@1.9.4':
|
||||
resolution: {integrity: sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==}
|
||||
engines: {node: '>=14.21.3'}
|
||||
hasBin: true
|
||||
|
||||
'@biomejs/cli-darwin-arm64@2.1.1':
|
||||
resolution: {integrity: sha512-2Muinu5ok4tWxq4nu5l19el48cwCY/vzvI7Vjbkf3CYIQkjxZLyj0Ad37Jv2OtlXYaLvv+Sfu1hFeXt/JwRRXQ==}
|
||||
'@biomejs/cli-darwin-arm64@1.9.4':
|
||||
resolution: {integrity: sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw==}
|
||||
engines: {node: '>=14.21.3'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@biomejs/cli-darwin-x64@2.1.1':
|
||||
resolution: {integrity: sha512-cC8HM5lrgKQXLAK+6Iz2FrYW5A62pAAX6KAnRlEyLb+Q3+Kr6ur/sSuoIacqlp1yvmjHJqjYfZjPvHWnqxoEIA==}
|
||||
'@biomejs/cli-darwin-x64@1.9.4':
|
||||
resolution: {integrity: sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg==}
|
||||
engines: {node: '>=14.21.3'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@biomejs/cli-linux-arm64-musl@2.1.1':
|
||||
resolution: {integrity: sha512-/7FBLnTswu4jgV9ttI3AMIdDGqVEPIZd8I5u2D4tfCoj8rl9dnjrEQbAIDlWhUXdyWlFSz8JypH3swU9h9P+2A==}
|
||||
'@biomejs/cli-linux-arm64-musl@1.9.4':
|
||||
resolution: {integrity: sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA==}
|
||||
engines: {node: '>=14.21.3'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@biomejs/cli-linux-arm64@2.1.1':
|
||||
resolution: {integrity: sha512-tw4BEbhAUkWPe4WBr6IX04DJo+2jz5qpPzpW/SWvqMjb9QuHY8+J0M23V8EPY/zWU4IG8Ui0XESapR1CB49Q7g==}
|
||||
'@biomejs/cli-linux-arm64@1.9.4':
|
||||
resolution: {integrity: sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g==}
|
||||
engines: {node: '>=14.21.3'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@biomejs/cli-linux-x64-musl@2.1.1':
|
||||
resolution: {integrity: sha512-kUu+loNI3OCD2c12cUt7M5yaaSjDnGIksZwKnueubX6c/HWUyi/0mPbTBHR49Me3F0KKjWiKM+ZOjsmC+lUt9g==}
|
||||
'@biomejs/cli-linux-x64-musl@1.9.4':
|
||||
resolution: {integrity: sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg==}
|
||||
engines: {node: '>=14.21.3'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@biomejs/cli-linux-x64@2.1.1':
|
||||
resolution: {integrity: sha512-3WJ1GKjU7NzZb6RTbwLB59v9cTIlzjbiFLDB0z4376TkDqoNYilJaC37IomCr/aXwuU8QKkrYoHrgpSq5ffJ4Q==}
|
||||
'@biomejs/cli-linux-x64@1.9.4':
|
||||
resolution: {integrity: sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg==}
|
||||
engines: {node: '>=14.21.3'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@biomejs/cli-win32-arm64@2.1.1':
|
||||
resolution: {integrity: sha512-vEHK0v0oW+E6RUWLoxb2isI3rZo57OX9ZNyyGH701fZPj6Il0Rn1f5DMNyCmyflMwTnIQstEbs7n2BxYSqQx4Q==}
|
||||
'@biomejs/cli-win32-arm64@1.9.4':
|
||||
resolution: {integrity: sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg==}
|
||||
engines: {node: '>=14.21.3'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@biomejs/cli-win32-x64@2.1.1':
|
||||
resolution: {integrity: sha512-i2PKdn70kY++KEF/zkQFvQfX1e8SkA8hq4BgC+yE9dZqyLzB/XStY2MvwI3qswlRgnGpgncgqe0QYKVS1blksg==}
|
||||
'@biomejs/cli-win32-x64@1.9.4':
|
||||
resolution: {integrity: sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA==}
|
||||
engines: {node: '>=14.21.3'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
@@ -7281,39 +7281,39 @@ snapshots:
|
||||
|
||||
'@better-fetch/fetch@1.1.18': {}
|
||||
|
||||
'@biomejs/biome@2.1.1':
|
||||
'@biomejs/biome@1.9.4':
|
||||
optionalDependencies:
|
||||
'@biomejs/cli-darwin-arm64': 2.1.1
|
||||
'@biomejs/cli-darwin-x64': 2.1.1
|
||||
'@biomejs/cli-linux-arm64': 2.1.1
|
||||
'@biomejs/cli-linux-arm64-musl': 2.1.1
|
||||
'@biomejs/cli-linux-x64': 2.1.1
|
||||
'@biomejs/cli-linux-x64-musl': 2.1.1
|
||||
'@biomejs/cli-win32-arm64': 2.1.1
|
||||
'@biomejs/cli-win32-x64': 2.1.1
|
||||
'@biomejs/cli-darwin-arm64': 1.9.4
|
||||
'@biomejs/cli-darwin-x64': 1.9.4
|
||||
'@biomejs/cli-linux-arm64': 1.9.4
|
||||
'@biomejs/cli-linux-arm64-musl': 1.9.4
|
||||
'@biomejs/cli-linux-x64': 1.9.4
|
||||
'@biomejs/cli-linux-x64-musl': 1.9.4
|
||||
'@biomejs/cli-win32-arm64': 1.9.4
|
||||
'@biomejs/cli-win32-x64': 1.9.4
|
||||
|
||||
'@biomejs/cli-darwin-arm64@2.1.1':
|
||||
'@biomejs/cli-darwin-arm64@1.9.4':
|
||||
optional: true
|
||||
|
||||
'@biomejs/cli-darwin-x64@2.1.1':
|
||||
'@biomejs/cli-darwin-x64@1.9.4':
|
||||
optional: true
|
||||
|
||||
'@biomejs/cli-linux-arm64-musl@2.1.1':
|
||||
'@biomejs/cli-linux-arm64-musl@1.9.4':
|
||||
optional: true
|
||||
|
||||
'@biomejs/cli-linux-arm64@2.1.1':
|
||||
'@biomejs/cli-linux-arm64@1.9.4':
|
||||
optional: true
|
||||
|
||||
'@biomejs/cli-linux-x64-musl@2.1.1':
|
||||
'@biomejs/cli-linux-x64-musl@1.9.4':
|
||||
optional: true
|
||||
|
||||
'@biomejs/cli-linux-x64@2.1.1':
|
||||
'@biomejs/cli-linux-x64@1.9.4':
|
||||
optional: true
|
||||
|
||||
'@biomejs/cli-win32-arm64@2.1.1':
|
||||
'@biomejs/cli-win32-arm64@1.9.4':
|
||||
optional: true
|
||||
|
||||
'@biomejs/cli-win32-x64@2.1.1':
|
||||
'@biomejs/cli-win32-x64@1.9.4':
|
||||
optional: true
|
||||
|
||||
'@codemirror/autocomplete@6.18.6':
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
packages:
|
||||
- "apps/api"
|
||||
- "apps/dokploy"
|
||||
- "apps/monitoring"
|
||||
- "apps/schedules"
|
||||
- "packages/server"
|
||||
|
||||
Reference in New Issue
Block a user