mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-07-19 12:55:26 +02:00
Compare commits
1 Commits
v0.29.9
...
fix/projec
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b354c3cb0f |
@@ -1,50 +0,0 @@
|
|||||||
import { redactRcloneCredentials } from "@dokploy/server/utils/backups/redact";
|
|
||||||
import { describe, expect, it } from "vitest";
|
|
||||||
|
|
||||||
describe("redactRcloneCredentials (#4621)", () => {
|
|
||||||
it("should redact access key in rclone command", () => {
|
|
||||||
const cmd =
|
|
||||||
'rclone rcat --s3-access-key-id="AKIAIOSFODNN7EXAMPLE" --s3-secret-access-key="wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" :s3:bucket/file.gz';
|
|
||||||
const redacted = redactRcloneCredentials(cmd);
|
|
||||||
expect(redacted).not.toContain("AKIAIOSFODNN7EXAMPLE");
|
|
||||||
expect(redacted).toContain('--s3-access-key-id="[REDACTED]"');
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should redact secret access key in rclone command", () => {
|
|
||||||
const cmd =
|
|
||||||
'rclone rcat --s3-access-key-id="key" --s3-secret-access-key="supersecret" :s3:bucket/file.gz';
|
|
||||||
const redacted = redactRcloneCredentials(cmd);
|
|
||||||
expect(redacted).not.toContain("supersecret");
|
|
||||||
expect(redacted).toContain('--s3-secret-access-key="[REDACTED]"');
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should redact both credentials simultaneously", () => {
|
|
||||||
const cmd =
|
|
||||||
'rclone lsf --s3-access-key-id="AKIA123" --s3-secret-access-key="secret456" --s3-region="us-east-1" :s3:bucket/';
|
|
||||||
const redacted = redactRcloneCredentials(cmd);
|
|
||||||
expect(redacted).not.toContain("AKIA123");
|
|
||||||
expect(redacted).not.toContain("secret456");
|
|
||||||
expect(redacted).toContain('--s3-region="us-east-1"');
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should not modify non-credential flags", () => {
|
|
||||||
const cmd =
|
|
||||||
'rclone rcat --s3-region="eu-west-1" --s3-endpoint="https://s3.example.com" --s3-no-check-bucket :s3:bucket/file.gz';
|
|
||||||
const redacted = redactRcloneCredentials(cmd);
|
|
||||||
expect(redacted).toBe(cmd);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should handle commands with no credentials", () => {
|
|
||||||
const cmd = "rclone lsf :s3:bucket/";
|
|
||||||
expect(redactRcloneCredentials(cmd)).toBe(cmd);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should handle error strings containing credentials", () => {
|
|
||||||
const errorStr =
|
|
||||||
'Error: Command failed: rclone lsf --s3-access-key-id="MYKEY" --s3-secret-access-key="MYSECRET" :s3:bucket/';
|
|
||||||
const redacted = redactRcloneCredentials(errorStr);
|
|
||||||
expect(redacted).not.toContain("MYKEY");
|
|
||||||
expect(redacted).not.toContain("MYSECRET");
|
|
||||||
expect(redacted).toContain("[REDACTED]");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -28,7 +28,7 @@ export const ShowInternalLibsqlCredentials = ({ libsqlId }: Props) => {
|
|||||||
<div className="grid w-full md:grid-cols-2 gap-4 md:gap-8">
|
<div className="grid w-full md:grid-cols-2 gap-4 md:gap-8">
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<Label>User</Label>
|
<Label>User</Label>
|
||||||
<Input enableCopyButton disabled value={data?.databaseUser} />
|
<Input disabled value={data?.databaseUser} />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<Label>Sqld Node</Label>
|
<Label>Sqld Node</Label>
|
||||||
@@ -71,7 +71,7 @@ export const ShowInternalLibsqlCredentials = ({ libsqlId }: Props) => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<Label>Internal Host</Label>
|
<Label>Internal Host</Label>
|
||||||
<Input enableCopyButton disabled value={data?.appName} />
|
<Input disabled value={data?.appName} />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<Label>Enable Namespaces</Label>
|
<Label>Enable Namespaces</Label>
|
||||||
|
|||||||
@@ -25,11 +25,11 @@ export const ShowInternalMariadbCredentials = ({ mariadbId }: Props) => {
|
|||||||
<div className="grid w-full md:grid-cols-2 gap-4 md:gap-8">
|
<div className="grid w-full md:grid-cols-2 gap-4 md:gap-8">
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<Label>User</Label>
|
<Label>User</Label>
|
||||||
<Input enableCopyButton disabled value={data?.databaseUser} />
|
<Input disabled value={data?.databaseUser} />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<Label>Database Name</Label>
|
<Label>Database Name</Label>
|
||||||
<Input enableCopyButton disabled value={data?.databaseName} />
|
<Input disabled value={data?.databaseName} />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<Label>Password</Label>
|
<Label>Password</Label>
|
||||||
@@ -79,7 +79,7 @@ export const ShowInternalMariadbCredentials = ({ mariadbId }: Props) => {
|
|||||||
|
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<Label>Internal Host</Label>
|
<Label>Internal Host</Label>
|
||||||
<Input enableCopyButton disabled value={data?.appName} />
|
<Input disabled value={data?.appName} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col gap-2 md:col-span-2">
|
<div className="flex flex-col gap-2 md:col-span-2">
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export const ShowInternalMongoCredentials = ({ mongoId }: Props) => {
|
|||||||
<div className="grid w-full md:grid-cols-2 gap-4 md:gap-8">
|
<div className="grid w-full md:grid-cols-2 gap-4 md:gap-8">
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<Label>User</Label>
|
<Label>User</Label>
|
||||||
<Input enableCopyButton disabled value={data?.databaseUser} />
|
<Input disabled value={data?.databaseUser} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
@@ -55,7 +55,7 @@ export const ShowInternalMongoCredentials = ({ mongoId }: Props) => {
|
|||||||
|
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<Label>Internal Host</Label>
|
<Label>Internal Host</Label>
|
||||||
<Input enableCopyButton disabled value={data?.appName} />
|
<Input disabled value={data?.appName} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col gap-2 md:col-span-2">
|
<div className="flex flex-col gap-2 md:col-span-2">
|
||||||
|
|||||||
@@ -25,11 +25,11 @@ export const ShowInternalMysqlCredentials = ({ mysqlId }: Props) => {
|
|||||||
<div className="grid w-full md:grid-cols-2 gap-4 md:gap-8">
|
<div className="grid w-full md:grid-cols-2 gap-4 md:gap-8">
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<Label>User</Label>
|
<Label>User</Label>
|
||||||
<Input enableCopyButton disabled value={data?.databaseUser} />
|
<Input disabled value={data?.databaseUser} />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<Label>Database Name</Label>
|
<Label>Database Name</Label>
|
||||||
<Input enableCopyButton disabled value={data?.databaseName} />
|
<Input disabled value={data?.databaseName} />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<Label>Password</Label>
|
<Label>Password</Label>
|
||||||
@@ -79,7 +79,7 @@ export const ShowInternalMysqlCredentials = ({ mysqlId }: Props) => {
|
|||||||
|
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<Label>Internal Host</Label>
|
<Label>Internal Host</Label>
|
||||||
<Input enableCopyButton disabled value={data?.appName} />
|
<Input disabled value={data?.appName} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col gap-2 md:col-span-2">
|
<div className="flex flex-col gap-2 md:col-span-2">
|
||||||
|
|||||||
@@ -25,11 +25,11 @@ export const ShowInternalPostgresCredentials = ({ postgresId }: Props) => {
|
|||||||
<div className="grid w-full md:grid-cols-2 gap-4 md:gap-8">
|
<div className="grid w-full md:grid-cols-2 gap-4 md:gap-8">
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<Label>User</Label>
|
<Label>User</Label>
|
||||||
<Input enableCopyButton disabled value={data?.databaseUser} />
|
<Input disabled value={data?.databaseUser} />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<Label>Database Name</Label>
|
<Label>Database Name</Label>
|
||||||
<Input enableCopyButton disabled value={data?.databaseName} />
|
<Input disabled value={data?.databaseName} />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<Label>Password</Label>
|
<Label>Password</Label>
|
||||||
@@ -57,7 +57,7 @@ export const ShowInternalPostgresCredentials = ({ postgresId }: Props) => {
|
|||||||
|
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<Label>Internal Host</Label>
|
<Label>Internal Host</Label>
|
||||||
<Input enableCopyButton disabled value={data?.appName} />
|
<Input disabled value={data?.appName} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ const dockerImageDefaultPlaceholder: Record<DbType, string> = {
|
|||||||
mariadb: "mariadb:11",
|
mariadb: "mariadb:11",
|
||||||
mysql: "mysql:8",
|
mysql: "mysql:8",
|
||||||
postgres: "postgres:18",
|
postgres: "postgres:18",
|
||||||
redis: "redis:8",
|
redis: "redis:7",
|
||||||
};
|
};
|
||||||
|
|
||||||
const databasesUserDefaultPlaceholder: Record<
|
const databasesUserDefaultPlaceholder: Record<
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ interface Details {
|
|||||||
envVariables: EnvVariable[];
|
envVariables: EnvVariable[];
|
||||||
shortDescription: string;
|
shortDescription: string;
|
||||||
domains: Domain[];
|
domains: Domain[];
|
||||||
configFiles?: Mount[] | null;
|
configFiles?: Mount[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Mount {
|
interface Mount {
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export const ShowInternalRedisCredentials = ({ redisId }: Props) => {
|
|||||||
<div className="grid w-full md:grid-cols-2 gap-4 md:gap-8">
|
<div className="grid w-full md:grid-cols-2 gap-4 md:gap-8">
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<Label>User</Label>
|
<Label>User</Label>
|
||||||
<Input enableCopyButton disabled value="default" />
|
<Input disabled value="default" />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<Label>Password</Label>
|
<Label>Password</Label>
|
||||||
@@ -53,7 +53,7 @@ export const ShowInternalRedisCredentials = ({ redisId }: Props) => {
|
|||||||
|
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<Label>Internal Host</Label>
|
<Label>Internal Host</Label>
|
||||||
<Input enableCopyButton disabled value={data?.appName} />
|
<Input disabled value={data?.appName} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col gap-2 md:col-span-2">
|
<div className="flex flex-col gap-2 md:col-span-2">
|
||||||
|
|||||||
@@ -131,10 +131,7 @@ export const HandleAi = ({ aiId }: Props) => {
|
|||||||
const apiUrl = form.watch("apiUrl");
|
const apiUrl = form.watch("apiUrl");
|
||||||
const apiKey = form.watch("apiKey");
|
const apiKey = form.watch("apiKey");
|
||||||
|
|
||||||
// Any Ollama instance on the default port 11434 is treated as no-auth
|
const isOllama = apiUrl.includes(":11434") || apiUrl.includes("ollama");
|
||||||
// (covers localhost and self-hosted LAN deployments). Ollama Cloud
|
|
||||||
// (ollama.com on 443) falls through and requires an API key.
|
|
||||||
const isLocalOllama = apiUrl.includes(":11434");
|
|
||||||
const {
|
const {
|
||||||
data: models,
|
data: models,
|
||||||
isFetching: isLoadingServerModels,
|
isFetching: isLoadingServerModels,
|
||||||
@@ -145,7 +142,7 @@ export const HandleAi = ({ aiId }: Props) => {
|
|||||||
apiKey: apiKey ?? "",
|
apiKey: apiKey ?? "",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
enabled: !!apiUrl && (isLocalOllama || !!apiKey),
|
enabled: !!apiUrl && (isOllama || !!apiKey),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -278,7 +275,7 @@ export const HandleAi = ({ aiId }: Props) => {
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{!isLocalOllama && (
|
{!isOllama && (
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="apiKey"
|
name="apiKey"
|
||||||
|
|||||||
@@ -1,17 +1,13 @@
|
|||||||
import copy from "copy-to-clipboard";
|
import { EyeIcon, EyeOffIcon, RefreshCcw } from "lucide-react";
|
||||||
import { Clipboard, EyeIcon, EyeOffIcon, RefreshCcw } from "lucide-react";
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { toast } from "sonner";
|
|
||||||
|
|
||||||
import { generateRandomPassword } from "@/lib/password-utils";
|
import { generateRandomPassword } from "@/lib/password-utils";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { Button } from "./button";
|
|
||||||
|
|
||||||
export interface InputProps extends React.ComponentProps<"input"> {
|
export interface InputProps extends React.ComponentProps<"input"> {
|
||||||
errorMessage?: string;
|
errorMessage?: string;
|
||||||
enablePasswordGenerator?: boolean;
|
enablePasswordGenerator?: boolean;
|
||||||
passwordGeneratorLength?: number;
|
passwordGeneratorLength?: number;
|
||||||
enableCopyButton?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function Input({
|
function Input({
|
||||||
@@ -20,7 +16,6 @@ function Input({
|
|||||||
errorMessage,
|
errorMessage,
|
||||||
enablePasswordGenerator = false,
|
enablePasswordGenerator = false,
|
||||||
passwordGeneratorLength,
|
passwordGeneratorLength,
|
||||||
enableCopyButton = false,
|
|
||||||
ref,
|
ref,
|
||||||
...props
|
...props
|
||||||
}: InputProps) {
|
}: InputProps) {
|
||||||
@@ -70,12 +65,8 @@ function Input({
|
|||||||
input.dispatchEvent(new Event("input", { bubbles: true }));
|
input.dispatchEvent(new Event("input", { bubbles: true }));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCopy = () => {
|
return (
|
||||||
copy(inputRef.current?.value || "");
|
<>
|
||||||
toast.success("Value is copied to clipboard");
|
|
||||||
};
|
|
||||||
|
|
||||||
const inputElement = (
|
|
||||||
<div className="relative w-full">
|
<div className="relative w-full">
|
||||||
<input
|
<input
|
||||||
type={inputType}
|
type={inputType}
|
||||||
@@ -117,20 +108,6 @@ function Input({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{enableCopyButton ? (
|
|
||||||
<div className="flex w-full items-center space-x-2">
|
|
||||||
{inputElement}
|
|
||||||
<Button type="button" variant={"secondary"} onClick={handleCopy}>
|
|
||||||
<Clipboard className="size-4 text-muted-foreground" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
inputElement
|
|
||||||
)}
|
|
||||||
{errorMessage && (
|
{errorMessage && (
|
||||||
<span className="text-sm text-red-600 text-secondary-foreground">
|
<span className="text-sm text-red-600 text-secondary-foreground">
|
||||||
{errorMessage}
|
{errorMessage}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "dokploy",
|
"name": "dokploy",
|
||||||
"version": "v0.29.9",
|
"version": "v0.29.8",
|
||||||
"private": true,
|
"private": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -54,20 +54,6 @@ const Page = ({ isCloud }: Props) => {
|
|||||||
</EnterpriseFeatureGate>
|
</EnterpriseFeatureGate>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
<Card className="h-full bg-sidebar p-2.5 rounded-xl mx-auto w-full">
|
|
||||||
<div className="rounded-xl bg-background shadow-md">
|
|
||||||
<EnterpriseFeatureGate
|
|
||||||
lockedProps={{
|
|
||||||
title: "Application Authentication",
|
|
||||||
description:
|
|
||||||
"Protect deployed applications behind an OIDC SSO gate (oauth2-proxy). Part of Dokploy Enterprise.",
|
|
||||||
ctaLabel: "Go to License",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ForwardAuthServers />
|
|
||||||
</EnterpriseFeatureGate>
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
{!isCloud && (
|
{!isCloud && (
|
||||||
<Card className="h-full bg-sidebar p-2.5 rounded-xl mx-auto w-full">
|
<Card className="h-full bg-sidebar p-2.5 rounded-xl mx-auto w-full">
|
||||||
<div className="rounded-xl bg-background shadow-md">
|
<div className="rounded-xl bg-background shadow-md">
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import {
|
|||||||
findAllDeploymentsByServerId,
|
findAllDeploymentsByServerId,
|
||||||
findAllDeploymentsCentralized,
|
findAllDeploymentsCentralized,
|
||||||
findDeploymentById,
|
findDeploymentById,
|
||||||
findScheduleById,
|
|
||||||
IS_CLOUD,
|
IS_CLOUD,
|
||||||
removeDeployment,
|
removeDeployment,
|
||||||
resolveServicePath,
|
resolveServicePath,
|
||||||
@@ -127,29 +126,9 @@ export const deploymentRouter = createTRPCRouter({
|
|||||||
allByType: protectedProcedure
|
allByType: protectedProcedure
|
||||||
.input(apiFindAllByType)
|
.input(apiFindAllByType)
|
||||||
.query(async ({ input, ctx }) => {
|
.query(async ({ input, ctx }) => {
|
||||||
if (input.type === "schedule") {
|
|
||||||
const schedule = await findScheduleById(input.id);
|
|
||||||
const serviceId = schedule.applicationId || schedule.composeId;
|
|
||||||
if (serviceId) {
|
|
||||||
await checkServicePermissionAndAccess(ctx, serviceId, {
|
|
||||||
deployment: ["read"],
|
|
||||||
});
|
|
||||||
} else if (schedule.serverId) {
|
|
||||||
const targetServer = await findServerById(schedule.serverId);
|
|
||||||
if (
|
|
||||||
targetServer.organizationId !== ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You don't have access to this schedule.",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
await checkServicePermissionAndAccess(ctx, input.id, {
|
await checkServicePermissionAndAccess(ctx, input.id, {
|
||||||
deployment: ["read"],
|
deployment: ["read"],
|
||||||
});
|
});
|
||||||
}
|
|
||||||
const deploymentsList = await db.query.deployments.findMany({
|
const deploymentsList = await db.query.deployments.findMany({
|
||||||
where: eq(deployments[`${input.type}Id`], input.id),
|
where: eq(deployments[`${input.type}Id`], input.id),
|
||||||
orderBy: desc(deployments.createdAt),
|
orderBy: desc(deployments.createdAt),
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ interface DockerOutput {
|
|||||||
dockerCompose: string;
|
dockerCompose: string;
|
||||||
envVariables: Array<{ name: string; value: string }>;
|
envVariables: Array<{ name: string; value: string }>;
|
||||||
domains: Array<{ host: string; port: number; serviceName: string }>;
|
domains: Array<{ host: string; port: number; serviceName: string }>;
|
||||||
configFiles?: Array<{ content: string; filePath: string }> | null;
|
configFiles?: Array<{ content: string; filePath: string }>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getAiSettingsByOrganizationId = async (organizationId: string) => {
|
export const getAiSettingsByOrganizationId = async (organizationId: string) => {
|
||||||
@@ -136,7 +136,7 @@ export const suggestVariants = async ({
|
|||||||
filePath: z.string(),
|
filePath: z.string(),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.nullable(),
|
.optional(),
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
@@ -198,12 +198,12 @@ export const suggestVariants = async ({
|
|||||||
1. ALWAYS use 'image:' field, NEVER use 'build:' field
|
1. ALWAYS use 'image:' field, NEVER use 'build:' field
|
||||||
2. NEVER use 'build: .' or any build directive - we don't have local Dockerfiles
|
2. NEVER use 'build: .' or any build directive - we don't have local Dockerfiles
|
||||||
3. Use images from Docker Hub or other public registries (e.g., docker.io, ghcr.io, quay.io)
|
3. Use images from Docker Hub or other public registries (e.g., docker.io, ghcr.io, quay.io)
|
||||||
4. For dependencies (databases, redis, etc.), use official images (e.g., postgres:16, redis:8, etc.)
|
4. For dependencies (databases, redis, etc.), use official images (e.g., postgres:16, redis:7, etc.)
|
||||||
5. Always specify image tags - avoid using 'latest' tag, use specific versions when possible
|
5. Always specify image tags - avoid using 'latest' tag, use specific versions when possible
|
||||||
6. Examples of correct image usage:
|
6. Examples of correct image usage:
|
||||||
- image: sendingtk/chatwoot:develop
|
- image: sendingtk/chatwoot:develop
|
||||||
- image: postgres:16-alpine
|
- image: postgres:16-alpine
|
||||||
- image: redis:8-alpine
|
- image: redis:7-alpine
|
||||||
7. Examples of INCORRECT usage (DO NOT USE):
|
7. Examples of INCORRECT usage (DO NOT USE):
|
||||||
- build: .
|
- build: .
|
||||||
- build: ./app
|
- build: ./app
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { docker } from "../constants";
|
|||||||
import { pullImage } from "../utils/docker/utils";
|
import { pullImage } from "../utils/docker/utils";
|
||||||
|
|
||||||
export const initializeRedis = async () => {
|
export const initializeRedis = async () => {
|
||||||
const imageName = "redis:8";
|
const imageName = "redis:7";
|
||||||
const containerName = "dokploy-redis";
|
const containerName = "dokploy-redis";
|
||||||
|
|
||||||
const settings: CreateServiceOptions = {
|
const settings: CreateServiceOptions = {
|
||||||
|
|||||||
@@ -74,10 +74,8 @@ export function selectAIProvider(config: { apiUrl: string; apiKey: string }) {
|
|||||||
});
|
});
|
||||||
case "ollama":
|
case "ollama":
|
||||||
return createOllama({
|
return createOllama({
|
||||||
|
// optional settings, e.g.
|
||||||
baseURL: config.apiUrl,
|
baseURL: config.apiUrl,
|
||||||
headers: config.apiKey
|
|
||||||
? { Authorization: `Bearer ${config.apiKey}` }
|
|
||||||
: undefined,
|
|
||||||
});
|
});
|
||||||
case "deepinfra":
|
case "deepinfra":
|
||||||
return createDeepInfra({
|
return createDeepInfra({
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import { startLogCleanup } from "../access-log/handler";
|
|||||||
import { cleanupAll } from "../docker/utils";
|
import { cleanupAll } from "../docker/utils";
|
||||||
import { sendDockerCleanupNotifications } from "../notifications/docker-cleanup";
|
import { sendDockerCleanupNotifications } from "../notifications/docker-cleanup";
|
||||||
import { execAsync, execAsyncRemote } from "../process/execAsync";
|
import { execAsync, execAsyncRemote } from "../process/execAsync";
|
||||||
import { redactRcloneCredentials } from "./redact";
|
|
||||||
import { getS3Credentials, normalizeS3Path, scheduleBackup } from "./utils";
|
import { getS3Credentials, normalizeS3Path, scheduleBackup } from "./utils";
|
||||||
|
|
||||||
export const initCronJobs = async () => {
|
export const initCronJobs = async () => {
|
||||||
@@ -154,6 +153,6 @@ export const keepLatestNBackups = async (
|
|||||||
await execAsync(rcloneCommand);
|
await execAsync(rcloneCommand);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(redactRcloneCredentials(String(error)));
|
console.error(error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
/**
|
|
||||||
* Redacts S3 credentials from rclone command strings.
|
|
||||||
*
|
|
||||||
* Used to prevent credential leakage in structured logs and error output.
|
|
||||||
* Matches the flag format produced by `getS3Credentials()`:
|
|
||||||
* --s3-access-key-id="VALUE" and --s3-secret-access-key="VALUE"
|
|
||||||
*/
|
|
||||||
export const redactRcloneCredentials = (command: string): string => {
|
|
||||||
return command
|
|
||||||
.replace(/(--s3-access-key-id=)"[^"]*"/g, '$1"[REDACTED]"')
|
|
||||||
.replace(/(--s3-secret-access-key=)"[^"]*"/g, '$1"[REDACTED]"');
|
|
||||||
};
|
|
||||||
@@ -9,7 +9,6 @@ import { runMariadbBackup } from "./mariadb";
|
|||||||
import { runMongoBackup } from "./mongo";
|
import { runMongoBackup } from "./mongo";
|
||||||
import { runMySqlBackup } from "./mysql";
|
import { runMySqlBackup } from "./mysql";
|
||||||
import { runPostgresBackup } from "./postgres";
|
import { runPostgresBackup } from "./postgres";
|
||||||
import { redactRcloneCredentials } from "./redact";
|
|
||||||
import { runWebServerBackup } from "./web-server";
|
import { runWebServerBackup } from "./web-server";
|
||||||
|
|
||||||
export const scheduleBackup = (backup: BackupSchedule) => {
|
export const scheduleBackup = (backup: BackupSchedule) => {
|
||||||
@@ -263,7 +262,7 @@ export const getBackupCommand = (
|
|||||||
{
|
{
|
||||||
containerSearch,
|
containerSearch,
|
||||||
backupCommand,
|
backupCommand,
|
||||||
rcloneCommand: redactRcloneCredentials(rcloneCommand),
|
rcloneCommand,
|
||||||
logPath,
|
logPath,
|
||||||
},
|
},
|
||||||
`Executing backup command: ${backup.databaseType} ${backup.backupType}`,
|
`Executing backup command: ${backup.databaseType} ${backup.backupType}`,
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import {
|
|||||||
import { findDestinationById } from "@dokploy/server/services/destination";
|
import { findDestinationById } from "@dokploy/server/services/destination";
|
||||||
import { sendDokployBackupNotifications } from "../notifications/dokploy-backup";
|
import { sendDokployBackupNotifications } from "../notifications/dokploy-backup";
|
||||||
import { execAsync } from "../process/execAsync";
|
import { execAsync } from "../process/execAsync";
|
||||||
import { redactRcloneCredentials } from "./redact";
|
|
||||||
import { getBackupTimestamp, getS3Credentials, normalizeS3Path } from "./utils";
|
import { getBackupTimestamp, getS3Credentials, normalizeS3Path } from "./utils";
|
||||||
|
|
||||||
function formatBytes(bytes?: number) {
|
function formatBytes(bytes?: number) {
|
||||||
@@ -114,23 +113,20 @@ export const runWebServerBackup = async (backup: BackupSchedule) => {
|
|||||||
try {
|
try {
|
||||||
await rm(tempDir, { recursive: true, force: true });
|
await rm(tempDir, { recursive: true, force: true });
|
||||||
} catch (cleanupError) {
|
} catch (cleanupError) {
|
||||||
console.error(
|
console.error("Cleanup error:", cleanupError);
|
||||||
"Cleanup error:",
|
|
||||||
redactRcloneCredentials(String(cleanupError)),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const safeErrorMessage = redactRcloneCredentials(
|
console.error("Backup error:", error);
|
||||||
error instanceof Error ? error.message : String(error),
|
|
||||||
);
|
|
||||||
console.error("Backup error:", redactRcloneCredentials(String(error)));
|
|
||||||
writeStream.write("Backup error❌\n");
|
writeStream.write("Backup error❌\n");
|
||||||
writeStream.write(`${safeErrorMessage}\n`);
|
writeStream.write(
|
||||||
|
error instanceof Error ? error.message : "Unknown error\n",
|
||||||
|
);
|
||||||
writeStream.end();
|
writeStream.end();
|
||||||
await sendDokployBackupNotifications({
|
await sendDokployBackupNotifications({
|
||||||
type: "error",
|
type: "error",
|
||||||
errorMessage: safeErrorMessage || "Error message not provided",
|
// @ts-ignore
|
||||||
|
errorMessage: error?.message || "Error message not provided",
|
||||||
backupSize: formatBytes(computedBackupSize),
|
backupSize: formatBytes(computedBackupSize),
|
||||||
});
|
});
|
||||||
await updateDeploymentStatus(deployment.deploymentId, "error");
|
await updateDeploymentStatus(deployment.deploymentId, "error");
|
||||||
|
|||||||
Reference in New Issue
Block a user