mirror of
https://github.com/LukeHagar/dokploy.git
synced 2025-12-06 20:37:45 +00:00
Merge branch 'canary' into feat/internal-path-routing
This commit is contained in:
23
GUIDES.md
23
GUIDES.md
@@ -16,28 +16,29 @@ Here's how to install docker on different operating systems:
|
|||||||
### Ubuntu
|
### Ubuntu
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
# Uninstall old versions
|
||||||
|
for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get remove $pkg; done
|
||||||
|
|
||||||
# Update package index
|
# Update package index
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
|
|
||||||
# Install prerequisites
|
# Install prerequisites
|
||||||
sudo apt-get install \
|
sudo apt-get install ca-certificates curl
|
||||||
apt-transport-https \
|
sudo install -m 0755 -d /etc/apt/keyrings
|
||||||
ca-certificates \
|
|
||||||
curl \
|
|
||||||
gnupg \
|
|
||||||
lsb-release
|
|
||||||
|
|
||||||
# Add Docker's official GPG key
|
# Add Docker's official GPG key
|
||||||
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
|
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
|
||||||
|
sudo chmod a+r /etc/apt/keyrings/docker.asc
|
||||||
|
|
||||||
# Set up stable repository
|
# Add the repository to Apt sources
|
||||||
echo \
|
echo \
|
||||||
"deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \
|
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
|
||||||
$(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
|
$(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}") stable" | \
|
||||||
|
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
|
||||||
|
|
||||||
# Install Docker Engine
|
# Install Docker Engine
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install docker-ce docker-ce-cli containerd.io
|
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
|
||||||
```
|
```
|
||||||
|
|
||||||
## Windows
|
## Windows
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
## Core License (Apache License 2.0)
|
## Core License (Apache License 2.0)
|
||||||
|
|
||||||
Copyright 2024 Mauricio Siu.
|
Copyright 2025 Mauricio Siu.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
|||||||
36
README.md
36
README.md
@@ -62,46 +62,26 @@ For detailed documentation, visit [docs.dokploy.com](https://docs.dokploy.com).
|
|||||||
### Hero Sponsors 🎖
|
### Hero Sponsors 🎖
|
||||||
|
|
||||||
<div style="display: flex; align-items: center; gap: 20px;">
|
<div style="display: flex; align-items: center; gap: 20px;">
|
||||||
<a href="https://www.hostinger.com/vps-hosting?ref=dokploy" target="_blank" style="display: inline-block; margin-right: 10px;">
|
<a href="https://www.hostinger.com/vps-hosting?ref=dokploy" target="_blank" style="display: inline-block; margin-right: 10px;"><img src=".github/sponsors/hostinger.jpg" alt="Hostinger" height="50"/></a>
|
||||||
<img src=".github/sponsors/hostinger.jpg" alt="Hostinger" height="50"/>
|
<a href="https://www.lxaer.com/?ref=dokploy" target="_blank" style="display: inline-block; margin-right: 10px;"><img src=".github/sponsors/lxaer.png" alt="LX Aer" height="50"/></a>
|
||||||
</a>
|
<a href="https://mandarin3d.com/?ref=dokploy" target="_blank" style="display: inline-block;"><img src=".github/sponsors/mandarin.png" alt="Mandarin" height="50"/></a>
|
||||||
<a href="https://www.lxaer.com/?ref=dokploy" target="_blank" style="display: inline-block; margin-right: 10px;">
|
<a href="https://lightnode.com/?ref=dokploy" target="_blank" style="display: inline-block;"><img src=".github/sponsors/light-node.webp" alt="Lightnode" height="70"/></a>
|
||||||
<img src=".github/sponsors/lxaer.png" alt="LX Aer" height="50"/>
|
|
||||||
</a>
|
|
||||||
<a href="https://mandarin3d.com/?ref=dokploy" target="_blank" style="display: inline-block;">
|
|
||||||
<img src=".github/sponsors/mandarin.png" alt="Mandarin" height="50"/>
|
|
||||||
</a>
|
|
||||||
<a href="https://lightnode.com/?ref=dokploy" target="_blank" style="display: inline-block;">
|
|
||||||
<img src=".github/sponsors/light-node.webp" alt="Lightnode" height="70"/>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
### Premium Supporters 🥇
|
### Premium Supporters 🥇
|
||||||
|
|
||||||
<div style="display: flex; align-items: center; gap: 20px;">
|
<div style="display: flex; align-items: center; gap: 20px;">
|
||||||
<a href="https://supafort.com/?ref=dokploy" target="_blank" style="display: inline-block; margin-right: 20px;">
|
<a href="https://supafort.com/?ref=dokploy" target="_blank" style="display: inline-block; margin-right: 20px;"><img src="https://supafort.com/build/q-4Ht4rBZR.webp" alt="Supafort.com" height="50"/></a>
|
||||||
<img src="https://supafort.com/build/q-4Ht4rBZR.webp" alt="Supafort.com" height="50"/>
|
<a href="https://agentdock.ai/?ref=dokploy" target="_blank" style="display: inline-block; margin-right: 50px;"><img src=".github/sponsors/agentdock.png" alt="agentdock.ai" height="70"/></a>
|
||||||
</a>
|
|
||||||
|
|
||||||
<a href="https://agentdock.ai/?ref=dokploy" target="_blank" style="display: inline-block; margin-right: 50px;">
|
|
||||||
<img src=".github/sponsors/agentdock.png" alt="agentdock.ai" height="70"/>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
### Elite Contributors 🥈
|
### Elite Contributors 🥈
|
||||||
|
|
||||||
<div style="display: flex; align-items: center; gap: 20px;">
|
<div style="display: flex; align-items: center; gap: 20px;">
|
||||||
|
<a href="https://americancloud.com/?ref=dokploy" target="_blank" style="display: inline-block; padding: 10px; border-radius: 10px;"><img src=".github/sponsors/american-cloud.png" alt="AmericanCloud" height="70"/></a>
|
||||||
<a href="https://americancloud.com/?ref=dokploy" target="_blank" style="display: inline-block; padding: 10px; border-radius: 10px;">
|
<a href="https://tolgee.io/?utm_source=github_dokploy&utm_medium=banner&utm_campaign=dokploy" target="_blank" style="display: inline-block; margin-right: 10px;"><img src="https://dokploy.com/tolgee-logo.png" alt="Tolgee" height="80"/></a>
|
||||||
<img src=".github/sponsors/american-cloud.png" alt="AmericanCloud" height="70"/>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a href="https://tolgee.io/?utm_source=github_dokploy&utm_medium=banner&utm_campaign=dokploy" target="_blank" style="display: inline-block; margin-right: 10px;">
|
|
||||||
<img src="https://dokploy.com/tolgee-logo.png" alt="Tolgee" height="80"/>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<!-- Elite Contributors 🥈 -->
|
<!-- Elite Contributors 🥈 -->
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
# License
|
|
||||||
|
|
||||||
## Core License (Apache License 2.0)
|
|
||||||
|
|
||||||
Copyright 2024 Mauricio Siu.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and limitations under the License.
|
|
||||||
|
|
||||||
## Additional Terms for Specific Features
|
|
||||||
|
|
||||||
The following additional terms apply to the multi-node support, Docker Compose file, Preview Deployments and Multi Server features of Dokploy. In the event of a conflict, these provisions shall take precedence over those in the Apache License:
|
|
||||||
|
|
||||||
- **Self-Hosted Version Free**: All features of Dokploy, including multi-node support, Docker Compose file support, Preview Deployments and Multi Server, will always be free to use in the self-hosted version.
|
|
||||||
- **Restriction on Resale**: The multi-node support, Docker Compose file support, Preview Deployments and Multi Server features cannot be sold or offered as a service by any party other than the copyright holder without prior written consent.
|
|
||||||
- **Modification Distribution**: Any modifications to the multi-node support, Docker Compose file support, Preview Deployments and Multi Server features must be distributed freely and cannot be sold or offered as a service.
|
|
||||||
|
|
||||||
For further inquiries or permissions, please contact us directly.
|
|
||||||
@@ -130,7 +130,7 @@ const createStringToJSONSchema = (schema: z.ZodTypeAny) => {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
return JSON.parse(str);
|
return JSON.parse(str);
|
||||||
} catch (_e) {
|
} catch {
|
||||||
ctx.addIssue({ code: "custom", message: "Invalid JSON format" });
|
ctx.addIssue({ code: "custom", message: "Invalid JSON format" });
|
||||||
return z.NEVER;
|
return z.NEVER;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ export const ShowImport = ({ composeId }: Props) => {
|
|||||||
composeId,
|
composeId,
|
||||||
});
|
});
|
||||||
setShowModal(false);
|
setShowModal(false);
|
||||||
} catch (_error) {
|
} catch {
|
||||||
toast.error("Error importing template");
|
toast.error("Error importing template");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -126,7 +126,7 @@ export const ShowImport = ({ composeId }: Props) => {
|
|||||||
});
|
});
|
||||||
setTemplateInfo(result);
|
setTemplateInfo(result);
|
||||||
setShowModal(true);
|
setShowModal(true);
|
||||||
} catch (_error) {
|
} catch {
|
||||||
toast.error("Error processing template");
|
toast.error("Error processing template");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -35,6 +35,9 @@ import { z } from "zod";
|
|||||||
|
|
||||||
const AddPortSchema = z.object({
|
const AddPortSchema = z.object({
|
||||||
publishedPort: z.number().int().min(1).max(65535),
|
publishedPort: z.number().int().min(1).max(65535),
|
||||||
|
publishMode: z.enum(["ingress", "host"], {
|
||||||
|
required_error: "Publish mode is required",
|
||||||
|
}),
|
||||||
targetPort: z.number().int().min(1).max(65535),
|
targetPort: z.number().int().min(1).max(65535),
|
||||||
protocol: z.enum(["tcp", "udp"], {
|
protocol: z.enum(["tcp", "udp"], {
|
||||||
required_error: "Protocol is required",
|
required_error: "Protocol is required",
|
||||||
@@ -80,6 +83,7 @@ export const HandlePorts = ({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
form.reset({
|
form.reset({
|
||||||
publishedPort: data?.publishedPort ?? 0,
|
publishedPort: data?.publishedPort ?? 0,
|
||||||
|
publishMode: data?.publishMode ?? "ingress",
|
||||||
targetPort: data?.targetPort ?? 0,
|
targetPort: data?.targetPort ?? 0,
|
||||||
protocol: data?.protocol ?? "tcp",
|
protocol: data?.protocol ?? "tcp",
|
||||||
});
|
});
|
||||||
@@ -165,6 +169,32 @@ export const HandlePorts = ({
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="publishMode"
|
||||||
|
render={({ field }) => {
|
||||||
|
return (
|
||||||
|
<FormItem className="md:col-span-2">
|
||||||
|
<FormLabel>Published Port Mode</FormLabel>
|
||||||
|
<Select
|
||||||
|
onValueChange={field.onChange}
|
||||||
|
value={field.value}
|
||||||
|
>
|
||||||
|
<FormControl>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Select a publish mode for the port" />
|
||||||
|
</SelectTrigger>
|
||||||
|
</FormControl>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value={"ingress"}>Ingress</SelectItem>
|
||||||
|
<SelectItem value={"host"}>Host</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="targetPort"
|
name="targetPort"
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ export const ShowPorts = ({ applicationId }: Props) => {
|
|||||||
{data?.ports.map((port) => (
|
{data?.ports.map((port) => (
|
||||||
<div key={port.portId}>
|
<div key={port.portId}>
|
||||||
<div className="flex w-full flex-col sm:flex-row sm:items-center justify-between gap-4 sm:gap-10 border rounded-lg p-4">
|
<div className="flex w-full flex-col sm:flex-row sm:items-center justify-between gap-4 sm:gap-10 border rounded-lg p-4">
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 flex-col gap-4 sm:gap-8">
|
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 flex-col gap-4 sm:gap-8">
|
||||||
<div className="flex flex-col gap-1">
|
<div className="flex flex-col gap-1">
|
||||||
<span className="font-medium">Published Port</span>
|
<span className="font-medium">Published Port</span>
|
||||||
<span className="text-sm text-muted-foreground">
|
<span className="text-sm text-muted-foreground">
|
||||||
@@ -68,7 +68,13 @@ export const ShowPorts = ({ applicationId }: Props) => {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-1">
|
<div className="flex flex-col gap-1">
|
||||||
<span className="font-medium"> Target Port</span>
|
<span className="font-medium">Published Port Mode</span>
|
||||||
|
<span className="text-sm text-muted-foreground">
|
||||||
|
{port?.publishMode?.toUpperCase()}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
|
<span className="font-medium">Target Port</span>
|
||||||
<span className="text-sm text-muted-foreground">
|
<span className="text-sm text-muted-foreground">
|
||||||
{port.targetPort}
|
{port.targetPort}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ export const ComposeFileEditor = ({ composeId }: Props) => {
|
|||||||
composeId,
|
composeId,
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch((_e) => {
|
.catch(() => {
|
||||||
toast.error("Error updating the Compose config");
|
toast.error("Error updating the Compose config");
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ export const ShowConvertedCompose = ({ composeId }: Props) => {
|
|||||||
.then(() => {
|
.then(() => {
|
||||||
refetch();
|
refetch();
|
||||||
})
|
})
|
||||||
.catch((_err) => {});
|
.catch(() => {});
|
||||||
}
|
}
|
||||||
}, [isOpen]);
|
}, [isOpen]);
|
||||||
|
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ export const AddApplication = ({ projectId, projectName }: Props) => {
|
|||||||
projectId,
|
projectId,
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch((_e) => {
|
.catch(() => {
|
||||||
toast.error("Error creating the service");
|
toast.error("Error creating the service");
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1063,7 +1063,7 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
toast.success("Connection Success");
|
toast.success("Connection Success");
|
||||||
} catch (_err) {
|
} catch {
|
||||||
toast.error("Error testing the provider");
|
toast.error("Error testing the provider");
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ export const Disable2FA = () => {
|
|||||||
toast.success("2FA disabled successfully");
|
toast.success("2FA disabled successfully");
|
||||||
utils.user.get.invalidate();
|
utils.user.get.invalidate();
|
||||||
setIsOpen(false);
|
setIsOpen(false);
|
||||||
} catch (_error) {
|
} catch {
|
||||||
form.setError("password", {
|
form.setError("password", {
|
||||||
message: "Connection error. Please try again.",
|
message: "Connection error. Please try again.",
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ export const ToggleDockerCleanup = ({ serverId }: Props) => {
|
|||||||
await refetch();
|
await refetch();
|
||||||
}
|
}
|
||||||
toast.success("Docker Cleanup updated");
|
toast.success("Docker Cleanup updated");
|
||||||
} catch (_error) {
|
} catch {
|
||||||
toast.error("Docker Cleanup Error");
|
toast.error("Docker Cleanup Error");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ export function GPUSupport({ serverId }: GPUSupportProps) {
|
|||||||
try {
|
try {
|
||||||
await utils.settings.checkGPUStatus.invalidate({ serverId });
|
await utils.settings.checkGPUStatus.invalidate({ serverId });
|
||||||
await refetch();
|
await refetch();
|
||||||
} catch (_error) {
|
} catch {
|
||||||
toast.error("Failed to refresh GPU status");
|
toast.error("Failed to refresh GPU status");
|
||||||
} finally {
|
} finally {
|
||||||
setIsRefreshing(false);
|
setIsRefreshing(false);
|
||||||
@@ -74,7 +74,7 @@ export function GPUSupport({ serverId }: GPUSupportProps) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await setupGPU.mutateAsync({ serverId });
|
await setupGPU.mutateAsync({ serverId });
|
||||||
} catch (_error) {
|
} catch {
|
||||||
// Error handling is done in mutation's onError
|
// Error handling is done in mutation's onError
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -146,7 +146,7 @@ export const ShowInvitations = () => {
|
|||||||
{invitation.status === "pending" && (
|
{invitation.status === "pending" && (
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
className="w-full cursor-pointer"
|
className="w-full cursor-pointer"
|
||||||
onSelect={(_e) => {
|
onSelect={() => {
|
||||||
copy(
|
copy(
|
||||||
`${origin}/invitation?token=${invitation.id}`,
|
`${origin}/invitation?token=${invitation.id}`,
|
||||||
);
|
);
|
||||||
@@ -162,7 +162,7 @@ export const ShowInvitations = () => {
|
|||||||
{invitation.status === "pending" && (
|
{invitation.status === "pending" && (
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
className="w-full cursor-pointer"
|
className="w-full cursor-pointer"
|
||||||
onSelect={async (_e) => {
|
onSelect={async () => {
|
||||||
const result =
|
const result =
|
||||||
await authClient.organization.cancelInvitation(
|
await authClient.organization.cancelInvitation(
|
||||||
{
|
{
|
||||||
@@ -189,7 +189,7 @@ export const ShowInvitations = () => {
|
|||||||
)}
|
)}
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
className="w-full cursor-pointer"
|
className="w-full cursor-pointer"
|
||||||
onSelect={async (_e) => {
|
onSelect={async () => {
|
||||||
await removeInvitation({
|
await removeInvitation({
|
||||||
invitationId: invitation.id,
|
invitationId: invitation.id,
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ export const ManageTraefikPorts = ({ children, serverId }: Props) => {
|
|||||||
});
|
});
|
||||||
toast.success(t("settings.server.webServer.traefik.portsUpdated"));
|
toast.success(t("settings.server.webServer.traefik.portsUpdated"));
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
} catch (_error) {}
|
} catch {}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ const PopoverContent = React.forwardRef<
|
|||||||
align={align}
|
align={align}
|
||||||
sideOffset={sideOffset}
|
sideOffset={sideOffset}
|
||||||
className={cn(
|
className={cn(
|
||||||
"z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
"z-50 w-full rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
2
apps/dokploy/drizzle/0099_wise_golden_guardian.sql
Normal file
2
apps/dokploy/drizzle/0099_wise_golden_guardian.sql
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
CREATE TYPE "public"."publishModeType" AS ENUM('ingress', 'host');--> statement-breakpoint
|
||||||
|
ALTER TABLE "port" ADD COLUMN "publishMode" "publishModeType" DEFAULT 'host' NOT NULL;
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"id": "4d928914-5268-4921-aa21-c1b087cc15cc",
|
"id": "71f68c87-ddb4-4e8c-b9fc-1db7fbcedf56",
|
||||||
"prevId": "edde8c54-b715-4db6-bc3a-85d435226083",
|
"prevId": "edde8c54-b715-4db6-bc3a-85d435226083",
|
||||||
"version": "7",
|
"version": "7",
|
||||||
"dialect": "postgresql",
|
"dialect": "postgresql",
|
||||||
@@ -1209,20 +1209,6 @@
|
|||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true,
|
||||||
"default": "'none'"
|
"default": "'none'"
|
||||||
},
|
|
||||||
"internalPath": {
|
|
||||||
"name": "internalPath",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false,
|
|
||||||
"default": "'/'"
|
|
||||||
},
|
|
||||||
"stripPath": {
|
|
||||||
"name": "stripPath",
|
|
||||||
"type": "boolean",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true,
|
|
||||||
"default": false
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"indexes": {},
|
"indexes": {},
|
||||||
@@ -2847,6 +2833,14 @@
|
|||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true
|
"notNull": true
|
||||||
},
|
},
|
||||||
|
"publishMode": {
|
||||||
|
"name": "publishMode",
|
||||||
|
"type": "publishModeType",
|
||||||
|
"typeSchema": "public",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "'host'"
|
||||||
|
},
|
||||||
"targetPort": {
|
"targetPort": {
|
||||||
"name": "targetPort",
|
"name": "targetPort",
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
@@ -5730,6 +5724,14 @@
|
|||||||
"udp"
|
"udp"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"public.publishModeType": {
|
||||||
|
"name": "publishModeType",
|
||||||
|
"schema": "public",
|
||||||
|
"values": [
|
||||||
|
"ingress",
|
||||||
|
"host"
|
||||||
|
]
|
||||||
|
},
|
||||||
"public.applicationStatus": {
|
"public.applicationStatus": {
|
||||||
"name": "applicationStatus",
|
"name": "applicationStatus",
|
||||||
"schema": "public",
|
"schema": "public",
|
||||||
|
|||||||
@@ -698,8 +698,8 @@
|
|||||||
{
|
{
|
||||||
"idx": 99,
|
"idx": 99,
|
||||||
"version": "7",
|
"version": "7",
|
||||||
"when": 1751293280505,
|
"when": 1751693569786,
|
||||||
"tag": "0099_fast_the_order",
|
"tag": "0099_wise_golden_guardian",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "dokploy",
|
"name": "dokploy",
|
||||||
"version": "v0.23.6",
|
"version": "v0.23.7",
|
||||||
"private": true,
|
"private": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ export async function getServerSideProps(
|
|||||||
trpcState: helpers.dehydrate(),
|
trpcState: helpers.dehydrate(),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
} catch (_error) {
|
} catch {
|
||||||
return {
|
return {
|
||||||
props: {},
|
props: {},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -390,7 +390,7 @@ const Project = (
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
success++;
|
success++;
|
||||||
} catch (_error) {
|
} catch {
|
||||||
toast.error(`Error starting service ${serviceId}`);
|
toast.error(`Error starting service ${serviceId}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -437,7 +437,7 @@ const Project = (
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
success++;
|
success++;
|
||||||
} catch (_error) {
|
} catch {
|
||||||
toast.error(`Error stopping service ${serviceId}`);
|
toast.error(`Error stopping service ${serviceId}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1107,7 +1107,7 @@ export async function getServerSideProps(
|
|||||||
projectId: params?.projectId,
|
projectId: params?.projectId,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
} catch (_error) {
|
} catch {
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: false,
|
permanent: false,
|
||||||
|
|||||||
@@ -413,7 +413,7 @@ export async function getServerSideProps(
|
|||||||
activeTab: (activeTab || "general") as TabState,
|
activeTab: (activeTab || "general") as TabState,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
} catch (_error) {
|
} catch {
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: false,
|
permanent: false,
|
||||||
|
|||||||
@@ -409,7 +409,7 @@ export async function getServerSideProps(
|
|||||||
activeTab: (activeTab || "general") as TabState,
|
activeTab: (activeTab || "general") as TabState,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
} catch (_error) {
|
} catch {
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: false,
|
permanent: false,
|
||||||
|
|||||||
@@ -338,7 +338,7 @@ export async function getServerSideProps(
|
|||||||
activeTab: (activeTab || "general") as TabState,
|
activeTab: (activeTab || "general") as TabState,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
} catch (_error) {
|
} catch {
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: false,
|
permanent: false,
|
||||||
|
|||||||
@@ -340,7 +340,7 @@ export async function getServerSideProps(
|
|||||||
activeTab: (activeTab || "general") as TabState,
|
activeTab: (activeTab || "general") as TabState,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
} catch (_error) {
|
} catch {
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: false,
|
permanent: false,
|
||||||
|
|||||||
@@ -324,7 +324,7 @@ export async function getServerSideProps(
|
|||||||
activeTab: (activeTab || "general") as TabState,
|
activeTab: (activeTab || "general") as TabState,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
} catch (_error) {
|
} catch {
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: false,
|
permanent: false,
|
||||||
|
|||||||
@@ -321,7 +321,7 @@ export async function getServerSideProps(
|
|||||||
activeTab: (activeTab || "general") as TabState,
|
activeTab: (activeTab || "general") as TabState,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
} catch (_error) {
|
} catch {
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: false,
|
permanent: false,
|
||||||
|
|||||||
@@ -328,7 +328,7 @@ export async function getServerSideProps(
|
|||||||
activeTab: (activeTab || "general") as TabState,
|
activeTab: (activeTab || "general") as TabState,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
} catch (_error) {
|
} catch {
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: false,
|
permanent: false,
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ export async function getServerSideProps(
|
|||||||
trpcState: helpers.dehydrate(),
|
trpcState: helpers.dehydrate(),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
} catch (_error) {
|
} catch {
|
||||||
return {
|
return {
|
||||||
props: {},
|
props: {},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ export async function getServerSideProps(
|
|||||||
trpcState: helpers.dehydrate(),
|
trpcState: helpers.dehydrate(),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
} catch (_error) {
|
} catch {
|
||||||
return {
|
return {
|
||||||
props: {},
|
props: {},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ export async function getServerSideProps(
|
|||||||
trpcState: helpers.dehydrate(),
|
trpcState: helpers.dehydrate(),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
} catch (_error) {
|
} catch {
|
||||||
return {
|
return {
|
||||||
props: {},
|
props: {},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ export async function getServerSideProps(
|
|||||||
trpcState: helpers.dehydrate(),
|
trpcState: helpers.dehydrate(),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
} catch (_error) {
|
} catch {
|
||||||
return {
|
return {
|
||||||
props: {},
|
props: {},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ export default function Home({ IS_CLOUD }: Props) {
|
|||||||
|
|
||||||
toast.success("Logged in successfully");
|
toast.success("Logged in successfully");
|
||||||
router.push("/dashboard/projects");
|
router.push("/dashboard/projects");
|
||||||
} catch (_error) {
|
} catch {
|
||||||
toast.error("An error occurred while logging in");
|
toast.error("An error occurred while logging in");
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoginLoading(false);
|
setIsLoginLoading(false);
|
||||||
@@ -124,7 +124,7 @@ export default function Home({ IS_CLOUD }: Props) {
|
|||||||
|
|
||||||
toast.success("Logged in successfully");
|
toast.success("Logged in successfully");
|
||||||
router.push("/dashboard/projects");
|
router.push("/dashboard/projects");
|
||||||
} catch (_error) {
|
} catch {
|
||||||
toast.error("An error occurred while verifying 2FA code");
|
toast.error("An error occurred while verifying 2FA code");
|
||||||
} finally {
|
} finally {
|
||||||
setIsTwoFactorLoading(false);
|
setIsTwoFactorLoading(false);
|
||||||
@@ -154,7 +154,7 @@ export default function Home({ IS_CLOUD }: Props) {
|
|||||||
|
|
||||||
toast.success("Logged in successfully");
|
toast.success("Logged in successfully");
|
||||||
router.push("/dashboard/projects");
|
router.push("/dashboard/projects");
|
||||||
} catch (_error) {
|
} catch {
|
||||||
toast.error("An error occurred while verifying backup code");
|
toast.error("An error occurred while verifying backup code");
|
||||||
} finally {
|
} finally {
|
||||||
setIsBackupCodeLoading(false);
|
setIsBackupCodeLoading(false);
|
||||||
@@ -478,7 +478,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} catch (_error) {}
|
} catch {}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ const Invitation = ({
|
|||||||
|
|
||||||
toast.success("Account created successfully");
|
toast.success("Account created successfully");
|
||||||
router.push("/dashboard/projects");
|
router.push("/dashboard/projects");
|
||||||
} catch (_error) {
|
} catch {
|
||||||
toast.error("An error occurred while creating your account");
|
toast.error("An error occurred while creating your account");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
containerRestart,
|
containerRestart,
|
||||||
|
findServerById,
|
||||||
getConfig,
|
getConfig,
|
||||||
getContainers,
|
getContainers,
|
||||||
getContainersByAppLabel,
|
getContainersByAppLabel,
|
||||||
@@ -9,6 +10,9 @@ import {
|
|||||||
} from "@dokploy/server";
|
} from "@dokploy/server";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { createTRPCRouter, protectedProcedure } from "../trpc";
|
import { createTRPCRouter, protectedProcedure } from "../trpc";
|
||||||
|
import { TRPCError } from "@trpc/server";
|
||||||
|
|
||||||
|
export const containerIdRegex = /^[a-zA-Z0-9.\-_]+$/;
|
||||||
|
|
||||||
export const dockerRouter = createTRPCRouter({
|
export const dockerRouter = createTRPCRouter({
|
||||||
getContainers: protectedProcedure
|
getContainers: protectedProcedure
|
||||||
@@ -17,14 +21,23 @@ export const dockerRouter = createTRPCRouter({
|
|||||||
serverId: z.string().optional(),
|
serverId: z.string().optional(),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.query(async ({ input }) => {
|
.query(async ({ input, ctx }) => {
|
||||||
|
if (input.serverId) {
|
||||||
|
const server = await findServerById(input.serverId);
|
||||||
|
if (server.organizationId !== ctx.session?.activeOrganizationId) {
|
||||||
|
throw new TRPCError({ code: "UNAUTHORIZED" });
|
||||||
|
}
|
||||||
|
}
|
||||||
return await getContainers(input.serverId);
|
return await getContainers(input.serverId);
|
||||||
}),
|
}),
|
||||||
|
|
||||||
restartContainer: protectedProcedure
|
restartContainer: protectedProcedure
|
||||||
.input(
|
.input(
|
||||||
z.object({
|
z.object({
|
||||||
containerId: z.string().min(1),
|
containerId: z
|
||||||
|
.string()
|
||||||
|
.min(1)
|
||||||
|
.regex(containerIdRegex, "Invalid container id."),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input }) => {
|
||||||
@@ -34,11 +47,20 @@ export const dockerRouter = createTRPCRouter({
|
|||||||
getConfig: protectedProcedure
|
getConfig: protectedProcedure
|
||||||
.input(
|
.input(
|
||||||
z.object({
|
z.object({
|
||||||
containerId: z.string().min(1),
|
containerId: z
|
||||||
|
.string()
|
||||||
|
.min(1)
|
||||||
|
.regex(containerIdRegex, "Invalid container id."),
|
||||||
serverId: z.string().optional(),
|
serverId: z.string().optional(),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.query(async ({ input }) => {
|
.query(async ({ input, ctx }) => {
|
||||||
|
if (input.serverId) {
|
||||||
|
const server = await findServerById(input.serverId);
|
||||||
|
if (server.organizationId !== ctx.session?.activeOrganizationId) {
|
||||||
|
throw new TRPCError({ code: "UNAUTHORIZED" });
|
||||||
|
}
|
||||||
|
}
|
||||||
return await getConfig(input.containerId, input.serverId);
|
return await getConfig(input.containerId, input.serverId);
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@@ -48,11 +70,17 @@ export const dockerRouter = createTRPCRouter({
|
|||||||
appType: z
|
appType: z
|
||||||
.union([z.literal("stack"), z.literal("docker-compose")])
|
.union([z.literal("stack"), z.literal("docker-compose")])
|
||||||
.optional(),
|
.optional(),
|
||||||
appName: z.string().min(1),
|
appName: z.string().min(1).regex(containerIdRegex, "Invalid app name."),
|
||||||
serverId: z.string().optional(),
|
serverId: z.string().optional(),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.query(async ({ input }) => {
|
.query(async ({ input, ctx }) => {
|
||||||
|
if (input.serverId) {
|
||||||
|
const server = await findServerById(input.serverId);
|
||||||
|
if (server.organizationId !== ctx.session?.activeOrganizationId) {
|
||||||
|
throw new TRPCError({ code: "UNAUTHORIZED" });
|
||||||
|
}
|
||||||
|
}
|
||||||
return await getContainersByAppNameMatch(
|
return await getContainersByAppNameMatch(
|
||||||
input.appName,
|
input.appName,
|
||||||
input.appType,
|
input.appType,
|
||||||
@@ -63,12 +91,18 @@ export const dockerRouter = createTRPCRouter({
|
|||||||
getContainersByAppLabel: protectedProcedure
|
getContainersByAppLabel: protectedProcedure
|
||||||
.input(
|
.input(
|
||||||
z.object({
|
z.object({
|
||||||
appName: z.string().min(1),
|
appName: z.string().min(1).regex(containerIdRegex, "Invalid app name."),
|
||||||
serverId: z.string().optional(),
|
serverId: z.string().optional(),
|
||||||
type: z.enum(["standalone", "swarm"]),
|
type: z.enum(["standalone", "swarm"]),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.query(async ({ input }) => {
|
.query(async ({ input, ctx }) => {
|
||||||
|
if (input.serverId) {
|
||||||
|
const server = await findServerById(input.serverId);
|
||||||
|
if (server.organizationId !== ctx.session?.activeOrganizationId) {
|
||||||
|
throw new TRPCError({ code: "UNAUTHORIZED" });
|
||||||
|
}
|
||||||
|
}
|
||||||
return await getContainersByAppLabel(
|
return await getContainersByAppLabel(
|
||||||
input.appName,
|
input.appName,
|
||||||
input.type,
|
input.type,
|
||||||
@@ -79,22 +113,34 @@ export const dockerRouter = createTRPCRouter({
|
|||||||
getStackContainersByAppName: protectedProcedure
|
getStackContainersByAppName: protectedProcedure
|
||||||
.input(
|
.input(
|
||||||
z.object({
|
z.object({
|
||||||
appName: z.string().min(1),
|
appName: z.string().min(1).regex(containerIdRegex, "Invalid app name."),
|
||||||
serverId: z.string().optional(),
|
serverId: z.string().optional(),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.query(async ({ input }) => {
|
.query(async ({ input, ctx }) => {
|
||||||
|
if (input.serverId) {
|
||||||
|
const server = await findServerById(input.serverId);
|
||||||
|
if (server.organizationId !== ctx.session?.activeOrganizationId) {
|
||||||
|
throw new TRPCError({ code: "UNAUTHORIZED" });
|
||||||
|
}
|
||||||
|
}
|
||||||
return await getStackContainersByAppName(input.appName, input.serverId);
|
return await getStackContainersByAppName(input.appName, input.serverId);
|
||||||
}),
|
}),
|
||||||
|
|
||||||
getServiceContainersByAppName: protectedProcedure
|
getServiceContainersByAppName: protectedProcedure
|
||||||
.input(
|
.input(
|
||||||
z.object({
|
z.object({
|
||||||
appName: z.string().min(1),
|
appName: z.string().min(1).regex(containerIdRegex, "Invalid app name."),
|
||||||
serverId: z.string().optional(),
|
serverId: z.string().optional(),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.query(async ({ input }) => {
|
.query(async ({ input, ctx }) => {
|
||||||
|
if (input.serverId) {
|
||||||
|
const server = await findServerById(input.serverId);
|
||||||
|
if (server.organizationId !== ctx.session?.activeOrganizationId) {
|
||||||
|
throw new TRPCError({ code: "UNAUTHORIZED" });
|
||||||
|
}
|
||||||
|
}
|
||||||
return await getServiceContainersByAppName(input.appName, input.serverId);
|
return await getServiceContainersByAppName(input.appName, input.serverId);
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -459,6 +459,15 @@ export const settingsRouter = createTRPCRouter({
|
|||||||
throw new TRPCError({ code: "UNAUTHORIZED" });
|
throw new TRPCError({ code: "UNAUTHORIZED" });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (input.serverId) {
|
||||||
|
const server = await findServerById(input.serverId);
|
||||||
|
|
||||||
|
if (server.organizationId !== ctx.session?.activeOrganizationId) {
|
||||||
|
throw new TRPCError({ code: "UNAUTHORIZED" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return readConfigInPath(input.path, input.serverId);
|
return readConfigInPath(input.path, input.serverId);
|
||||||
}),
|
}),
|
||||||
getIp: protectedProcedure.query(async ({ ctx }) => {
|
getIp: protectedProcedure.query(async ({ ctx }) => {
|
||||||
|
|||||||
@@ -6,6 +6,9 @@ import {
|
|||||||
} from "@dokploy/server";
|
} from "@dokploy/server";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { createTRPCRouter, protectedProcedure } from "../trpc";
|
import { createTRPCRouter, protectedProcedure } from "../trpc";
|
||||||
|
import { TRPCError } from "@trpc/server";
|
||||||
|
import { findServerById } from "@dokploy/server";
|
||||||
|
import { containerIdRegex } from "./docker";
|
||||||
|
|
||||||
export const swarmRouter = createTRPCRouter({
|
export const swarmRouter = createTRPCRouter({
|
||||||
getNodes: protectedProcedure
|
getNodes: protectedProcedure
|
||||||
@@ -14,12 +17,24 @@ export const swarmRouter = createTRPCRouter({
|
|||||||
serverId: z.string().optional(),
|
serverId: z.string().optional(),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.query(async ({ input }) => {
|
.query(async ({ input, ctx }) => {
|
||||||
|
if (input.serverId) {
|
||||||
|
const server = await findServerById(input.serverId);
|
||||||
|
if (server.organizationId !== ctx.session?.activeOrganizationId) {
|
||||||
|
throw new TRPCError({ code: "UNAUTHORIZED" });
|
||||||
|
}
|
||||||
|
}
|
||||||
return await getSwarmNodes(input.serverId);
|
return await getSwarmNodes(input.serverId);
|
||||||
}),
|
}),
|
||||||
getNodeInfo: protectedProcedure
|
getNodeInfo: protectedProcedure
|
||||||
.input(z.object({ nodeId: z.string(), serverId: z.string().optional() }))
|
.input(z.object({ nodeId: z.string(), serverId: z.string().optional() }))
|
||||||
.query(async ({ input }) => {
|
.query(async ({ input, ctx }) => {
|
||||||
|
if (input.serverId) {
|
||||||
|
const server = await findServerById(input.serverId);
|
||||||
|
if (server.organizationId !== ctx.session?.activeOrganizationId) {
|
||||||
|
throw new TRPCError({ code: "UNAUTHORIZED" });
|
||||||
|
}
|
||||||
|
}
|
||||||
return await getNodeInfo(input.nodeId, input.serverId);
|
return await getNodeInfo(input.nodeId, input.serverId);
|
||||||
}),
|
}),
|
||||||
getNodeApps: protectedProcedure
|
getNodeApps: protectedProcedure
|
||||||
@@ -28,17 +43,29 @@ export const swarmRouter = createTRPCRouter({
|
|||||||
serverId: z.string().optional(),
|
serverId: z.string().optional(),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.query(async ({ input }) => {
|
.query(async ({ input, ctx }) => {
|
||||||
|
if (input.serverId) {
|
||||||
|
const server = await findServerById(input.serverId);
|
||||||
|
if (server.organizationId !== ctx.session?.activeOrganizationId) {
|
||||||
|
throw new TRPCError({ code: "UNAUTHORIZED" });
|
||||||
|
}
|
||||||
|
}
|
||||||
return getNodeApplications(input.serverId);
|
return getNodeApplications(input.serverId);
|
||||||
}),
|
}),
|
||||||
getAppInfos: protectedProcedure
|
getAppInfos: protectedProcedure
|
||||||
.input(
|
.input(
|
||||||
z.object({
|
z.object({
|
||||||
appName: z.string(),
|
appName: z.string().min(1).regex(containerIdRegex, "Invalid app name."),
|
||||||
serverId: z.string().optional(),
|
serverId: z.string().optional(),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.query(async ({ input }) => {
|
.query(async ({ input, ctx }) => {
|
||||||
|
if (input.serverId) {
|
||||||
|
const server = await findServerById(input.serverId);
|
||||||
|
if (server.organizationId !== ctx.session?.activeOrganizationId) {
|
||||||
|
throw new TRPCError({ code: "UNAUTHORIZED" });
|
||||||
|
}
|
||||||
|
}
|
||||||
return await getApplicationInfo(input.appName, input.serverId);
|
return await getApplicationInfo(input.appName, input.serverId);
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -75,6 +75,24 @@ export const userRouter = createTRPCRouter({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// If user not found in the organization, deny access
|
||||||
|
if (!memberResult) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "NOT_FOUND",
|
||||||
|
message: "User not found in this organization",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow access if:
|
||||||
|
// 1. User is requesting their own information
|
||||||
|
// 2. User has owner role (admin permissions) AND user is in the same organization
|
||||||
|
if (memberResult.userId !== ctx.user.id && ctx.user.role !== "owner") {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "UNAUTHORIZED",
|
||||||
|
message: "You are not authorized to access this user",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return memberResult;
|
return memberResult;
|
||||||
}),
|
}),
|
||||||
get: protectedProcedure.query(async ({ ctx }) => {
|
get: protectedProcedure.query(async ({ ctx }) => {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ export const isWSL = async () => {
|
|||||||
const { stdout } = await execAsync("uname -r");
|
const { stdout } = await execAsync("uname -r");
|
||||||
const isWSL = stdout.includes("microsoft");
|
const isWSL = stdout.includes("microsoft");
|
||||||
return isWSL;
|
return isWSL;
|
||||||
} catch (_error) {
|
} catch {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ export const setupDeploymentLogsWebSocketServer = (
|
|||||||
ws.close();
|
ws.close();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (_error) {
|
} catch {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
// const errorMessage = error?.message as unknown as string;
|
// const errorMessage = error?.message as unknown as string;
|
||||||
// ws.send(errorMessage);
|
// ws.send(errorMessage);
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
initializeNetwork,
|
initializeNetwork,
|
||||||
initializeSwarm,
|
initializeSwarm,
|
||||||
} from "@dokploy/server/setup/setup";
|
} from "@dokploy/server/setup/setup";
|
||||||
|
import { execAsync } from "@dokploy/server";
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
setupDirectories();
|
setupDirectories();
|
||||||
@@ -20,6 +21,7 @@ import {
|
|||||||
await initializeNetwork();
|
await initializeNetwork();
|
||||||
createDefaultTraefikConfig();
|
createDefaultTraefikConfig();
|
||||||
createDefaultServerTraefikConfig();
|
createDefaultServerTraefikConfig();
|
||||||
|
await execAsync("docker pull traefik:v3.1.2");
|
||||||
await initializeTraefik();
|
await initializeTraefik();
|
||||||
await initializeRedis();
|
await initializeRedis();
|
||||||
await initializePostgres();
|
await initializePostgres();
|
||||||
|
|||||||
@@ -242,3 +242,8 @@
|
|||||||
background-color: var(--terminal-paste) !important;
|
background-color: var(--terminal-paste) !important;
|
||||||
color: currentColor !important;
|
color: currentColor !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cm-content,
|
||||||
|
.cm-lineWrapping {
|
||||||
|
@apply font-mono;
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright 2025 Mauricio Siu.
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -6,6 +6,7 @@ import { z } from "zod";
|
|||||||
import { applications } from "./application";
|
import { applications } from "./application";
|
||||||
|
|
||||||
export const protocolType = pgEnum("protocolType", ["tcp", "udp"]);
|
export const protocolType = pgEnum("protocolType", ["tcp", "udp"]);
|
||||||
|
export const publishModeType = pgEnum("publishModeType", ["ingress", "host"]);
|
||||||
|
|
||||||
export const ports = pgTable("port", {
|
export const ports = pgTable("port", {
|
||||||
portId: text("portId")
|
portId: text("portId")
|
||||||
@@ -13,6 +14,7 @@ export const ports = pgTable("port", {
|
|||||||
.primaryKey()
|
.primaryKey()
|
||||||
.$defaultFn(() => nanoid()),
|
.$defaultFn(() => nanoid()),
|
||||||
publishedPort: integer("publishedPort").notNull(),
|
publishedPort: integer("publishedPort").notNull(),
|
||||||
|
publishMode: publishModeType("publishMode").notNull().default("host"),
|
||||||
targetPort: integer("targetPort").notNull(),
|
targetPort: integer("targetPort").notNull(),
|
||||||
protocol: protocolType("protocol").notNull(),
|
protocol: protocolType("protocol").notNull(),
|
||||||
|
|
||||||
@@ -32,6 +34,7 @@ const createSchema = createInsertSchema(ports, {
|
|||||||
portId: z.string().min(1),
|
portId: z.string().min(1),
|
||||||
applicationId: z.string().min(1),
|
applicationId: z.string().min(1),
|
||||||
publishedPort: z.number(),
|
publishedPort: z.number(),
|
||||||
|
publishMode: z.enum(["ingress", "host"]).default("ingress"),
|
||||||
targetPort: z.number(),
|
targetPort: z.number(),
|
||||||
protocol: z.enum(["tcp", "udp"]).default("tcp"),
|
protocol: z.enum(["tcp", "udp"]).default("tcp"),
|
||||||
});
|
});
|
||||||
@@ -39,6 +42,7 @@ const createSchema = createInsertSchema(ports, {
|
|||||||
export const apiCreatePort = createSchema
|
export const apiCreatePort = createSchema
|
||||||
.pick({
|
.pick({
|
||||||
publishedPort: true,
|
publishedPort: true,
|
||||||
|
publishMode: true,
|
||||||
targetPort: true,
|
targetPort: true,
|
||||||
protocol: true,
|
protocol: true,
|
||||||
applicationId: true,
|
applicationId: true,
|
||||||
@@ -55,6 +59,7 @@ export const apiUpdatePort = createSchema
|
|||||||
.pick({
|
.pick({
|
||||||
portId: true,
|
portId: true,
|
||||||
publishedPort: true,
|
publishedPort: true,
|
||||||
|
publishMode: true,
|
||||||
targetPort: true,
|
targetPort: true,
|
||||||
protocol: true,
|
protocol: true,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import { backups } from "./backups";
|
|||||||
import { projects } from "./project";
|
import { projects } from "./project";
|
||||||
import { schedules } from "./schedule";
|
import { schedules } from "./schedule";
|
||||||
import { certificateType } from "./shared";
|
import { certificateType } from "./shared";
|
||||||
|
import { paths } from "@dokploy/server/constants";
|
||||||
/**
|
/**
|
||||||
* This is an example of how to use the multi-project schema feature of Drizzle ORM. Use the same
|
* This is an example of how to use the multi-project schema feature of Drizzle ORM. Use the same
|
||||||
* database instance for multiple projects.
|
* database instance for multiple projects.
|
||||||
@@ -236,7 +237,31 @@ export const apiModifyTraefikConfig = z.object({
|
|||||||
serverId: z.string().optional(),
|
serverId: z.string().optional(),
|
||||||
});
|
});
|
||||||
export const apiReadTraefikConfig = z.object({
|
export const apiReadTraefikConfig = z.object({
|
||||||
path: z.string().min(1),
|
path: z
|
||||||
|
.string()
|
||||||
|
.min(1)
|
||||||
|
.refine(
|
||||||
|
(path) => {
|
||||||
|
// Prevent directory traversal attacks
|
||||||
|
if (path.includes("../") || path.includes("..\\")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { MAIN_TRAEFIK_PATH } = paths();
|
||||||
|
if (path.startsWith("/") && !path.startsWith(MAIN_TRAEFIK_PATH)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Prevent null bytes and other dangerous characters
|
||||||
|
if (path.includes("\0") || path.includes("\x00")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message:
|
||||||
|
"Invalid path: path traversal or unauthorized directory access detected",
|
||||||
|
},
|
||||||
|
),
|
||||||
serverId: z.string().optional(),
|
serverId: z.string().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ export const readStatsFile = async (
|
|||||||
const filePath = `${MONITORING_PATH}/${appName}/${statType}.json`;
|
const filePath = `${MONITORING_PATH}/${appName}/${statType}.json`;
|
||||||
const data = await promises.readFile(filePath, "utf-8");
|
const data = await promises.readFile(filePath, "utf-8");
|
||||||
return JSON.parse(data);
|
return JSON.parse(data);
|
||||||
} catch (_error) {
|
} catch {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -108,7 +108,7 @@ export const readLastValueStatsFile = async (
|
|||||||
const data = await promises.readFile(filePath, "utf-8");
|
const data = await promises.readFile(filePath, "utf-8");
|
||||||
const stats = JSON.parse(data);
|
const stats = JSON.parse(data);
|
||||||
return stats[stats.length - 1] || null;
|
return stats[stats.length - 1] || null;
|
||||||
} catch (_error) {
|
} catch {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ export const getConfig = async (
|
|||||||
const config = JSON.parse(stdout);
|
const config = JSON.parse(stdout);
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
} catch (_error) {}
|
} catch {}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getContainersByAppNameMatch = async (
|
export const getContainersByAppNameMatch = async (
|
||||||
@@ -156,7 +156,7 @@ export const getContainersByAppNameMatch = async (
|
|||||||
});
|
});
|
||||||
|
|
||||||
return containers || [];
|
return containers || [];
|
||||||
} catch (_error) {}
|
} catch {}
|
||||||
|
|
||||||
return [];
|
return [];
|
||||||
};
|
};
|
||||||
@@ -214,7 +214,7 @@ export const getStackContainersByAppName = async (
|
|||||||
});
|
});
|
||||||
|
|
||||||
return containers || [];
|
return containers || [];
|
||||||
} catch (_error) {}
|
} catch {}
|
||||||
|
|
||||||
return [];
|
return [];
|
||||||
};
|
};
|
||||||
@@ -274,7 +274,7 @@ export const getServiceContainersByAppName = async (
|
|||||||
});
|
});
|
||||||
|
|
||||||
return containers || [];
|
return containers || [];
|
||||||
} catch (_error) {}
|
} catch {}
|
||||||
|
|
||||||
return [];
|
return [];
|
||||||
};
|
};
|
||||||
@@ -331,7 +331,7 @@ export const getContainersByAppLabel = async (
|
|||||||
});
|
});
|
||||||
|
|
||||||
return containers || [];
|
return containers || [];
|
||||||
} catch (_error) {}
|
} catch {}
|
||||||
|
|
||||||
return [];
|
return [];
|
||||||
};
|
};
|
||||||
@@ -350,7 +350,7 @@ export const containerRestart = async (containerId: string) => {
|
|||||||
const config = JSON.parse(stdout);
|
const config = JSON.parse(stdout);
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
} catch (_error) {}
|
} catch {}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getSwarmNodes = async (serverId?: string) => {
|
export const getSwarmNodes = async (serverId?: string) => {
|
||||||
@@ -379,7 +379,7 @@ export const getSwarmNodes = async (serverId?: string) => {
|
|||||||
.split("\n")
|
.split("\n")
|
||||||
.map((line) => JSON.parse(line));
|
.map((line) => JSON.parse(line));
|
||||||
return nodesArray;
|
return nodesArray;
|
||||||
} catch (_error) {}
|
} catch {}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getNodeInfo = async (nodeId: string, serverId?: string) => {
|
export const getNodeInfo = async (nodeId: string, serverId?: string) => {
|
||||||
@@ -405,7 +405,7 @@ export const getNodeInfo = async (nodeId: string, serverId?: string) => {
|
|||||||
const nodeInfo = JSON.parse(stdout);
|
const nodeInfo = JSON.parse(stdout);
|
||||||
|
|
||||||
return nodeInfo;
|
return nodeInfo;
|
||||||
} catch (_error) {}
|
} catch {}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getNodeApplications = async (serverId?: string) => {
|
export const getNodeApplications = async (serverId?: string) => {
|
||||||
@@ -437,7 +437,7 @@ export const getNodeApplications = async (serverId?: string) => {
|
|||||||
.filter((service) => !service.Name.startsWith("dokploy-"));
|
.filter((service) => !service.Name.startsWith("dokploy-"));
|
||||||
|
|
||||||
return appArray;
|
return appArray;
|
||||||
} catch (_error) {}
|
} catch {}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getApplicationInfo = async (
|
export const getApplicationInfo = async (
|
||||||
@@ -470,5 +470,5 @@ export const getApplicationInfo = async (
|
|||||||
.map((line) => JSON.parse(line));
|
.map((line) => JSON.parse(line));
|
||||||
|
|
||||||
return appArray;
|
return appArray;
|
||||||
} catch (_error) {}
|
} catch {}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ export const issueCommentExists = async ({
|
|||||||
comment_id: comment_id,
|
comment_id: comment_id,
|
||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
} catch (_error) {
|
} catch {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -212,7 +212,7 @@ export const deleteFileMount = async (mountId: string) => {
|
|||||||
} else {
|
} else {
|
||||||
await removeFileOrDirectory(fullPath);
|
await removeFileOrDirectory(fullPath);
|
||||||
}
|
}
|
||||||
} catch (_error) {}
|
} catch {}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getBaseFilesPath = async (mountId: string) => {
|
export const getBaseFilesPath = async (mountId: string) => {
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ export const removePreviewDeployment = async (previewDeploymentId: string) => {
|
|||||||
for (const operation of cleanupOperations) {
|
for (const operation of cleanupOperations) {
|
||||||
try {
|
try {
|
||||||
await operation();
|
await operation();
|
||||||
} catch (_error) {}
|
} catch {}
|
||||||
}
|
}
|
||||||
return deployment[0];
|
return deployment[0];
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -195,7 +195,7 @@ const rollbackApplication = async (
|
|||||||
ForceUpdate: inspect.Spec.TaskTemplate.ForceUpdate + 1,
|
ForceUpdate: inspect.Spec.TaskTemplate.ForceUpdate + 1,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
} catch (_error: unknown) {
|
} catch {
|
||||||
await docker.createService(settings);
|
await docker.createService(settings);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ export const setupMonitoring = async (serverId: string) => {
|
|||||||
await container.inspect();
|
await container.inspect();
|
||||||
await container.remove({ force: true });
|
await container.remove({ force: true });
|
||||||
console.log("Removed existing container");
|
console.log("Removed existing container");
|
||||||
} catch (_error) {
|
} catch {
|
||||||
// Container doesn't exist, continue
|
// Container doesn't exist, continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,7 +135,7 @@ export const setupWebMonitoring = async (userId: string) => {
|
|||||||
await container.inspect();
|
await container.inspect();
|
||||||
await container.remove({ force: true });
|
await container.remove({ force: true });
|
||||||
console.log("Removed existing container");
|
console.log("Removed existing container");
|
||||||
} catch (_error) {}
|
} catch {}
|
||||||
|
|
||||||
await docker.createContainer(settings);
|
await docker.createContainer(settings);
|
||||||
const newContainer = docker.getContainer(containerName);
|
const newContainer = docker.getContainer(containerName);
|
||||||
|
|||||||
@@ -419,14 +419,26 @@ if ! [ -x "$(command -v docker)" ]; then
|
|||||||
systemctl enable docker >/dev/null 2>&1
|
systemctl enable docker >/dev/null 2>&1
|
||||||
;;
|
;;
|
||||||
"opencloudos")
|
"opencloudos")
|
||||||
dnf config-manager --add-repo=https://download.docker.com/linux/centos/docker-ce.repo >/dev/null 2>&1
|
# Special handling for OpenCloud OS
|
||||||
dnf install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin >/dev/null 2>&1
|
echo " - Installing Docker for OpenCloud OS..."
|
||||||
|
dnf install -y docker >/dev/null 2>&1
|
||||||
if ! [ -x "$(command -v docker)" ]; then
|
if ! [ -x "$(command -v docker)" ]; then
|
||||||
echo " - Docker could not be installed automatically. Please visit https://docs.docker.com/engine/install/ and install Docker manually to continue."
|
echo " - Docker could not be installed automatically. Please visit https://docs.docker.com/engine/install/ and install Docker manually to continue."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
systemctl start docker >/dev/null 2>&1
|
|
||||||
|
# Remove --live-restore parameter from Docker configuration if it exists
|
||||||
|
if [ -f "/etc/sysconfig/docker" ]; then
|
||||||
|
echo " - Removing --live-restore parameter from Docker configuration..."
|
||||||
|
sed -i 's/--live-restore[^[:space:]]*//' /etc/sysconfig/docker >/dev/null 2>&1
|
||||||
|
sed -i 's/--live-restore//' /etc/sysconfig/docker >/dev/null 2>&1
|
||||||
|
# Clean up any double spaces that might be left
|
||||||
|
sed -i 's/ */ /g' /etc/sysconfig/docker >/dev/null 2>&1
|
||||||
|
fi
|
||||||
|
|
||||||
systemctl enable docker >/dev/null 2>&1
|
systemctl enable docker >/dev/null 2>&1
|
||||||
|
systemctl start docker >/dev/null 2>&1
|
||||||
|
echo " - Docker configured for OpenCloud OS"
|
||||||
;;
|
;;
|
||||||
"alpine")
|
"alpine")
|
||||||
apk add docker docker-cli-compose >/dev/null 2>&1
|
apk add docker docker-cli-compose >/dev/null 2>&1
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export const dockerSwarmInitialized = async () => {
|
|||||||
await docker.swarmInspect();
|
await docker.swarmInspect();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} catch (_e) {
|
} catch {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -41,7 +41,7 @@ export const dockerNetworkInitialized = async () => {
|
|||||||
try {
|
try {
|
||||||
await docker.getNetwork("dokploy-network").inspect();
|
await docker.getNetwork("dokploy-network").inspect();
|
||||||
return true;
|
return true;
|
||||||
} catch (_e) {
|
} catch {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -101,11 +101,11 @@ export const initializeTraefik = async ({
|
|||||||
console.log("Waiting for service cleanup...");
|
console.log("Waiting for service cleanup...");
|
||||||
await new Promise((resolve) => setTimeout(resolve, 5000));
|
await new Promise((resolve) => setTimeout(resolve, 5000));
|
||||||
attempts++;
|
attempts++;
|
||||||
} catch (_e) {
|
} catch {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (_err) {
|
} catch {
|
||||||
console.log("No existing service to remove");
|
console.log("No existing service to remove");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,7 +120,7 @@ export const initializeTraefik = async ({
|
|||||||
|
|
||||||
await container.remove({ force: true });
|
await container.remove({ force: true });
|
||||||
await new Promise((resolve) => setTimeout(resolve, 5000));
|
await new Promise((resolve) => setTimeout(resolve, 5000));
|
||||||
} catch (_err) {
|
} catch {
|
||||||
console.log("No existing container to remove");
|
console.log("No existing container to remove");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -183,6 +183,7 @@ export const mechanizeDockerContainer = async (
|
|||||||
RollbackConfig,
|
RollbackConfig,
|
||||||
EndpointSpec: {
|
EndpointSpec: {
|
||||||
Ports: ports.map((port) => ({
|
Ports: ports.map((port) => ({
|
||||||
|
PublishMode: port.publishMode,
|
||||||
Protocol: port.protocol,
|
Protocol: port.protocol,
|
||||||
TargetPort: port.targetPort,
|
TargetPort: port.targetPort,
|
||||||
PublishedPort: port.publishedPort,
|
PublishedPort: port.publishedPort,
|
||||||
@@ -203,7 +204,7 @@ export const mechanizeDockerContainer = async (
|
|||||||
ForceUpdate: inspect.Spec.TaskTemplate.ForceUpdate + 1,
|
ForceUpdate: inspect.Spec.TaskTemplate.ForceUpdate + 1,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
} catch (_error: unknown) {
|
} catch {
|
||||||
await docker.createService(settings);
|
await docker.createService(settings);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ export const buildMariadb = async (mariadb: MariadbNested) => {
|
|||||||
version: Number.parseInt(inspect.Version.Index),
|
version: Number.parseInt(inspect.Version.Index),
|
||||||
...settings,
|
...settings,
|
||||||
});
|
});
|
||||||
} catch (_error) {
|
} catch {
|
||||||
await docker.createService(settings);
|
await docker.createService(settings);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -152,7 +152,7 @@ ${command ?? "wait $MONGOD_PID"}`;
|
|||||||
version: Number.parseInt(inspect.Version.Index),
|
version: Number.parseInt(inspect.Version.Index),
|
||||||
...settings,
|
...settings,
|
||||||
});
|
});
|
||||||
} catch (_error) {
|
} catch {
|
||||||
await docker.createService(settings);
|
await docker.createService(settings);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ export const buildMysql = async (mysql: MysqlNested) => {
|
|||||||
version: Number.parseInt(inspect.Version.Index),
|
version: Number.parseInt(inspect.Version.Index),
|
||||||
...settings,
|
...settings,
|
||||||
});
|
});
|
||||||
} catch (_error) {
|
} catch {
|
||||||
await docker.createService(settings);
|
await docker.createService(settings);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ export const buildRedis = async (redis: RedisNested) => {
|
|||||||
version: Number.parseInt(inspect.Version.Index),
|
version: Number.parseInt(inspect.Version.Index),
|
||||||
...settings,
|
...settings,
|
||||||
});
|
});
|
||||||
} catch (_error) {
|
} catch {
|
||||||
await docker.createService(settings);
|
await docker.createService(settings);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ export const loadDockerComposeRemote = async (
|
|||||||
if (!stdout) return null;
|
if (!stdout) return null;
|
||||||
const parsedConfig = load(stdout) as ComposeSpecification;
|
const parsedConfig = load(stdout) as ComposeSpecification;
|
||||||
return parsedConfig;
|
return parsedConfig;
|
||||||
} catch (_err) {
|
} catch {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ export const containerExists = async (containerName: string) => {
|
|||||||
try {
|
try {
|
||||||
await container.inspect();
|
await container.inspect();
|
||||||
return true;
|
return true;
|
||||||
} catch (_error) {
|
} catch {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ export async function checkGPUStatus(serverId?: string): Promise<GPUInfo> {
|
|||||||
...gpuInfo,
|
...gpuInfo,
|
||||||
...cudaInfo,
|
...cudaInfo,
|
||||||
};
|
};
|
||||||
} catch (_error) {
|
} catch {
|
||||||
return {
|
return {
|
||||||
driverInstalled: false,
|
driverInstalled: false,
|
||||||
driverVersion: undefined,
|
driverVersion: undefined,
|
||||||
@@ -315,7 +315,7 @@ const setupLocalServer = async (daemonConfig: any) => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await execAsync(setupCommands);
|
await execAsync(setupCommands);
|
||||||
} catch (_error) {
|
} catch {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"Failed to configure GPU support. Please ensure you have sudo privileges and try again.",
|
"Failed to configure GPU support. Please ensure you have sudo privileges and try again.",
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ export const removeTraefikConfig = async (
|
|||||||
if (fs.existsSync(configPath)) {
|
if (fs.existsSync(configPath)) {
|
||||||
await fs.promises.unlink(configPath);
|
await fs.promises.unlink(configPath);
|
||||||
}
|
}
|
||||||
} catch (_error) {}
|
} catch {}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const removeTraefikConfigRemote = async (
|
export const removeTraefikConfigRemote = async (
|
||||||
@@ -78,7 +78,7 @@ export const removeTraefikConfigRemote = async (
|
|||||||
const { DYNAMIC_TRAEFIK_PATH } = paths(true);
|
const { DYNAMIC_TRAEFIK_PATH } = paths(true);
|
||||||
const configPath = path.join(DYNAMIC_TRAEFIK_PATH, `${appName}.yml`);
|
const configPath = path.join(DYNAMIC_TRAEFIK_PATH, `${appName}.yml`);
|
||||||
await execAsyncRemote(serverId, `rm ${configPath}`);
|
await execAsyncRemote(serverId, `rm ${configPath}`);
|
||||||
} catch (_error) {}
|
} catch {}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const loadOrCreateConfig = (appName: string): FileConfig => {
|
export const loadOrCreateConfig = (appName: string): FileConfig => {
|
||||||
@@ -110,7 +110,7 @@ export const loadOrCreateConfigRemote = async (
|
|||||||
http: { routers: {}, services: {} },
|
http: { routers: {}, services: {} },
|
||||||
};
|
};
|
||||||
return parsedConfig;
|
return parsedConfig;
|
||||||
} catch (_err) {
|
} catch {
|
||||||
return fileConfig;
|
return fileConfig;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -132,7 +132,7 @@ export const readRemoteConfig = async (serverId: string, appName: string) => {
|
|||||||
const { stdout } = await execAsyncRemote(serverId, `cat ${configPath}`);
|
const { stdout } = await execAsyncRemote(serverId, `cat ${configPath}`);
|
||||||
if (!stdout) return null;
|
if (!stdout) return null;
|
||||||
return stdout;
|
return stdout;
|
||||||
} catch (_err) {
|
} catch {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user