Merge branch 'canary' into feat/internal-path-routing

This commit is contained in:
Jhonatan Caldeira
2025-07-05 15:50:04 -03:00
67 changed files with 347 additions and 181 deletions

View File

@@ -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

View File

@@ -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.

View File

@@ -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 🥈 -->

View File

@@ -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.

View File

@@ -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;
} }

View File

@@ -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");
} }
}; };

View File

@@ -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"

View File

@@ -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>

View File

@@ -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");
}); });
}; };

View File

@@ -40,7 +40,7 @@ export const ShowConvertedCompose = ({ composeId }: Props) => {
.then(() => { .then(() => {
refetch(); refetch();
}) })
.catch((_err) => {}); .catch(() => {});
} }
}, [isOpen]); }, [isOpen]);

View File

@@ -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");
}); });
}; };

View File

@@ -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");
} }
}} }}

View File

@@ -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.",
}); });

View File

@@ -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");
} }
}; };

View File

@@ -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
} }
}; };

View File

@@ -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(() => {

View File

@@ -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 (

View File

@@ -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}

View 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;

View File

@@ -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",

View File

@@ -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
} }
] ]

View File

@@ -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",

View File

@@ -72,7 +72,7 @@ export async function getServerSideProps(
trpcState: helpers.dehydrate(), trpcState: helpers.dehydrate(),
}, },
}; };
} catch (_error) { } catch {
return { return {
props: {}, props: {},
}; };

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -68,7 +68,7 @@ export async function getServerSideProps(
trpcState: helpers.dehydrate(), trpcState: helpers.dehydrate(),
}, },
}; };
} catch (_error) { } catch {
return { return {
props: {}, props: {},
}; };

View File

@@ -69,7 +69,7 @@ export async function getServerSideProps(
trpcState: helpers.dehydrate(), trpcState: helpers.dehydrate(),
}, },
}; };
} catch (_error) { } catch {
return { return {
props: {}, props: {},
}; };

View File

@@ -72,7 +72,7 @@ export async function getServerSideProps(
trpcState: helpers.dehydrate(), trpcState: helpers.dehydrate(),
}, },
}; };
} catch (_error) { } catch {
return { return {
props: {}, props: {},
}; };

View File

@@ -72,7 +72,7 @@ export async function getServerSideProps(
trpcState: helpers.dehydrate(), trpcState: helpers.dehydrate(),
}, },
}; };
} catch (_error) { } catch {
return { return {
props: {}, props: {},
}; };

View File

@@ -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: {

View File

@@ -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");
} }
}; };

View File

@@ -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);
}), }),
}); });

View File

@@ -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 }) => {

View File

@@ -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);
}), }),
}); });

View File

@@ -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 }) => {

View File

@@ -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;
} }
}; };

View File

@@ -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);

View File

@@ -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();

View File

@@ -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;
}

View File

@@ -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.

View File

@@ -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,
}) })

View File

@@ -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(),
}); });

View File

@@ -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;
} }
}; };

View File

@@ -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 {}
}; };

View File

@@ -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;
} }
}; };

View File

@@ -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) => {

View File

@@ -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) {

View File

@@ -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);
} }
}; };

View File

@@ -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);

View File

@@ -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

View File

@@ -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;
} }
}; };

View File

@@ -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");
} }

View File

@@ -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);
} }
}; };

View File

@@ -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);
} }
}; };

View File

@@ -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);
} }
}; };

View File

@@ -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);
} }
}; };

View File

@@ -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);
} }
}; };

View File

@@ -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;
} }
}; };

View File

@@ -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;
} }
}; };

View File

@@ -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.",
); );

View File

@@ -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;
} }
}; };