Compare commits

..

1 Commits

Author SHA1 Message Date
autofix-ci[bot]
04a10146ac [autofix.ci] apply automated fixes 2025-05-17 05:16:30 +00:00
24 changed files with 138 additions and 306 deletions

View File

@@ -49,7 +49,7 @@ RUN curl -fsSL https://get.docker.com -o get-docker.sh && sh get-docker.sh && rm
# Install Nixpacks and tsx # Install Nixpacks and tsx
# | VERBOSE=1 VERSION=1.21.0 bash # | VERBOSE=1 VERSION=1.21.0 bash
ARG NIXPACKS_VERSION=1.39.0 ARG NIXPACKS_VERSION=1.35.0
RUN curl -sSL https://nixpacks.com/install.sh -o install.sh \ RUN curl -sSL https://nixpacks.com/install.sh -o install.sh \
&& chmod +x install.sh \ && chmod +x install.sh \
&& ./install.sh \ && ./install.sh \
@@ -63,4 +63,4 @@ RUN curl -sSL https://railpack.com/install.sh | bash
COPY --from=buildpacksio/pack:0.35.0 /usr/local/bin/pack /usr/local/bin/pack COPY --from=buildpacksio/pack:0.35.0 /usr/local/bin/pack /usr/local/bin/pack
EXPOSE 3000 EXPOSE 3000
CMD [ "pnpm", "start" ] CMD [ "pnpm", "start" ]

View File

@@ -8,7 +8,7 @@ COPY . /usr/src/app
WORKDIR /usr/src/app WORKDIR /usr/src/app
RUN apt-get update && apt-get install -y python3 make g++ git git-lfs && git lfs install && rm -rf /var/lib/apt/lists/* RUN apt-get update && apt-get install -y python3 make g++ git && rm -rf /var/lib/apt/lists/*
# Install dependencies # Install dependencies
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile

View File

@@ -263,7 +263,7 @@ export const ShowImport = ({ composeId }: Props) => {
{templateInfo.template.envs.map((env, index) => ( {templateInfo.template.envs.map((env, index) => (
<div <div
key={index} key={index}
className="rounded-lg truncate border bg-card p-2 font-mono text-sm" className="rounded-lg border bg-card p-2 font-mono text-sm"
> >
{env} {env}
</div> </div>
@@ -328,7 +328,7 @@ export const ShowImport = ({ composeId }: Props) => {
<DialogDescription>Mount File Content</DialogDescription> <DialogDescription>Mount File Content</DialogDescription>
</DialogHeader> </DialogHeader>
<ScrollArea className="h-[45vh] pr-4"> <ScrollArea className="h-[25vh] pr-4">
<CodeEditor <CodeEditor
language="yaml" language="yaml"
value={selectedMount?.content || ""} value={selectedMount?.content || ""}

View File

@@ -136,7 +136,7 @@ export const SaveBitbucketProvider = ({ applicationId }: Props) => {
enableSubmodules: data.enableSubmodules || false, enableSubmodules: data.enableSubmodules || false,
}); });
} }
}, [form.reset, data?.applicationId, form]); }, [form.reset, data, form]);
const onSubmit = async (data: BitbucketProvider) => { const onSubmit = async (data: BitbucketProvider) => {
await mutateAsync({ await mutateAsync({

View File

@@ -53,7 +53,7 @@ export const SaveDockerProvider = ({ applicationId }: Props) => {
registryURL: data.registryUrl || "", registryURL: data.registryUrl || "",
}); });
} }
}, [form.reset, data?.applicationId, form]); }, [form.reset, data, form]);
const onSubmit = async (values: DockerProvider) => { const onSubmit = async (values: DockerProvider) => {
await mutateAsync({ await mutateAsync({

View File

@@ -158,7 +158,7 @@ export const SaveGiteaProvider = ({ applicationId }: Props) => {
enableSubmodules: data.enableSubmodules || false, enableSubmodules: data.enableSubmodules || false,
}); });
} }
}, [form.reset, data?.applicationId, form]); }, [form.reset, data, form]);
const onSubmit = async (data: GiteaProvider) => { const onSubmit = async (data: GiteaProvider) => {
await mutateAsync({ await mutateAsync({

View File

@@ -134,7 +134,7 @@ export const SaveGithubProvider = ({ applicationId }: Props) => {
enableSubmodules: data.enableSubmodules ?? false, enableSubmodules: data.enableSubmodules ?? false,
}); });
} }
}, [form.reset, data?.applicationId, form]); }, [form.reset, data, form]);
const onSubmit = async (data: GithubProvider) => { const onSubmit = async (data: GithubProvider) => {
await mutateAsync({ await mutateAsync({

View File

@@ -141,7 +141,7 @@ export const SaveGitlabProvider = ({ applicationId }: Props) => {
enableSubmodules: data.enableSubmodules ?? false, enableSubmodules: data.enableSubmodules ?? false,
}); });
} }
}, [form.reset, data?.applicationId, form]); }, [form.reset, data, form]);
const onSubmit = async (data: GitlabProvider) => { const onSubmit = async (data: GitlabProvider) => {
await mutateAsync({ await mutateAsync({

View File

@@ -136,7 +136,7 @@ export const SaveBitbucketProviderCompose = ({ composeId }: Props) => {
enableSubmodules: data.enableSubmodules ?? false, enableSubmodules: data.enableSubmodules ?? false,
}); });
} }
}, [form.reset, data?.composeId, form]); }, [form.reset, data, form]);
const onSubmit = async (data: BitbucketProvider) => { const onSubmit = async (data: BitbucketProvider) => {
await mutateAsync({ await mutateAsync({

View File

@@ -142,7 +142,7 @@ export const SaveGiteaProviderCompose = ({ composeId }: Props) => {
enableSubmodules: data.enableSubmodules ?? false, enableSubmodules: data.enableSubmodules ?? false,
}); });
} }
}, [form.reset, data?.composeId, form]); }, [form.reset, data, form]);
const onSubmit = async (data: GiteaProvider) => { const onSubmit = async (data: GiteaProvider) => {
await mutateAsync({ await mutateAsync({

View File

@@ -134,7 +134,7 @@ export const SaveGithubProviderCompose = ({ composeId }: Props) => {
enableSubmodules: data.enableSubmodules ?? false, enableSubmodules: data.enableSubmodules ?? false,
}); });
} }
}, [form.reset, data?.composeId, form]); }, [form.reset, data, form]);
const onSubmit = async (data: GithubProvider) => { const onSubmit = async (data: GithubProvider) => {
await mutateAsync({ await mutateAsync({

View File

@@ -142,7 +142,7 @@ export const SaveGitlabProviderCompose = ({ composeId }: Props) => {
enableSubmodules: data.enableSubmodules ?? false, enableSubmodules: data.enableSubmodules ?? false,
}); });
} }
}, [form.reset, data?.composeId, form]); }, [form.reset, data, form]);
const onSubmit = async (data: GitlabProvider) => { const onSubmit = async (data: GitlabProvider) => {
await mutateAsync({ await mutateAsync({

View File

@@ -71,8 +71,8 @@ export const IsolatedDeployment = ({ composeId }: Props) => {
isolatedDeployment: formData?.isolatedDeployment || false, isolatedDeployment: formData?.isolatedDeployment || false,
}) })
.then(async (_data) => { .then(async (_data) => {
await randomizeCompose(); randomizeCompose();
await refetch(); refetch();
toast.success("Compose updated"); toast.success("Compose updated");
}) })
.catch(() => { .catch(() => {
@@ -84,10 +84,15 @@ export const IsolatedDeployment = ({ composeId }: Props) => {
await mutateAsync({ await mutateAsync({
composeId, composeId,
suffix: data?.appName || "", suffix: data?.appName || "",
}).then(async (data) => { })
await utils.project.all.invalidate(); .then(async (data) => {
setCompose(data); await utils.project.all.invalidate();
}); setCompose(data);
toast.success("Compose Isolated");
})
.catch(() => {
toast.error("Error isolating the compose");
});
}; };
return ( return (

View File

@@ -77,8 +77,8 @@ export const RandomizeCompose = ({ composeId }: Props) => {
randomize: formData?.randomize || false, randomize: formData?.randomize || false,
}) })
.then(async (_data) => { .then(async (_data) => {
await randomizeCompose(); randomizeCompose();
await refetch(); refetch();
toast.success("Compose updated"); toast.success("Compose updated");
}) })
.catch(() => { .catch(() => {
@@ -90,10 +90,15 @@ export const RandomizeCompose = ({ composeId }: Props) => {
await mutateAsync({ await mutateAsync({
composeId, composeId,
suffix, suffix,
}).then(async (data) => { })
await utils.project.all.invalidate(); .then(async (data) => {
setCompose(data); await utils.project.all.invalidate();
}); setCompose(data);
toast.success("Compose randomized");
})
.catch(() => {
toast.error("Error randomizing the compose");
});
}; };
return ( return (

View File

@@ -15,7 +15,6 @@ import { Copy, Loader2 } from "lucide-react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { useState } from "react"; import { useState } from "react";
import { toast } from "sonner"; import { toast } from "sonner";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
export type Services = { export type Services = {
appName: string; appName: string;
@@ -49,7 +48,6 @@ export const DuplicateProject = ({
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const [name, setName] = useState(""); const [name, setName] = useState("");
const [description, setDescription] = useState(""); const [description, setDescription] = useState("");
const [duplicateType, setDuplicateType] = useState("new-project"); // "new-project" or "same-project"
const utils = api.useUtils(); const utils = api.useUtils();
const router = useRouter(); const router = useRouter();
@@ -61,15 +59,9 @@ export const DuplicateProject = ({
api.project.duplicate.useMutation({ api.project.duplicate.useMutation({
onSuccess: async (newProject) => { onSuccess: async (newProject) => {
await utils.project.all.invalidate(); await utils.project.all.invalidate();
toast.success( toast.success("Project duplicated successfully");
duplicateType === "new-project"
? "Project duplicated successfully"
: "Services duplicated successfully",
);
setOpen(false); setOpen(false);
if (duplicateType === "new-project") { router.push(`/dashboard/project/${newProject.projectId}`);
router.push(`/dashboard/project/${newProject.projectId}`);
}
}, },
onError: (error) => { onError: (error) => {
toast.error(error.message); toast.error(error.message);
@@ -77,7 +69,7 @@ export const DuplicateProject = ({
}); });
const handleDuplicate = async () => { const handleDuplicate = async () => {
if (duplicateType === "new-project" && !name) { if (!name) {
toast.error("Project name is required"); toast.error("Project name is required");
return; return;
} }
@@ -91,7 +83,6 @@ export const DuplicateProject = ({
id: service.id, id: service.id,
type: service.type, type: service.type,
})), })),
duplicateInSameProject: duplicateType === "same-project",
}); });
}; };
@@ -104,7 +95,6 @@ export const DuplicateProject = ({
// Reset form when closing // Reset form when closing
setName(""); setName("");
setDescription(""); setDescription("");
setDuplicateType("new-project");
} }
}} }}
> >
@@ -116,54 +106,32 @@ export const DuplicateProject = ({
</DialogTrigger> </DialogTrigger>
<DialogContent> <DialogContent>
<DialogHeader> <DialogHeader>
<DialogTitle>Duplicate Services</DialogTitle> <DialogTitle>Duplicate Project</DialogTitle>
<DialogDescription> <DialogDescription>
Choose where to duplicate the selected services Create a new project with the selected services
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
<div className="grid gap-4 py-4"> <div className="grid gap-4 py-4">
<div className="grid gap-2"> <div className="grid gap-2">
<Label>Duplicate to</Label> <Label htmlFor="name">Name</Label>
<RadioGroup <Input
value={duplicateType} id="name"
onValueChange={setDuplicateType} value={name}
className="grid gap-2" onChange={(e) => setName(e.target.value)}
> placeholder="New project name"
<div className="flex items-center space-x-2"> />
<RadioGroupItem value="new-project" id="new-project" />
<Label htmlFor="new-project">New project</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="same-project" id="same-project" />
<Label htmlFor="same-project">Same project</Label>
</div>
</RadioGroup>
</div> </div>
{duplicateType === "new-project" && ( <div className="grid gap-2">
<> <Label htmlFor="description">Description</Label>
<div className="grid gap-2"> <Input
<Label htmlFor="name">Name</Label> id="description"
<Input value={description}
id="name" onChange={(e) => setDescription(e.target.value)}
value={name} placeholder="Project description (optional)"
onChange={(e) => setName(e.target.value)} />
placeholder="New project name" </div>
/>
</div>
<div className="grid gap-2">
<Label htmlFor="description">Description</Label>
<Input
id="description"
value={description}
onChange={(e) => setDescription(e.target.value)}
placeholder="Project description (optional)"
/>
</div>
</>
)}
<div className="grid gap-2"> <div className="grid gap-2">
<Label>Selected services to duplicate</Label> <Label>Selected services to duplicate</Label>
@@ -191,14 +159,10 @@ export const DuplicateProject = ({
{isLoading ? ( {isLoading ? (
<> <>
<Loader2 className="mr-2 h-4 w-4 animate-spin" /> <Loader2 className="mr-2 h-4 w-4 animate-spin" />
{duplicateType === "new-project" Duplicating...
? "Duplicating project..."
: "Duplicating services..."}
</> </>
) : duplicateType === "new-project" ? (
"Duplicate project"
) : ( ) : (
"Duplicate services" "Duplicate"
)} )}
</Button> </Button>
</DialogFooter> </DialogFooter>

View File

@@ -156,67 +156,6 @@ export const HandleServers = ({ serverId }: Props) => {
remotely. remotely.
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
<div>
<p className="text-primary text-sm font-medium">
You will need to purchase or rent a Virtual Private Server (VPS) to
proceed, we recommend to use one of these providers since has been
heavily tested.
</p>
<ul className="list-inside list-disc pl-4 text-sm text-muted-foreground mt-4">
<li>
<a
href="https://www.hostinger.com/vps-hosting?REFERRALCODE=1SIUMAURICI97"
className="text-link underline"
>
Hostinger - Get 20% Discount
</a>
</li>
<li>
<a
href=" https://app.americancloud.com/register?ref=dokploy"
className="text-link underline"
>
American Cloud - Get $20 Credits
</a>
</li>
<li>
<a
href="https://m.do.co/c/db24efd43f35"
className="text-link underline"
>
DigitalOcean - Get $200 Credits
</a>
</li>
<li>
<a
href="https://hetzner.cloud/?ref=vou4fhxJ1W2D"
className="text-link underline"
>
Hetzner - Get 20 Credits
</a>
</li>
<li>
<a
href="https://www.vultr.com/?ref=9679828"
className="text-link underline"
>
Vultr
</a>
</li>
<li>
<a
href="https://www.linode.com/es/pricing/#compute-shared"
className="text-link underline"
>
Linode
</a>
</li>
</ul>
<AlertBlock className="mt-4 px-4">
You are free to use whatever provider, but we recommend to use one
of the above, to avoid issues.
</AlertBlock>
</div>
{!canCreateMoreServers && ( {!canCreateMoreServers && (
<AlertBlock type="warning"> <AlertBlock type="warning">
You cannot create more servers,{" "} You cannot create more servers,{" "}

View File

@@ -177,14 +177,6 @@ export const WelcomeSuscription = () => {
Hostinger - Get 20% Discount Hostinger - Get 20% Discount
</a> </a>
</li> </li>
<li>
<a
href=" https://app.americancloud.com/register?ref=dokploy"
className="text-link underline"
>
American Cloud - Get $20 Credits
</a>
</li>
<li> <li>
<a <a
href="https://m.do.co/c/db24efd43f35" href="https://m.do.co/c/db24efd43f35"

View File

@@ -1,6 +1,6 @@
{ {
"name": "dokploy", "name": "dokploy",
"version": "v0.22.6", "version": "v0.22.4",
"private": true, "private": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"type": "module", "type": "module",

View File

@@ -439,15 +439,7 @@ export const composeRouter = createTRPCRouter({
} }
const projectName = slugify(`${project.name} ${input.id}`); const projectName = slugify(`${project.name} ${input.id}`);
const appName = `${projectName}-${generatePassword(6)}`; const generate = processTemplate(template.config, {
const config = {
...template.config,
variables: {
APP_NAME: appName,
...template.config.variables,
},
};
const generate = processTemplate(config, {
serverIp: serverIp, serverIp: serverIp,
projectName: projectName, projectName: projectName,
}); });
@@ -459,7 +451,7 @@ export const composeRouter = createTRPCRouter({
serverId: input.serverId, serverId: input.serverId,
name: input.id, name: input.id,
sourceType: "raw", sourceType: "raw",
appName: appName, appName: `${projectName}-${generatePassword(6)}`,
isolatedDeployment: true, isolatedDeployment: true,
}); });
@@ -613,15 +605,7 @@ export const composeRouter = createTRPCRouter({
}); });
} }
const configModified = { const processedTemplate = processTemplate(config, {
...config,
variables: {
APP_NAME: compose.appName,
...config.variables,
},
};
const processedTemplate = processTemplate(configModified, {
serverIp: serverIp, serverIp: serverIp,
projectName: compose.appName, projectName: compose.appName,
}); });
@@ -691,15 +675,7 @@ export const composeRouter = createTRPCRouter({
}); });
} }
const configModified = { const processedTemplate = processTemplate(config, {
...config,
variables: {
APP_NAME: compose.appName,
...config.variables,
},
};
const processedTemplate = processTemplate(configModified, {
serverIp: serverIp, serverIp: serverIp,
projectName: compose.appName, projectName: compose.appName,
}); });

View File

@@ -309,7 +309,6 @@ export const projectRouter = createTRPCRouter({
}), }),
) )
.optional(), .optional(),
duplicateInSameProject: z.boolean().default(false),
}), }),
) )
.mutation(async ({ ctx, input }) => { .mutation(async ({ ctx, input }) => {
@@ -332,17 +331,15 @@ export const projectRouter = createTRPCRouter({
}); });
} }
// Create new project or use existing one // Create new project
const targetProject = input.duplicateInSameProject const newProject = await createProject(
? sourceProject {
: await createProject( name: input.name,
{ description: input.description,
name: input.name, env: sourceProject.env,
description: input.description, },
env: sourceProject.env, ctx.session.activeOrganizationId,
}, );
ctx.session.activeOrganizationId,
);
if (input.includeServices) { if (input.includeServices) {
const servicesToDuplicate = input.selectedServices || []; const servicesToDuplicate = input.selectedServices || [];
@@ -365,10 +362,7 @@ export const projectRouter = createTRPCRouter({
const newApplication = await createApplication({ const newApplication = await createApplication({
...application, ...application,
name: input.duplicateInSameProject projectId: newProject.projectId,
? `${application.name} (copy)`
: application.name,
projectId: targetProject.projectId,
}); });
for (const domain of domains) { for (const domain of domains) {
@@ -429,10 +423,7 @@ export const projectRouter = createTRPCRouter({
const newPostgres = await createPostgres({ const newPostgres = await createPostgres({
...postgres, ...postgres,
name: input.duplicateInSameProject projectId: newProject.projectId,
? `${postgres.name} (copy)`
: postgres.name,
projectId: targetProject.projectId,
}); });
for (const mount of mounts) { for (const mount of mounts) {
@@ -458,10 +449,7 @@ export const projectRouter = createTRPCRouter({
await findMariadbById(id); await findMariadbById(id);
const newMariadb = await createMariadb({ const newMariadb = await createMariadb({
...mariadb, ...mariadb,
name: input.duplicateInSameProject projectId: newProject.projectId,
? `${mariadb.name} (copy)`
: mariadb.name,
projectId: targetProject.projectId,
}); });
for (const mount of mounts) { for (const mount of mounts) {
@@ -487,10 +475,7 @@ export const projectRouter = createTRPCRouter({
await findMongoById(id); await findMongoById(id);
const newMongo = await createMongo({ const newMongo = await createMongo({
...mongo, ...mongo,
name: input.duplicateInSameProject projectId: newProject.projectId,
? `${mongo.name} (copy)`
: mongo.name,
projectId: targetProject.projectId,
}); });
for (const mount of mounts) { for (const mount of mounts) {
@@ -516,10 +501,7 @@ export const projectRouter = createTRPCRouter({
await findMySqlById(id); await findMySqlById(id);
const newMysql = await createMysql({ const newMysql = await createMysql({
...mysql, ...mysql,
name: input.duplicateInSameProject projectId: newProject.projectId,
? `${mysql.name} (copy)`
: mysql.name,
projectId: targetProject.projectId,
}); });
for (const mount of mounts) { for (const mount of mounts) {
@@ -544,10 +526,7 @@ export const projectRouter = createTRPCRouter({
const { redisId, mounts, ...redis } = await findRedisById(id); const { redisId, mounts, ...redis } = await findRedisById(id);
const newRedis = await createRedis({ const newRedis = await createRedis({
...redis, ...redis,
name: input.duplicateInSameProject projectId: newProject.projectId,
? `${redis.name} (copy)`
: redis.name,
projectId: targetProject.projectId,
}); });
for (const mount of mounts) { for (const mount of mounts) {
@@ -566,10 +545,7 @@ export const projectRouter = createTRPCRouter({
await findComposeById(id); await findComposeById(id);
const newCompose = await createCompose({ const newCompose = await createCompose({
...compose, ...compose,
name: input.duplicateInSameProject projectId: newProject.projectId,
? `${compose.name} (copy)`
: compose.name,
projectId: targetProject.projectId,
}); });
for (const mount of mounts) { for (const mount of mounts) {
@@ -596,20 +572,21 @@ export const projectRouter = createTRPCRouter({
}; };
// Duplicate selected services // Duplicate selected services
for (const service of servicesToDuplicate) { for (const service of servicesToDuplicate) {
await duplicateService(service.id, service.type); await duplicateService(service.id, service.type);
} }
} }
if (!input.duplicateInSameProject && ctx.user.role === "member") { if (ctx.user.role === "member") {
await addNewProject( await addNewProject(
ctx.user.id, ctx.user.id,
targetProject.projectId, newProject.projectId,
ctx.session.activeOrganizationId, ctx.session.activeOrganizationId,
); );
} }
return targetProject; return newProject;
} catch (error) { } catch (error) {
throw new TRPCError({ throw new TRPCError({
code: "BAD_REQUEST", code: "BAD_REQUEST",

View File

@@ -356,20 +356,20 @@ const installUtilities = () => `
case "$OS_TYPE" in case "$OS_TYPE" in
arch) arch)
pacman -Sy --noconfirm --needed curl wget git git-lfs jq openssl >/dev/null || true pacman -Sy --noconfirm --needed curl wget git jq openssl >/dev/null || true
;; ;;
alpine) alpine)
sed -i '/^#.*\/community/s/^#//' /etc/apk/repositories sed -i '/^#.*\/community/s/^#//' /etc/apk/repositories
apk update >/dev/null apk update >/dev/null
apk add curl wget git git-lfs jq openssl sudo unzip tar >/dev/null apk add curl wget git jq openssl sudo unzip tar >/dev/null
;; ;;
ubuntu | debian | raspbian) ubuntu | debian | raspbian)
DEBIAN_FRONTEND=noninteractive apt-get update -y >/dev/null DEBIAN_FRONTEND=noninteractive apt-get update -y >/dev/null
DEBIAN_FRONTEND=noninteractive apt-get install -y unzip curl wget git git-lfs jq openssl >/dev/null DEBIAN_FRONTEND=noninteractive apt-get install -y unzip curl wget git jq openssl >/dev/null
;; ;;
centos | fedora | rhel | ol | rocky | almalinux | amzn) centos | fedora | rhel | ol | rocky | almalinux | amzn)
if [ "$OS_TYPE" = "amzn" ]; then if [ "$OS_TYPE" = "amzn" ]; then
dnf install -y wget git git-lfs jq openssl >/dev/null dnf install -y wget git jq openssl >/dev/null
else else
if ! command -v dnf >/dev/null; then if ! command -v dnf >/dev/null; then
yum install -y dnf >/dev/null yum install -y dnf >/dev/null
@@ -377,12 +377,12 @@ const installUtilities = () => `
if ! command -v curl >/dev/null; then if ! command -v curl >/dev/null; then
dnf install -y curl >/dev/null dnf install -y curl >/dev/null
fi fi
dnf install -y wget git git-lfs jq openssl unzip >/dev/null dnf install -y wget git jq openssl unzip >/dev/null
fi fi
;; ;;
sles | opensuse-leap | opensuse-tumbleweed) sles | opensuse-leap | opensuse-tumbleweed)
zypper refresh >/dev/null zypper refresh >/dev/null
zypper install -y curl wget git git-lfs jq openssl >/dev/null zypper install -y curl wget git jq openssl >/dev/null
;; ;;
*) *)
echo "This script only supports Debian, Redhat, Arch Linux, or SLES based operating systems for now." echo "This script only supports Debian, Redhat, Arch Linux, or SLES based operating systems for now."
@@ -577,7 +577,7 @@ const installNixpacks = () => `
if command_exists nixpacks; then if command_exists nixpacks; then
echo "Nixpacks already installed ✅" echo "Nixpacks already installed ✅"
else else
export NIXPACKS_VERSION=1.39.0 export NIXPACKS_VERSION=1.35.0
bash -c "$(curl -fsSL https://nixpacks.com/install.sh)" bash -c "$(curl -fsSL https://nixpacks.com/install.sh)"
echo "Nixpacks version $NIXPACKS_VERSION installed ✅" echo "Nixpacks version $NIXPACKS_VERSION installed ✅"
fi fi

View File

@@ -3,7 +3,7 @@ import { execAsync } from "../process/execAsync";
import { getS3Credentials, normalizeS3Path } from "./utils"; import { getS3Credentials, normalizeS3Path } from "./utils";
import { findDestinationById } from "@dokploy/server/services/destination"; import { findDestinationById } from "@dokploy/server/services/destination";
import { IS_CLOUD, paths } from "@dokploy/server/constants"; import { IS_CLOUD, paths } from "@dokploy/server/constants";
import { mkdtemp, rm } from "node:fs/promises"; import { mkdtemp } from "node:fs/promises";
import { join } from "node:path"; import { join } from "node:path";
import { tmpdir } from "node:os"; import { tmpdir } from "node:os";
import { import {
@@ -51,23 +51,13 @@ export const runWebServerBackup = async (backup: BackupSchedule) => {
const postgresContainerId = containerId.trim(); const postgresContainerId = containerId.trim();
// First dump the database inside the container const postgresCommand = `docker exec ${postgresContainerId} pg_dump -v -Fc -U dokploy -d dokploy > '${tempDir}/database.sql'`;
const dumpCommand = `docker exec ${postgresContainerId} pg_dump -v -Fc -U dokploy -d dokploy -f /tmp/database.sql`;
writeStream.write(`Running dump command: ${dumpCommand}\n`);
await execAsync(dumpCommand);
// Then copy the file from the container to host writeStream.write(`Running command: ${postgresCommand}\n`);
const copyCommand = `docker cp ${postgresContainerId}:/tmp/database.sql ${tempDir}/database.sql`; await execAsync(postgresCommand);
writeStream.write(`Copying database dump: ${copyCommand}\n`);
await execAsync(copyCommand);
// Clean up the temp file in the container
const cleanupCommand = `docker exec ${postgresContainerId} rm -f /tmp/database.sql`;
writeStream.write(`Cleaning up temp file: ${cleanupCommand}\n`);
await execAsync(cleanupCommand);
await execAsync( await execAsync(
`rsync -a --ignore-errors ${BASE_PATH}/ ${tempDir}/filesystem/`, `rsync -av --ignore-errors ${BASE_PATH}/ ${tempDir}/filesystem/`,
); );
writeStream.write("Copied filesystem to temp directory\n"); writeStream.write("Copied filesystem to temp directory\n");
@@ -87,11 +77,7 @@ export const runWebServerBackup = async (backup: BackupSchedule) => {
await updateDeploymentStatus(deployment.deploymentId, "done"); await updateDeploymentStatus(deployment.deploymentId, "done");
return true; return true;
} finally { } finally {
try { await execAsync(`rm -rf ${tempDir}`);
await rm(tempDir, { recursive: true, force: true });
} catch (cleanupError) {
console.error("Cleanup error:", cleanupError);
}
} }
} catch (error) { } catch (error) {
console.error("Backup error:", error); console.error("Backup error:", error);

View File

@@ -190,8 +190,7 @@ const createEnvFile = (compose: ComposeNested) => {
join(COMPOSE_PATH, appName, "code", "docker-compose.yml"); join(COMPOSE_PATH, appName, "code", "docker-compose.yml");
const envFilePath = join(dirname(composeFilePath), ".env"); const envFilePath = join(dirname(composeFilePath), ".env");
let envContent = `APP_NAME=${appName}\n`; let envContent = env || "";
envContent += env || "";
if (!envContent.includes("DOCKER_CONFIG")) { if (!envContent.includes("DOCKER_CONFIG")) {
envContent += "\nDOCKER_CONFIG=/root/.docker/config.json"; envContent += "\nDOCKER_CONFIG=/root/.docker/config.json";
} }
@@ -220,8 +219,7 @@ export const getCreateEnvFileCommand = (compose: ComposeNested) => {
const envFilePath = join(dirname(composeFilePath), ".env"); const envFilePath = join(dirname(composeFilePath), ".env");
let envContent = `APP_NAME=${appName}\n`; let envContent = env || "";
envContent += env || "";
if (!envContent.includes("DOCKER_CONFIG")) { if (!envContent.includes("DOCKER_CONFIG")) {
envContent += "\nDOCKER_CONFIG=/root/.docker/config.json"; envContent += "\nDOCKER_CONFIG=/root/.docker/config.json";
} }

View File

@@ -246,16 +246,32 @@ export const getGitlabRepositories = async (gitlabId?: string) => {
const gitlabProvider = await findGitlabById(gitlabId); const gitlabProvider = await findGitlabById(gitlabId);
const allProjects = await validateGitlabProvider(gitlabProvider); const response = await fetch(
`${gitlabProvider.gitlabUrl}/api/v4/projects?membership=true&owned=true&page=${0}&per_page=${100}`,
{
headers: {
Authorization: `Bearer ${gitlabProvider.accessToken}`,
},
},
);
const filteredRepos = allProjects.filter((repo: any) => { if (!response.ok) {
throw new TRPCError({
code: "BAD_REQUEST",
message: `Failed to fetch repositories: ${response.statusText}`,
});
}
const repositories = await response.json();
const filteredRepos = repositories.filter((repo: any) => {
const { full_path, kind } = repo.namespace; const { full_path, kind } = repo.namespace;
const groupName = gitlabProvider.groupName?.toLowerCase(); const groupName = gitlabProvider.groupName?.toLowerCase();
if (groupName) { if (groupName) {
const isIncluded = groupName const isIncluded = groupName
.split(",") .split(",")
.some((name) => full_path === name); .some((name) => full_path.toLowerCase().includes(name));
return isIncluded && kind === "group"; return isIncluded && kind === "group";
} }
@@ -416,60 +432,34 @@ export const testGitlabConnection = async (
const gitlabProvider = await findGitlabById(gitlabId); const gitlabProvider = await findGitlabById(gitlabId);
const repositories = await validateGitlabProvider(gitlabProvider); const response = await fetch(
`${gitlabProvider.gitlabUrl}/api/v4/projects?membership=true&owned=true&page=${0}&per_page=${100}`,
{
headers: {
Authorization: `Bearer ${gitlabProvider.accessToken}`,
},
},
);
if (!response.ok) {
throw new TRPCError({
code: "BAD_REQUEST",
message: `Failed to fetch repositories: ${response.statusText}`,
});
}
const repositories = await response.json();
const filteredRepos = repositories.filter((repo: any) => { const filteredRepos = repositories.filter((repo: any) => {
const { full_path, kind } = repo.namespace; const { full_path, kind } = repo.namespace;
if (groupName) { if (groupName) {
return groupName.split(",").some((name) => full_path === name); return groupName
.split(",")
.some((name) => full_path.toLowerCase().includes(name));
} }
return kind === "user"; return kind === "user";
}); });
return filteredRepos.length; return filteredRepos.length;
}; };
export const validateGitlabProvider = async (gitlabProvider: Gitlab) => {
try {
const allProjects = [];
let page = 1;
const perPage = 100; // GitLab's max per page is 100
while (true) {
const response = await fetch(
`${gitlabProvider.gitlabUrl}/api/v4/projects?membership=true&owned=true&page=${page}&per_page=${perPage}`,
{
headers: {
Authorization: `Bearer ${gitlabProvider.accessToken}`,
},
},
);
if (!response.ok) {
throw new TRPCError({
code: "BAD_REQUEST",
message: `Failed to fetch repositories: ${response.statusText}`,
});
}
const projects = await response.json();
if (projects.length === 0) {
break;
}
allProjects.push(...projects);
page++;
const total = response.headers.get("x-total");
if (total && allProjects.length >= Number.parseInt(total)) {
break;
}
}
return allProjects;
} catch (error) {
throw error;
}
};