mirror of
https://github.com/Dokploy/dokploy
synced 2025-06-26 18:27:59 +00:00
Compare commits
1 Commits
v0.22.6
...
1888-docke
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
04a10146ac |
@@ -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" ]
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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 || ""}
|
||||||
|
|||||||
@@ -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({
|
||||||
|
|||||||
@@ -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({
|
||||||
|
|||||||
@@ -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({
|
||||||
|
|||||||
@@ -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({
|
||||||
|
|||||||
@@ -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({
|
||||||
|
|||||||
@@ -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({
|
||||||
|
|||||||
@@ -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({
|
||||||
|
|||||||
@@ -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({
|
||||||
|
|||||||
@@ -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({
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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,{" "}
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|||||||
Reference in New Issue
Block a user