Compare commits

..

37 Commits

Author SHA1 Message Date
Mauricio Siu
b95dfed8fc chore(package): bump version to v0.24.4 2025-07-20 20:06:47 -06:00
Mauricio Siu
7fe3418d55 Merge pull request #2218 from Dokploy/2179-reloading-traefik-on-the-remote-server-will-cause-traefik-on-the-instance-to-change-accordingly
fix(traefik): remove duplicate file write operation in writeTraefikCo…
2025-07-20 20:05:48 -06:00
Mauricio Siu
288d86c73b fix(traefik): remove duplicate file write operation in writeTraefikConfigInPath function 2025-07-20 20:05:30 -06:00
Mauricio Siu
ffd5ccd386 Merge pull request #2202 from gentslava/feat/traefik-config
feat(config): Traefik
2025-07-20 19:45:53 -06:00
Mauricio Siu
98ddd096e5 Update packages/server/src/setup/traefik-setup.ts 2025-07-20 19:45:41 -06:00
Mauricio Siu
da6cc9fe72 Merge pull request #2190 from Marukome0743/format
chore: version up format.yml actions
2025-07-20 19:44:20 -06:00
Mauricio Siu
22d0af269e Merge pull request #2200 from Marukome0743/server
refactor: lint and sort imports on dokploy/server
2025-07-20 19:42:15 -06:00
Mauricio Siu
f0fdc46de5 Merge pull request #2187 from Marukome0743/v2
chore: upgrade to Biome v2
2025-07-20 19:41:49 -06:00
Mauricio Siu
9aea24115d Merge pull request #2199 from Marukome0743/lint
refactor: lint and sort import on dokploy application
2025-07-20 19:41:02 -06:00
Mauricio Siu
a9ee6c2393 Merge pull request #2194 from Marukome0743/pnpm
chore(package): version up pnpm to v9.12.0
2025-07-20 19:40:09 -06:00
Mauricio Siu
349717044c Merge pull request #2196 from Marukome0743/dispatch
ci: remove custom branch and add workflow_dispatch event
2025-07-20 19:37:27 -06:00
Mauricio Siu
f94f32695f Merge pull request #2195 from Marukome0743/monitoring
chore: remove `apps/monitoring` from `pnpm-workspace.yaml`
2025-07-20 19:37:07 -06:00
Mauricio Siu
37b78ea09c Merge pull request #2217 from Dokploy/2201-daily-docker-cleanup-not-working-on-remote-server
fix(dashboard): update Docker cleanup toggle logic to prioritize serv…
2025-07-20 19:01:46 -06:00
Mauricio Siu
9b89b4631f fix(dashboard): update Docker cleanup toggle logic to prioritize server settings 2025-07-20 19:01:20 -06:00
Mauricio Siu
7100095f2b Merge pull request #2216 from Dokploy/2209-update-s3-destination-form-loses-its-state-when-current-tab-loses-its-focus
fix(dashboard): disable refetch on window focus for destination handling
2025-07-20 18:57:33 -06:00
Mauricio Siu
a36ab65aa6 fix(dashboard): disable refetch on window focus for destination handling 2025-07-20 18:56:35 -06:00
Mauricio Siu
bf81ba20ff Merge pull request #2215 from Dokploy/2197-git-provider-api-undefined_value-error
refactor(auth): simplify user session structure in validateRequest fu…
2025-07-20 18:55:16 -06:00
Mauricio Siu
658a4a9b99 refactor(auth): simplify user session structure in validateRequest function
- Changed user object in mockSession to only include userId, removing email and name for a more streamlined session representation.
2025-07-20 18:54:57 -06:00
Mauricio Siu
47cb096cf3 Merge pull request #2214 from Dokploy/2203-identical-webhook-redeploy-url-after-duplicating-project
feat(project): add refreshToken to application and compose data retri…
2025-07-20 18:45:39 -06:00
Mauricio Siu
f3856722da feat(project): add refreshToken to application and compose data retrieval
- Included refreshToken in the data returned from findApplicationById and findComposeById functions to enhance application state management.
2025-07-20 18:45:18 -06:00
Vyacheslav Scherbinin
a67c3eb979 feat(conf): accessLog filePath 2025-07-16 16:46:47 +07:00
Vyacheslav Scherbinin
aaa205f104 feat(conf): disable sendAnonymousUsage 2025-07-16 12:29:31 +07:00
Marukome0743
cadea7ff28 refactor: lint and sort imports on dokploy/server 2025-07-15 14:22:37 +09:00
Marukome0743
9ab937f726 refactor: lint dokploy application 2025-07-15 14:13:32 +09:00
Marukome0743
d0af517eb7 ci: remove custom branch and add workflow_dispatch event 2025-07-14 19:03:41 +09:00
Marukome0743
66bdf9bf0a chore: remove apps/monitoring from pnpm-workspace.yaml 2025-07-14 18:24:26 +09:00
Marukome0743
d4a3af475a chore(package): version up pnpm to v9.12.0 2025-07-14 15:58:20 +09:00
autofix-ci[bot]
e92a8d7c98 [autofix.ci] apply automated fixes 2025-07-14 15:30:24 +09:00
Marukome0743
c4fec8cee5 chore: upgrade to Biome v2 2025-07-14 15:30:23 +09:00
Marukome0743
55f75bce53 chore: version up format.yml actions 2025-07-14 15:30:06 +09:00
Mauricio Siu
fdc524d79d fix(ui): adjust layout in UpdateServer component
- Removed unnecessary padding from DialogContent for a cleaner appearance.
- Added margin-top to the button container for improved spacing.
2025-07-13 23:37:05 -06:00
Mauricio Siu
93d6662466 docs(preview): update collaborator permission description in preview settings 2025-07-13 23:26:41 -06:00
Mauricio Siu
1977235d31 Merge pull request #2192 from Dokploy/fix/preview-deployments-public-repos
feat(preview): add collaborator permission requirement for preview de…
2025-07-13 23:20:51 -06:00
Mauricio Siu
1dd713a1d1 fix(deploy): change preview deployment limit check to be exclusive 2025-07-13 23:20:23 -06:00
Mauricio Siu
18b65f28f2 chore(package): bump version to v0.24.3 and comment out unused trustedOrigins function in auth.ts 2025-07-13 23:19:31 -06:00
Mauricio Siu
666db23b8e test: add previewRequireCollaboratorPermissions field to drop and traefik test cases 2025-07-13 23:17:32 -06:00
Mauricio Siu
2ca5321fdc feat(preview): add collaborator permission requirement for preview deployments
- Introduced a new boolean field `previewRequireCollaboratorPermissions` in the application schema to enforce permission checks for preview deployments.
- Updated the UI to include a toggle for this setting in the preview deployment settings.
- Enhanced GitHub deployment handler to validate PR authors against the required permissions, blocking unauthorized deployments and providing security notifications.
- Added SQL migration to update the database schema accordingly.
2025-07-13 23:12:09 -06:00
56 changed files with 6642 additions and 156 deletions

View File

@@ -2,7 +2,8 @@ name: Build Docker images
on:
push:
branches: ["canary", "main", "feat/monitoring"]
branches: [main, canary]
workflow_dispatch:
jobs:
build-and-push-cloud-image:

View File

@@ -2,7 +2,8 @@ name: Dokploy Docker Build
on:
push:
branches: [main, canary, "1061-custom-docker-service-hostname"]
branches: [main, canary]
workflow_dispatch:
env:
IMAGE_NAME: dokploy/dokploy

View File

@@ -11,12 +11,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Setup biomeJs
uses: biomejs/setup-biome@v2
- name: Run Biome formatter
run: biome format . --write
run: biome format --write
- uses: autofix-ci/action@551dded8c6cc8a1054039c8bc0b8b48c51dfc6ef
- uses: autofix-ci/action@635ffb0c9798bd160680f18fd73371e355b85f27 # v1.3.2

View File

@@ -29,5 +29,9 @@
"tsx": "^4.16.2",
"typescript": "^5.8.3"
},
"packageManager": "pnpm@9.5.0"
"packageManager": "pnpm@9.12.0",
"engines": {
"node": "^20.16.0",
"pnpm": ">=9.12.0"
}
}

View File

@@ -29,6 +29,7 @@ const baseApp: ApplicationNested = {
herokuVersion: "",
giteaBranch: "",
giteaBuildPath: "",
previewRequireCollaboratorPermissions: false,
giteaId: "",
giteaOwner: "",
giteaRepository: "",

View File

@@ -18,6 +18,7 @@ const baseApp: ApplicationNested = {
appName: "",
autoDeploy: true,
enableSubmodules: false,
previewRequireCollaboratorPermissions: false,
serverId: "",
branch: null,
dockerBuildStage: "",

View File

@@ -1,4 +1,5 @@
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";
@@ -10,14 +11,13 @@ import {
CardTitle,
} from "@/components/ui/card";
import { type RouterOutputs, api } from "@/utils/api";
import { Clock, Loader2, RocketIcon, Settings, RefreshCcw } from "lucide-react";
import { Clock, Loader2, RefreshCcw, RocketIcon, Settings } 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;

View File

@@ -46,6 +46,7 @@ const schema = z
previewPath: z.string(),
previewCertificateType: z.enum(["letsencrypt", "none", "custom"]),
previewCustomCertResolver: z.string().optional(),
previewRequireCollaboratorPermissions: z.boolean(),
})
.superRefine((input, ctx) => {
if (
@@ -83,6 +84,7 @@ export const ShowPreviewSettings = ({ applicationId }: Props) => {
previewHttps: false,
previewPath: "/",
previewCertificateType: "none",
previewRequireCollaboratorPermissions: true,
},
resolver: zodResolver(schema),
});
@@ -105,6 +107,8 @@ export const ShowPreviewSettings = ({ applicationId }: Props) => {
previewPath: data.previewPath || "/",
previewCertificateType: data.previewCertificateType || "none",
previewCustomCertResolver: data.previewCustomCertResolver || "",
previewRequireCollaboratorPermissions:
data.previewRequireCollaboratorPermissions || true,
});
}
}, [data]);
@@ -121,6 +125,8 @@ export const ShowPreviewSettings = ({ applicationId }: Props) => {
previewPath: formData.previewPath,
previewCertificateType: formData.previewCertificateType,
previewCustomCertResolver: formData.previewCustomCertResolver,
previewRequireCollaboratorPermissions:
formData.previewRequireCollaboratorPermissions,
})
.then(() => {
toast.success("Preview Deployments settings updated");
@@ -312,6 +318,37 @@ 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"

View File

@@ -1,3 +1,4 @@
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";
@@ -42,9 +43,8 @@ import { useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
import { type LogLine, parseLogs } from "../../docker/logs/utils";
import { formatBytes } from "../../database/backups/restore-backup";
import { AlertBlock } from "@/components/shared/alert-block";
import { type LogLine, parseLogs } from "../../docker/logs/utils";
interface Props {
id: string;

View File

@@ -23,8 +23,8 @@ import {
Trash2,
} from "lucide-react";
import { toast } from "sonner";
import { HandleVolumeBackups } from "./handle-volume-backups";
import { ShowDeploymentsModal } from "../deployments/show-deployments-modal";
import { HandleVolumeBackups } from "./handle-volume-backups";
import { RestoreVolumeBackups } from "./restore-volume-backups";
interface Props {

View File

@@ -1,3 +1,4 @@
import { UnauthorizedGitProvider } from "@/components/dashboard/application/general/generic/unauthorized-git-provider";
import {
BitbucketIcon,
GitIcon,
@@ -11,6 +12,7 @@ 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";
@@ -18,8 +20,6 @@ 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 {

View File

@@ -70,6 +70,7 @@ export const HandleDestinations = ({ destinationId }: Props) => {
},
{
enabled: !!destinationId,
refetchOnWindowFocus: false,
},
);
const {

View File

@@ -20,7 +20,9 @@ export const ToggleDockerCleanup = ({ serverId }: Props) => {
},
);
const enabled = data?.user.enableDockerCleanup || server?.enableDockerCleanup;
const enabled = serverId
? server?.enableDockerCleanup
: data?.user.enableDockerCleanup;
const { mutateAsync } = api.settings.updateDockerCleanup.useMutation();

View File

@@ -126,7 +126,7 @@ export const UpdateServer = ({
</TooltipProvider>
)}
</DialogTrigger>
<DialogContent className="max-w-lg p-6">
<DialogContent className="max-w-lg">
<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">
<div className="space-y-4 flex items-center justify-end mt-4 ">
<div className="flex items-center gap-2">
<Button variant="outline" onClick={() => onOpenChange?.(false)}>
Cancel

View File

@@ -96,10 +96,7 @@ type SingleNavItem = {
title: string;
url: string;
icon?: LucideIcon;
isEnabled?: (opts: {
auth?: AuthQueryOutput;
isCloud: boolean;
}) => boolean;
isEnabled?: (opts: { auth?: AuthQueryOutput; isCloud: boolean }) => boolean;
};
// NavItem type
@@ -125,10 +122,7 @@ 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

View File

@@ -0,0 +1 @@
ALTER TABLE "application" ADD COLUMN "previewRequireCollaboratorPermissions" boolean DEFAULT true;

File diff suppressed because it is too large Load Diff

View File

@@ -722,6 +722,13 @@
"when": 1751848685503,
"tag": "0102_opposite_grandmaster",
"breakpoints": true
},
{
"idx": 103,
"version": "7",
"when": 1752465764072,
"tag": "0103_cultured_pestilence",
"breakpoints": true
}
]
}

View File

@@ -1,6 +1,6 @@
{
"name": "dokploy",
"version": "v0.24.2",
"version": "v0.24.4",
"private": true,
"license": "Apache-2.0",
"type": "module",
@@ -187,10 +187,10 @@
"ct3aMetadata": {
"initVersion": "7.25.2"
},
"packageManager": "pnpm@9.5.0",
"packageManager": "pnpm@9.12.0",
"engines": {
"node": "^20.16.0",
"pnpm": ">=9.5.0"
"pnpm": ">=9.12.0"
},
"lint-staged": {
"*": [
@@ -198,6 +198,8 @@
]
},
"commitlint": {
"extends": ["@commitlint/config-conventional"]
"extends": [
"@commitlint/config-conventional"
]
}
}

View File

@@ -5,7 +5,10 @@ import { myQueue } from "@/server/queues/queueSetup";
import { deploy } from "@/server/utils/deploy";
import {
IS_CLOUD,
checkUserRepositoryPermissions,
createPreviewDeployment,
createSecurityBlockedComment,
findGithubById,
findPreviewDeploymentByApplicationId,
findPreviewDeploymentsByPullRequestId,
removePreviewDeployment,
@@ -346,6 +349,18 @@ 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(
@@ -361,13 +376,72 @@ 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;
for (const app of apps) {
// 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) {
const previewLimit = app?.previewLimit || 0;
if (app?.previewDeployments?.length > previewLimit) {
continue;

View File

@@ -37,7 +37,6 @@ 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";

View File

@@ -33,7 +33,6 @@ 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";

View File

@@ -28,6 +28,8 @@ 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";
@@ -35,8 +37,6 @@ 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.

View File

@@ -20,8 +20,8 @@ import {
} from "@dokploy/server";
import { TRPCError } from "@trpc/server";
import { desc, eq } from "drizzle-orm";
import { createTRPCRouter, protectedProcedure } from "../trpc";
import { z } from "zod";
import { createTRPCRouter, protectedProcedure } from "../trpc";
export const deploymentRouter = createTRPCRouter({
all: protectedProcedure

View File

@@ -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.\-_]+$/;

View File

@@ -12,8 +12,8 @@ import {
getServiceContainer,
updateMount,
} from "@dokploy/server";
import { createTRPCRouter, protectedProcedure } from "../trpc";
import { z } from "zod";
import { createTRPCRouter, protectedProcedure } from "../trpc";
export const mountRouter = createTRPCRouter({
create: protectedProcedure

View File

@@ -361,6 +361,7 @@ export const projectRouter = createTRPCRouter({
previewDeployments,
mounts,
appName,
refreshToken,
...application
} = await findApplicationById(id);
const newAppName = appName.substring(
@@ -603,8 +604,14 @@ export const projectRouter = createTRPCRouter({
break;
}
case "compose": {
const { composeId, mounts, domains, appName, ...compose } =
await findComposeById(id);
const {
composeId,
mounts,
domains,
appName,
refreshToken,
...compose
} = await findComposeById(id);
const newAppName = appName.substring(
0,

View File

@@ -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({

View File

@@ -1,30 +1,30 @@
import { removeJob, schedule, updateJob } from "@/server/utils/backup";
import {
IS_CLOUD,
updateVolumeBackup,
removeVolumeBackup,
createVolumeBackup,
runVolumeBackup,
findVolumeBackupById,
restoreVolume,
scheduleVolumeBackup,
removeVolumeBackup,
removeVolumeBackupJob,
restoreVolume,
runVolumeBackup,
scheduleVolumeBackup,
updateVolumeBackup,
} 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

View File

@@ -5,6 +5,7 @@ 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";
@@ -12,7 +13,6 @@ import {
initializeNetwork,
initializeSwarm,
} from "@dokploy/server/setup/setup";
import { execAsync } from "@dokploy/server";
(async () => {
try {
setupDirectories();

View File

@@ -13,10 +13,7 @@ declare global {
baseDomain?: string;
};
chatwootSDK?: {
run: (config: {
websiteToken: string;
baseUrl: string;
}) => void;
run: (config: { websiteToken: string; baseUrl: string }) => void;
};
$chatwoot?: {
setUser: (

View File

@@ -29,5 +29,9 @@
"tsx": "^4.16.2",
"typescript": "^5.8.3"
},
"packageManager": "pnpm@9.5.0"
"packageManager": "pnpm@9.12.0",
"engines": {
"node": "^20.16.0",
"pnpm": ">=9.12.0"
}
}

View File

@@ -1,18 +1,17 @@
{
"$schema": "./node_modules/@biomejs/biome/configuration_schema.json",
"files": {
"ignore": [
"node_modules/**",
".next/**",
"drizzle/**",
".docker",
"dist",
"packages/server/package.json"
"includes": [
"**",
"!**/.docker",
"!**/.next/**",
"!**/dist",
"!**/drizzle/**",
"!node_modules/**",
"!packages/server/package.json"
]
},
"organizeImports": {
"enabled": true
},
"assist": { "actions": { "source": { "organizeImports": "on" } } },
"linter": {
"rules": {
"security": {
@@ -30,7 +29,17 @@
"noUnusedVariables": "error"
},
"style": {
"noNonNullAssertion": "off"
"noNonNullAssertion": "off",
"noParameterAssign": "error",
"useAsConstAssertion": "error",
"useDefaultParameterLast": "error",
"useEnumInitializers": "error",
"useSelfClosingElements": "error",
"useSingleVarDeclarator": "error",
"noUnusedTemplateLiteral": "error",
"useNumberNamespace": "error",
"noInferrableTypes": "error",
"noUselessElse": "error"
},
"suspicious": {
"noArrayIndexKey": "off",

View File

@@ -1,7 +1,10 @@
{
"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",
@@ -20,7 +23,7 @@
"format-and-lint:fix": "biome check . --write"
},
"devDependencies": {
"@biomejs/biome": "1.9.4",
"@biomejs/biome": "2.1.1",
"@commitlint/cli": "^19.8.1",
"@commitlint/config-conventional": "^19.8.1",
"@types/node": "^18.19.104",
@@ -30,10 +33,10 @@
"lint-staged": "^15.5.2",
"tsx": "4.16.2"
},
"packageManager": "pnpm@9.5.0",
"packageManager": "pnpm@9.12.0",
"engines": {
"node": "^20.16.0",
"pnpm": ">=9.5.0"
"pnpm": ">=9.12.0"
},
"lint-staged": {
"*": [
@@ -41,7 +44,9 @@
]
},
"commitlint": {
"extends": ["@commitlint/config-conventional"]
"extends": [
"@commitlint/config-conventional"
]
},
"resolutions": {
"@types/react": "18.3.5",

View File

@@ -105,5 +105,10 @@
"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"
}
}
}

View File

@@ -131,6 +131,10 @@ 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"),
@@ -428,6 +432,7 @@ 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(),
});

View File

@@ -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",

View File

@@ -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")

View File

@@ -1,3 +1,4 @@
import { paths } from "@dokploy/server/constants";
import { relations } from "drizzle-orm";
import {
boolean,
@@ -15,7 +16,6 @@ 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.

View File

@@ -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 { serviceType } from "./mount";
import { applications } from "./application";
import { mongo } from "./mongo";
import { mysql } from "./mysql";
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 { destinations } from "./destination";
import { mariadb } from "./mariadb";
import { mongo } from "./mongo";
import { serviceType } from "./mount";
import { mysql } from "./mysql";
import { postgres } from "./postgres";
import { redis } from "./redis";
import { generateAppName } from "./utils";
export const volumeBackups = pgTable("volume_backup", {

View File

@@ -45,6 +45,7 @@ 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";

View File

@@ -298,11 +298,7 @@ export const validateRequest = async (request: IncomingMessage) => {
const mockSession = {
session: {
user: {
id: apiKeyRecord.user.id,
email: apiKeyRecord.user.email,
name: apiKeyRecord.user.name,
},
userId: apiKeyRecord.user.id,
activeOrganizationId: organizationId || "",
},
user: {

View File

@@ -31,8 +31,8 @@ import {
findPreviewDeploymentById,
updatePreviewDeployment,
} from "./preview-deployment";
import { findScheduleById } from "./schedule";
import { removeRollbackById } from "./rollbacks";
import { findScheduleById } from "./schedule";
import { findVolumeBackupById } from "./volume-backups";
export type Deployment = typeof deployments.$inferSelect;

View File

@@ -192,3 +192,156 @@ 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;
}
};

View File

@@ -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,
rollbacks,
deployments as deploymentsSchema,
rollbacks,
} from "../db/schema";
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 { type ApplicationNested, getAuthConfig } from "../utils/builders";
import {
prepareEnvironmentVariables,
calculateResources,
generateBindMounts,
generateConfigContainer,
generateVolumeMounts,
prepareEnvironmentVariables,
} from "../utils/docker/utils";
import type { Project } from "./project";
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 { Mount } from "./mount";
import type { Port } from "./port";
import type { Project } from "./project";
export const createRollback = async (
input: z.infer<typeof createRollbackSchema>,

View File

@@ -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({

View File

@@ -191,6 +191,9 @@ export const createDefaultServerTraefikConfig = () => {
export const getDefaultTraefikConfig = () => {
const configObject: MainTraefikConfig = {
global: {
sendAnonymousUsage: false,
},
providers: {
...(process.env.NODE_ENV === "development"
? {

View File

@@ -1,8 +1,8 @@
import { findComposeById } from "@dokploy/server/services/compose";
import { dump, load } from "js-yaml";
import { addAppNameToAllServiceNames } from "./collision/root-network";
import { addSuffixToAllVolumes } from "./compose/volume";
import { generateRandomHash } from "./compose";
import { addSuffixToAllVolumes } from "./compose/volume";
import type { ComposeSpecification } from "./types";
export const addAppNameToPreventCollision = (

View File

@@ -45,6 +45,49 @@ 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 &&

View File

@@ -1,6 +1,6 @@
import fs, { writeFileSync } from "node:fs";
import path from "node:path";
import { createReadStream } from "node:fs";
import path from "node:path";
import { createInterface } from "node:readline";
import { paths } from "@dokploy/server/constants";
import type { Domain } from "@dokploy/server/services/domain";
@@ -237,7 +237,6 @@ 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);
}

View File

@@ -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) {

View File

@@ -1,9 +1,9 @@
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";
import type { findVolumeBackupById } from "@dokploy/server/services/volume-backups";
import { normalizeS3Path } from "../backups/utils";
import { getS3Credentials } from "../backups/utils";
export const backupVolume = async (
volumeBackup: Awaited<ReturnType<typeof findVolumeBackupById>>,

View File

@@ -1,3 +1,4 @@
import path from "node:path";
import {
findApplicationById,
findComposeById,
@@ -5,7 +6,6 @@ import {
getS3Credentials,
paths,
} from "../..";
import path from "node:path";
export const restoreVolume = async (
id: string,

View File

@@ -1,4 +1,5 @@
import { findVolumeBackupById } from "@dokploy/server/services/volume-backups";
import { scheduleJob, scheduledJobs } from "node-schedule";
import {
createDeploymentVolumeBackup,
execAsync,
@@ -6,7 +7,6 @@ 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
View File

@@ -13,8 +13,8 @@ importers:
.:
devDependencies:
'@biomejs/biome':
specifier: 1.9.4
version: 1.9.4
specifier: 2.1.1
version: 2.1.1
'@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@1.9.4':
resolution: {integrity: sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==}
'@biomejs/biome@2.1.1':
resolution: {integrity: sha512-HFGYkxG714KzG+8tvtXCJ1t1qXQMzgWzfvQaUjxN6UeKv+KvMEuliInnbZLJm6DXFXwqVi6446EGI0sGBLIYng==}
engines: {node: '>=14.21.3'}
hasBin: true
'@biomejs/cli-darwin-arm64@1.9.4':
resolution: {integrity: sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw==}
'@biomejs/cli-darwin-arm64@2.1.1':
resolution: {integrity: sha512-2Muinu5ok4tWxq4nu5l19el48cwCY/vzvI7Vjbkf3CYIQkjxZLyj0Ad37Jv2OtlXYaLvv+Sfu1hFeXt/JwRRXQ==}
engines: {node: '>=14.21.3'}
cpu: [arm64]
os: [darwin]
'@biomejs/cli-darwin-x64@1.9.4':
resolution: {integrity: sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg==}
'@biomejs/cli-darwin-x64@2.1.1':
resolution: {integrity: sha512-cC8HM5lrgKQXLAK+6Iz2FrYW5A62pAAX6KAnRlEyLb+Q3+Kr6ur/sSuoIacqlp1yvmjHJqjYfZjPvHWnqxoEIA==}
engines: {node: '>=14.21.3'}
cpu: [x64]
os: [darwin]
'@biomejs/cli-linux-arm64-musl@1.9.4':
resolution: {integrity: sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA==}
'@biomejs/cli-linux-arm64-musl@2.1.1':
resolution: {integrity: sha512-/7FBLnTswu4jgV9ttI3AMIdDGqVEPIZd8I5u2D4tfCoj8rl9dnjrEQbAIDlWhUXdyWlFSz8JypH3swU9h9P+2A==}
engines: {node: '>=14.21.3'}
cpu: [arm64]
os: [linux]
'@biomejs/cli-linux-arm64@1.9.4':
resolution: {integrity: sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g==}
'@biomejs/cli-linux-arm64@2.1.1':
resolution: {integrity: sha512-tw4BEbhAUkWPe4WBr6IX04DJo+2jz5qpPzpW/SWvqMjb9QuHY8+J0M23V8EPY/zWU4IG8Ui0XESapR1CB49Q7g==}
engines: {node: '>=14.21.3'}
cpu: [arm64]
os: [linux]
'@biomejs/cli-linux-x64-musl@1.9.4':
resolution: {integrity: sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg==}
'@biomejs/cli-linux-x64-musl@2.1.1':
resolution: {integrity: sha512-kUu+loNI3OCD2c12cUt7M5yaaSjDnGIksZwKnueubX6c/HWUyi/0mPbTBHR49Me3F0KKjWiKM+ZOjsmC+lUt9g==}
engines: {node: '>=14.21.3'}
cpu: [x64]
os: [linux]
'@biomejs/cli-linux-x64@1.9.4':
resolution: {integrity: sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg==}
'@biomejs/cli-linux-x64@2.1.1':
resolution: {integrity: sha512-3WJ1GKjU7NzZb6RTbwLB59v9cTIlzjbiFLDB0z4376TkDqoNYilJaC37IomCr/aXwuU8QKkrYoHrgpSq5ffJ4Q==}
engines: {node: '>=14.21.3'}
cpu: [x64]
os: [linux]
'@biomejs/cli-win32-arm64@1.9.4':
resolution: {integrity: sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg==}
'@biomejs/cli-win32-arm64@2.1.1':
resolution: {integrity: sha512-vEHK0v0oW+E6RUWLoxb2isI3rZo57OX9ZNyyGH701fZPj6Il0Rn1f5DMNyCmyflMwTnIQstEbs7n2BxYSqQx4Q==}
engines: {node: '>=14.21.3'}
cpu: [arm64]
os: [win32]
'@biomejs/cli-win32-x64@1.9.4':
resolution: {integrity: sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA==}
'@biomejs/cli-win32-x64@2.1.1':
resolution: {integrity: sha512-i2PKdn70kY++KEF/zkQFvQfX1e8SkA8hq4BgC+yE9dZqyLzB/XStY2MvwI3qswlRgnGpgncgqe0QYKVS1blksg==}
engines: {node: '>=14.21.3'}
cpu: [x64]
os: [win32]
@@ -7281,39 +7281,39 @@ snapshots:
'@better-fetch/fetch@1.1.18': {}
'@biomejs/biome@1.9.4':
'@biomejs/biome@2.1.1':
optionalDependencies:
'@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-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-arm64@2.1.1':
optional: true
'@biomejs/cli-darwin-x64@1.9.4':
'@biomejs/cli-darwin-x64@2.1.1':
optional: true
'@biomejs/cli-linux-arm64-musl@1.9.4':
'@biomejs/cli-linux-arm64-musl@2.1.1':
optional: true
'@biomejs/cli-linux-arm64@1.9.4':
'@biomejs/cli-linux-arm64@2.1.1':
optional: true
'@biomejs/cli-linux-x64-musl@1.9.4':
'@biomejs/cli-linux-x64-musl@2.1.1':
optional: true
'@biomejs/cli-linux-x64@1.9.4':
'@biomejs/cli-linux-x64@2.1.1':
optional: true
'@biomejs/cli-win32-arm64@1.9.4':
'@biomejs/cli-win32-arm64@2.1.1':
optional: true
'@biomejs/cli-win32-x64@1.9.4':
'@biomejs/cli-win32-x64@2.1.1':
optional: true
'@codemirror/autocomplete@6.18.6':

View File

@@ -1,6 +1,5 @@
packages:
- "apps/api"
- "apps/dokploy"
- "apps/monitoring"
- "apps/schedules"
- "packages/server"