Compare commits

..

24 Commits

Author SHA1 Message Date
Mauricio Siu
17a26353b6 chore: bump version to v0.22.6 in package.json 2025-05-18 02:30:04 -06:00
Mauricio Siu
e2c163c6d5 Merge pull request #1919 from nktnet1/fix-randomise-compose-await
fix: randomize-compose missing await
2025-05-18 02:29:32 -06:00
Khiet Tam Nguyen
616e11722c fix: randomize-compose missing await 2025-05-18 18:26:44 +10:00
Mauricio Siu
91a44706df Merge pull request #1917 from nktnet1/fix-isolated-randomized-compose-notifs
fix: multiple notifications for isolated compose and randomize compose
2025-05-18 02:19:51 -06:00
Mauricio Siu
748de47a6d Merge pull request #1918 from Dokploy/fix/web-server-backup-maxlenght
fix: update rsync command in web server backup to remove verbose flag
2025-05-18 02:18:31 -06:00
Mauricio Siu
cbf9aef0df fix: remove console log for rsync command in web server backup 2025-05-18 02:18:05 -06:00
Mauricio Siu
e2befc24a5 fix: update rsync command in web server backup to remove verbose flag 2025-05-18 02:17:41 -06:00
autofix-ci[bot]
0f48f2c830 [autofix.ci] apply automated fixes 2025-05-18 05:12:06 +00:00
Khiet Tam Nguyen
5dfa7645f3 fix: multiple notifications for isolated compose and randomize compose 2025-05-18 15:07:05 +10:00
Mauricio Siu
7fe163dd33 Merge pull request #1913 from Dokploy/feat/add-appname-compose
Feat/add appname compose
2025-05-17 15:37:22 -06:00
Mauricio Siu
19b56771b8 style: update styling for environment display and increase scroll area height in import component 2025-05-17 15:28:52 -06:00
Mauricio Siu
cff01ed438 refactor: modify template processing to include APP_NAME variable in configuration 2025-05-17 15:27:42 -06:00
Mauricio Siu
10fa3c8cf1 fix: update environment file generation to include APP_NAME variable 2025-05-17 15:18:31 -06:00
Mauricio Siu
6c5497ed21 Merge pull request #1912 from Dokploy/1873-duplicate-clones-the-project-not-the-service
feat: enhance project duplication functionality with options for new …
2025-05-17 14:35:17 -06:00
Mauricio Siu
380656efee feat: enhance project duplication functionality with options for new or same project 2025-05-17 14:33:07 -06:00
Mauricio Siu
c64d2245ce Merge pull request #1897 from enie123/canary
build: update nixpacks to 1.39.0
2025-05-17 03:54:11 -06:00
Mauricio Siu
a985998b93 feat: add VPS provider recommendations and alerts in server settings 2025-05-17 03:53:37 -06:00
Mauricio Siu
4f3ba16dfa chore: bump version to v0.22.5 in package.json 2025-05-17 03:34:31 -06:00
Mauricio Siu
6c788429f1 Merge pull request #1910 from Dokploy/1894-gitlab-self-hosted-cannot-find-all-repository
refactor: streamline GitLab repository fetching by introducing valida…
2025-05-17 03:02:26 -06:00
Mauricio Siu
3176a9d7e3 refactor: streamline GitLab repository fetching by introducing validateGitlabProvider function for improved error handling and pagination 2025-05-17 02:51:40 -06:00
Mauricio Siu
94a6a9587e Merge pull request #1872 from IPdotSetAF/git-lfs-not-supported
fix: installed git-lfs in docker image
2025-05-17 02:09:03 -06:00
Mauricio Siu
911681f389 fix: add git-lfs installation to various OS setups in server-setup script 2025-05-17 02:03:04 -06:00
Eric Nie
d4032f34bf build: update nixpacks to 1.39.0 2025-05-15 00:45:48 +07:00
IPdotSetAF
5156b45ffc fix: installed git-lfs in docker image 2025-05-11 10:36:52 +03:30
15 changed files with 278 additions and 124 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.35.0 ARG NIXPACKS_VERSION=1.39.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 \

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 && rm -rf /var/lib/apt/lists/* RUN apt-get update && apt-get install -y python3 make g++ git git-lfs && git lfs install && 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 border bg-card p-2 font-mono text-sm" className="rounded-lg truncate 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-[25vh] pr-4"> <ScrollArea className="h-[45vh] pr-4">
<CodeEditor <CodeEditor
language="yaml" language="yaml"
value={selectedMount?.content || ""} value={selectedMount?.content || ""}

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) => {
randomizeCompose(); await randomizeCompose();
refetch(); await refetch();
toast.success("Compose updated"); toast.success("Compose updated");
}) })
.catch(() => { .catch(() => {
@@ -84,15 +84,10 @@ export const IsolatedDeployment = ({ composeId }: Props) => {
await mutateAsync({ await mutateAsync({
composeId, composeId,
suffix: data?.appName || "", suffix: data?.appName || "",
}) }).then(async (data) => {
.then(async (data) => { await utils.project.all.invalidate();
await utils.project.all.invalidate(); setCompose(data);
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) => {
randomizeCompose(); await randomizeCompose();
refetch(); await refetch();
toast.success("Compose updated"); toast.success("Compose updated");
}) })
.catch(() => { .catch(() => {
@@ -90,15 +90,10 @@ export const RandomizeCompose = ({ composeId }: Props) => {
await mutateAsync({ await mutateAsync({
composeId, composeId,
suffix, suffix,
}) }).then(async (data) => {
.then(async (data) => { await utils.project.all.invalidate();
await utils.project.all.invalidate(); setCompose(data);
setCompose(data); });
toast.success("Compose randomized");
})
.catch(() => {
toast.error("Error randomizing the compose");
});
}; };
return ( return (

View File

@@ -15,6 +15,7 @@ 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;
@@ -48,6 +49,7 @@ 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();
@@ -59,9 +61,15 @@ 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("Project duplicated successfully"); toast.success(
duplicateType === "new-project"
? "Project duplicated successfully"
: "Services duplicated successfully",
);
setOpen(false); setOpen(false);
router.push(`/dashboard/project/${newProject.projectId}`); if (duplicateType === "new-project") {
router.push(`/dashboard/project/${newProject.projectId}`);
}
}, },
onError: (error) => { onError: (error) => {
toast.error(error.message); toast.error(error.message);
@@ -69,7 +77,7 @@ export const DuplicateProject = ({
}); });
const handleDuplicate = async () => { const handleDuplicate = async () => {
if (!name) { if (duplicateType === "new-project" && !name) {
toast.error("Project name is required"); toast.error("Project name is required");
return; return;
} }
@@ -83,6 +91,7 @@ export const DuplicateProject = ({
id: service.id, id: service.id,
type: service.type, type: service.type,
})), })),
duplicateInSameProject: duplicateType === "same-project",
}); });
}; };
@@ -95,6 +104,7 @@ export const DuplicateProject = ({
// Reset form when closing // Reset form when closing
setName(""); setName("");
setDescription(""); setDescription("");
setDuplicateType("new-project");
} }
}} }}
> >
@@ -106,32 +116,54 @@ export const DuplicateProject = ({
</DialogTrigger> </DialogTrigger>
<DialogContent> <DialogContent>
<DialogHeader> <DialogHeader>
<DialogTitle>Duplicate Project</DialogTitle> <DialogTitle>Duplicate Services</DialogTitle>
<DialogDescription> <DialogDescription>
Create a new project with the selected services Choose where to duplicate 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 htmlFor="name">Name</Label> <Label>Duplicate to</Label>
<Input <RadioGroup
id="name" value={duplicateType}
value={name} onValueChange={setDuplicateType}
onChange={(e) => setName(e.target.value)} className="grid gap-2"
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>
<div className="grid gap-2"> {duplicateType === "new-project" && (
<Label htmlFor="description">Description</Label> <>
<Input <div className="grid gap-2">
id="description" <Label htmlFor="name">Name</Label>
value={description} <Input
onChange={(e) => setDescription(e.target.value)} id="name"
placeholder="Project description (optional)" value={name}
/> onChange={(e) => setName(e.target.value)}
</div> placeholder="New project name"
/>
</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>
@@ -159,10 +191,14 @@ 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" />
Duplicating... {duplicateType === "new-project"
? "Duplicating project..."
: "Duplicating services..."}
</> </>
) : duplicateType === "new-project" ? (
"Duplicate project"
) : ( ) : (
"Duplicate" "Duplicate services"
)} )}
</Button> </Button>
</DialogFooter> </DialogFooter>

View File

@@ -156,6 +156,67 @@ 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,6 +177,14 @@ 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.4", "version": "v0.22.6",
"private": true, "private": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"type": "module", "type": "module",

View File

@@ -439,7 +439,15 @@ export const composeRouter = createTRPCRouter({
} }
const projectName = slugify(`${project.name} ${input.id}`); const projectName = slugify(`${project.name} ${input.id}`);
const generate = processTemplate(template.config, { const appName = `${projectName}-${generatePassword(6)}`;
const config = {
...template.config,
variables: {
APP_NAME: appName,
...template.config.variables,
},
};
const generate = processTemplate(config, {
serverIp: serverIp, serverIp: serverIp,
projectName: projectName, projectName: projectName,
}); });
@@ -451,7 +459,7 @@ export const composeRouter = createTRPCRouter({
serverId: input.serverId, serverId: input.serverId,
name: input.id, name: input.id,
sourceType: "raw", sourceType: "raw",
appName: `${projectName}-${generatePassword(6)}`, appName: appName,
isolatedDeployment: true, isolatedDeployment: true,
}); });
@@ -605,7 +613,15 @@ export const composeRouter = createTRPCRouter({
}); });
} }
const processedTemplate = processTemplate(config, { const configModified = {
...config,
variables: {
APP_NAME: compose.appName,
...config.variables,
},
};
const processedTemplate = processTemplate(configModified, {
serverIp: serverIp, serverIp: serverIp,
projectName: compose.appName, projectName: compose.appName,
}); });
@@ -675,7 +691,15 @@ export const composeRouter = createTRPCRouter({
}); });
} }
const processedTemplate = processTemplate(config, { const configModified = {
...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,6 +309,7 @@ export const projectRouter = createTRPCRouter({
}), }),
) )
.optional(), .optional(),
duplicateInSameProject: z.boolean().default(false),
}), }),
) )
.mutation(async ({ ctx, input }) => { .mutation(async ({ ctx, input }) => {
@@ -331,15 +332,17 @@ export const projectRouter = createTRPCRouter({
}); });
} }
// Create new project // Create new project or use existing one
const newProject = await createProject( const targetProject = input.duplicateInSameProject
{ ? sourceProject
name: input.name, : await createProject(
description: input.description, {
env: sourceProject.env, name: input.name,
}, description: input.description,
ctx.session.activeOrganizationId, env: sourceProject.env,
); },
ctx.session.activeOrganizationId,
);
if (input.includeServices) { if (input.includeServices) {
const servicesToDuplicate = input.selectedServices || []; const servicesToDuplicate = input.selectedServices || [];
@@ -362,7 +365,10 @@ export const projectRouter = createTRPCRouter({
const newApplication = await createApplication({ const newApplication = await createApplication({
...application, ...application,
projectId: newProject.projectId, name: input.duplicateInSameProject
? `${application.name} (copy)`
: application.name,
projectId: targetProject.projectId,
}); });
for (const domain of domains) { for (const domain of domains) {
@@ -423,7 +429,10 @@ export const projectRouter = createTRPCRouter({
const newPostgres = await createPostgres({ const newPostgres = await createPostgres({
...postgres, ...postgres,
projectId: newProject.projectId, name: input.duplicateInSameProject
? `${postgres.name} (copy)`
: postgres.name,
projectId: targetProject.projectId,
}); });
for (const mount of mounts) { for (const mount of mounts) {
@@ -449,7 +458,10 @@ export const projectRouter = createTRPCRouter({
await findMariadbById(id); await findMariadbById(id);
const newMariadb = await createMariadb({ const newMariadb = await createMariadb({
...mariadb, ...mariadb,
projectId: newProject.projectId, name: input.duplicateInSameProject
? `${mariadb.name} (copy)`
: mariadb.name,
projectId: targetProject.projectId,
}); });
for (const mount of mounts) { for (const mount of mounts) {
@@ -475,7 +487,10 @@ export const projectRouter = createTRPCRouter({
await findMongoById(id); await findMongoById(id);
const newMongo = await createMongo({ const newMongo = await createMongo({
...mongo, ...mongo,
projectId: newProject.projectId, name: input.duplicateInSameProject
? `${mongo.name} (copy)`
: mongo.name,
projectId: targetProject.projectId,
}); });
for (const mount of mounts) { for (const mount of mounts) {
@@ -501,7 +516,10 @@ export const projectRouter = createTRPCRouter({
await findMySqlById(id); await findMySqlById(id);
const newMysql = await createMysql({ const newMysql = await createMysql({
...mysql, ...mysql,
projectId: newProject.projectId, name: input.duplicateInSameProject
? `${mysql.name} (copy)`
: mysql.name,
projectId: targetProject.projectId,
}); });
for (const mount of mounts) { for (const mount of mounts) {
@@ -526,7 +544,10 @@ 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,
projectId: newProject.projectId, name: input.duplicateInSameProject
? `${redis.name} (copy)`
: redis.name,
projectId: targetProject.projectId,
}); });
for (const mount of mounts) { for (const mount of mounts) {
@@ -545,7 +566,10 @@ export const projectRouter = createTRPCRouter({
await findComposeById(id); await findComposeById(id);
const newCompose = await createCompose({ const newCompose = await createCompose({
...compose, ...compose,
projectId: newProject.projectId, name: input.duplicateInSameProject
? `${compose.name} (copy)`
: compose.name,
projectId: targetProject.projectId,
}); });
for (const mount of mounts) { for (const mount of mounts) {
@@ -572,21 +596,20 @@ 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 (ctx.user.role === "member") { if (!input.duplicateInSameProject && ctx.user.role === "member") {
await addNewProject( await addNewProject(
ctx.user.id, ctx.user.id,
newProject.projectId, targetProject.projectId,
ctx.session.activeOrganizationId, ctx.session.activeOrganizationId,
); );
} }
return newProject; return targetProject;
} 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 jq openssl >/dev/null || true pacman -Sy --noconfirm --needed curl wget git git-lfs 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 jq openssl sudo unzip tar >/dev/null apk add curl wget git git-lfs 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 jq openssl >/dev/null DEBIAN_FRONTEND=noninteractive apt-get install -y unzip curl wget git git-lfs 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 jq openssl >/dev/null dnf install -y wget git git-lfs 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 jq openssl unzip >/dev/null dnf install -y wget git git-lfs 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 jq openssl >/dev/null zypper install -y curl wget git git-lfs 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.35.0 export NIXPACKS_VERSION=1.39.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

@@ -67,7 +67,7 @@ export const runWebServerBackup = async (backup: BackupSchedule) => {
await execAsync(cleanupCommand); await execAsync(cleanupCommand);
await execAsync( await execAsync(
`rsync -av --ignore-errors ${BASE_PATH}/ ${tempDir}/filesystem/`, `rsync -a --ignore-errors ${BASE_PATH}/ ${tempDir}/filesystem/`,
); );
writeStream.write("Copied filesystem to temp directory\n"); writeStream.write("Copied filesystem to temp directory\n");

View File

@@ -190,7 +190,8 @@ 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 = env || ""; let envContent = `APP_NAME=${appName}\n`;
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";
} }
@@ -219,7 +220,8 @@ export const getCreateEnvFileCommand = (compose: ComposeNested) => {
const envFilePath = join(dirname(composeFilePath), ".env"); const envFilePath = join(dirname(composeFilePath), ".env");
let envContent = env || ""; let envContent = `APP_NAME=${appName}\n`;
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,32 +246,16 @@ export const getGitlabRepositories = async (gitlabId?: string) => {
const gitlabProvider = await findGitlabById(gitlabId); const gitlabProvider = await findGitlabById(gitlabId);
const response = await fetch( const allProjects = await validateGitlabProvider(gitlabProvider);
`${gitlabProvider.gitlabUrl}/api/v4/projects?membership=true&owned=true&page=${0}&per_page=${100}`,
{
headers: {
Authorization: `Bearer ${gitlabProvider.accessToken}`,
},
},
);
if (!response.ok) { const filteredRepos = allProjects.filter((repo: any) => {
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.toLowerCase().includes(name)); .some((name) => full_path === name);
return isIncluded && kind === "group"; return isIncluded && kind === "group";
} }
@@ -432,34 +416,60 @@ export const testGitlabConnection = async (
const gitlabProvider = await findGitlabById(gitlabId); const gitlabProvider = await findGitlabById(gitlabId);
const response = await fetch( const repositories = await validateGitlabProvider(gitlabProvider);
`${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 return groupName.split(",").some((name) => full_path === name);
.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;
}
};