diff --git a/Dockerfile b/Dockerfile
index 8da1db459..8b9d215ce 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -27,7 +27,7 @@ WORKDIR /app
# Set production
ENV NODE_ENV=production
-RUN apt-get update && apt-get install -y curl apache2-utils && rm -rf /var/lib/apt/lists/*
+RUN apt-get update && apt-get install -y curl unzip apache2-utils && rm -rf /var/lib/apt/lists/*
# Copy only the necessary files
COPY --from=build /prod/dokploy/.next ./.next
@@ -42,7 +42,7 @@ COPY --from=build /prod/dokploy/node_modules ./node_modules
# Install docker
-RUN curl -fsSL https://get.docker.com -o get-docker.sh && sh get-docker.sh && rm get-docker.sh
+RUN curl -fsSL https://get.docker.com -o get-docker.sh && sh get-docker.sh && rm get-docker.sh && curl https://rclone.org/install.sh | bash
# Install Nixpacks and tsx
# | VERBOSE=1 VERSION=1.21.0 bash
@@ -55,4 +55,4 @@ RUN curl -sSL https://nixpacks.com/install.sh -o install.sh \
COPY --from=buildpacksio/pack:0.35.0 /usr/local/bin/pack /usr/local/bin/pack
EXPOSE 3000
-CMD [ "pnpm", "start" ]
+CMD [ "pnpm", "start" ]
\ No newline at end of file
diff --git a/apps/docs/app/[lang]/[[...slug]]/page.tsx b/apps/docs/app/[lang]/[[...slug]]/page.tsx
index 9a7f3d2cb..8307d655d 100644
--- a/apps/docs/app/[lang]/[[...slug]]/page.tsx
+++ b/apps/docs/app/[lang]/[[...slug]]/page.tsx
@@ -74,7 +74,7 @@ export function generateMetadata({
},
twitter: {
card: "summary_large_image",
- creator: "@siumauricio",
+ creator: "@getdokploy",
title: page.data.title,
description: page.data.description,
images: [
diff --git a/apps/docs/content/docs/core/application/overview.mdx b/apps/docs/content/docs/core/application/overview.mdx
index 788daff82..9f881f6b8 100644
--- a/apps/docs/content/docs/core/application/overview.mdx
+++ b/apps/docs/content/docs/core/application/overview.mdx
@@ -15,6 +15,8 @@ Configure the source of your code, the way your application is built, and also m
If you need to assign environment variables to your application, you can do so here.
+In case you need to use a multiline variable, you can wrap it in double quotes just like this `'"here_is_my_private_key"'`.
+
## Monitoring
Four graphs will be displayed for the use of memory, CPU, disk, and network. Note that the information is only updated if you are viewing the current page, otherwise it will not be updated.
diff --git a/apps/docs/content/docs/core/databases/overview.mdx b/apps/docs/content/docs/core/databases/overview.mdx
index f9702fb01..0fd2f5b0d 100644
--- a/apps/docs/content/docs/core/databases/overview.mdx
+++ b/apps/docs/content/docs/core/databases/overview.mdx
@@ -26,6 +26,8 @@ Actions like deploying, updating, and deleting your database, and stopping it.
If you need to assign environment variables to your application, you can do so here.
+In case you need to use a multiline variable, you can wrap it in double quotes just like this `'"here_is_my_private_key"'`.
+
## Monitoring
Four graphs will be displayed for the use of memory, CPU, disk, and network. Note that the information is only updated if you are viewing the current page, otherwise it will not be updated.
diff --git a/apps/docs/content/docs/core/extra/comparison.mdx b/apps/docs/content/docs/core/extra/comparison.mdx
index 0036ccbf8..028ea6d08 100644
--- a/apps/docs/content/docs/core/extra/comparison.mdx
+++ b/apps/docs/content/docs/core/extra/comparison.mdx
@@ -1,22 +1,23 @@
---
-title: 'Comparison'
-description: 'A comparison of Dokploy, CapRover, Dokku, and Coolify'
+title: "Comparison"
+description: "A comparison of Dokploy, CapRover, Dokku, and Coolify"
---
Comparison of the following deployment tools:
-| Feature | Dokploy | CapRover | Dokku | Coolify |
-|-----------------------------------|---------------------------------------|--------------------------------------|--------------------------------------|--------------------------------------|
-| **User Interface** | ✅ | ✅ | ❌ | ✅ |
-| **Docker compose support** | ✅ | ❌ | ❌ | ✅ |
-| **API/CLI** | ✅ | ✅ | ✅ | ✅ |
-| **Multi node support** | ✅ | ✅ | ❌ | ✅ |
-| **Traefik Integration** | ✅ | ✅ | Available via Plugins | ✅ |
-| **User Permission Management** | ✅ | ❌ | ❌ | ✅ |
-| **Advanced User Permission Management** | ✅ | ❌ | ❌ | ❌ |
-| **Terminal Access Built In** | ✅ | ❌ | ❌ | ✅ |
-| **Database Support** | ✅ | ✅ | ❌ | ✅ |
-| **Monitoring** | ✅ | ✅ | ❌ | ❌ |
-| **Backups** | ✅ | Available via Plugins | Available via Plugins | ✅ |
-| **Open Source** | ✅ | ✅ | ✅ | ✅ |
-| **Cloud/Paid Version** | ❌ | ✅ | ❌ | ✅ |
+| Feature | Dokploy | CapRover | Dokku | Coolify |
+| --------------------------------------- | ------- | --------------------- | --------------------- | ------- |
+| **User Interface** | ✅ | ✅ | ❌ | ✅ |
+| **Docker compose support** | ✅ | ❌ | ❌ | ✅ |
+| **API/CLI** | ✅ | ✅ | ✅ | ✅ |
+| **Multi node support** | ✅ | ✅ | ❌ | ✅ |
+| **Traefik Integration** | ✅ | ✅ | Available via Plugins | ✅ |
+| **User Permission Management** | ✅ | ❌ | ❌ | ✅ |
+| **Advanced User Permission Management** | ✅ | ❌ | ❌ | ❌ |
+| **Terminal Access Built In** | ✅ | ❌ | ❌ | ✅ |
+| **Database Support** | ✅ | ✅ | ❌ | ✅ |
+| **Monitoring** | ✅ | ✅ | ❌ | ❌ |
+| **Backups** | ✅ | Available via Plugins | Available via Plugins | ✅ |
+| **Open Source** | ✅ | ✅ | ✅ | ✅ |
+| **Multi Server Support** | ✅ | ❌ | ❌ | ✅ |
+| **Cloud/Paid Version** | ❌ | ✅ | ✅ | ✅ |
diff --git a/apps/docs/content/docs/core/meta.json b/apps/docs/content/docs/core/meta.json
index a6a8a3c9e..b2c3020fc 100644
--- a/apps/docs/content/docs/core/meta.json
+++ b/apps/docs/content/docs/core/meta.json
@@ -64,6 +64,9 @@
"docker/overview",
"---Monitoring---",
"monitoring/overview",
+ "---Multi Server---",
+ "multi-server/overview",
+ "multi-server/example",
"---Cluster---",
"cluster/overview",
"---Deployments---",
diff --git a/apps/docs/content/docs/core/multi-server/example.mdx b/apps/docs/content/docs/core/multi-server/example.mdx
new file mode 100644
index 000000000..109722c42
--- /dev/null
+++ b/apps/docs/content/docs/core/multi-server/example.mdx
@@ -0,0 +1,117 @@
+---
+title: Example
+description: "Example to setup a remote server and deploy application in a VPS."
+---
+
+import { Callout } from "fumadocs-ui/components/callout";
+
+Multi server allows you to deploy your apps remotely to different servers without needing to build and run them where the Dokploy UI is installed.
+
+## Requirements
+
+1. To install Dokploy UI, follow the [installation guide](en/docs/core/get-started/installation).
+
+2. Create an SSH key by going to `/dashboard/settings/ssh-keys` and add a new key. Be sure to copy the public key.
+
+
+
+3. Decide which remote server to deploy your apps on. We recommend these reliable providers:
+
+- [Hostinger](https://www.hostinger.com/vps-hosting?ref=dokploy) Get 20% off with this [referral link](https://www.hostinger.com/vps-hosting?REFERRALCODE=1SIUMAURICI97).
+- [DigitalOcean](https://www.digitalocean.com/pricing/droplets#basic-droplets) Get $200 credits for free with this [referral link](https://m.do.co/c/db24efd43f35).
+- [Hetzner](https://www.hetzner.com/cloud/) Get €20 credits with this [referral link](https://hetzner.cloud/?ref=vou4fhxJ1W2D).
+- [Linode](https://www.linode.com/es/pricing/#compute-shared).
+- [Vultr](https://www.vultr.com/pricing/#cloud-compute).
+- [Scaleway](https://www.scaleway.com/en/pricing/?tags=baremetal,available).
+- [Google Cloud](https://cloud.google.com/).
+- [AWS](https://aws.amazon.com/ec2/pricing/).
+
+4. When creating the server, it should ask for SSH keys. Ideally, use your computer's public key and the key you generated in the previous step. Here's how to add the public key in Hostinger:
+
+
+
+The steps are similar across other providers.
+
+5. Copy the server’s IP address and ensure you know the username (often `root`). Fill in all fields and click `Create`.
+
+
+
+6. To test connectivity, open the server dropdown and click `Enter Terminal`. If everything is correct, you should be able to interact with the remote server.
+
+7. Click `Setup Server` to proceed. There are two tabs: SSH Keys and Deployments. This guide explains the easy way, but you can follow the manual process via the Dokploy UI if you prefer.
+
+
+
+8. Click `Deployments`, then `Setup Server`. If everything is correct, you should see output similar to this:
+
+
+
+
+ You only need to run this setup once. If Dokploy updates later, check the
+ release notes to see if rerunning this command is required.
+
+
+9. You're ready to deploy your apps! Let's test it out:
+
+
+
+10. To check which server an app belongs to, you’ll see the server name at the top. If no server is selected, it defaults to `Dokploy Server`. Click `Deploy` to start building your app on the remote server. You can check the `Logs` tab to see the build process. For this example, we’ll use a test repo:
+ Repo: `https://github.com/Dokploy/examples.git`
+ Branch: `main`
+ Build Path: `/astro`
+
+
+
+11. Once the build is done, go to `Domains` and create a free domain. Just click `Create` and you’re good to go! 🎊
+
+{" "}
+
+
diff --git a/apps/docs/content/docs/core/multi-server/overview.mdx b/apps/docs/content/docs/core/multi-server/overview.mdx
new file mode 100644
index 000000000..051b314c0
--- /dev/null
+++ b/apps/docs/content/docs/core/multi-server/overview.mdx
@@ -0,0 +1,29 @@
+---
+title: Overview
+description: "Deploy your apps to multiple servers remotely."
+---
+
+import { Callout } from "fumadocs-ui/components/callout";
+
+Multi server allows you to deploy your apps remotely to different servers without needing to build and run them where the Dokploy UI is installed.
+
+To use the multi-server feature, you need to have Dokploy UI installed either locally or on a remote server. We recommend using a remote server for better connectivity, security, and isolation, for remote instances we install only a traefik instance.
+
+If you plan to only deploy apps to remote servers and use Dokploy UI for managing deployments, Dokploy will use around 250 MB of RAM and minimal CPU, so a low-resource server should be sufficient.
+
+All the features we have documented previously are supported by Dokploy Multi Server. The only feature not supported is remote server monitoring, due to performance reasons. However, all functionalities should work the same as when deploying on the same server where Dokploy UI is installed.
+
+## Features
+
+1. **Enter the terminal**: Allows you to access the terminal of the remote server.
+2. **Setup Server**: Allows you to configure the remote server.
+ - **SSH Keys**: Steps to add SSH keys to the remote server.
+ - **Deployments**: Steps to configure the remote server for deploying applications.
+3. **Edit Server**: Allows you to modify the remote server's details, such as SSH key, name, description, IP, etc.
+4. **View Actions**: Lets you perform actions like managing the Traefik instance, storage, and activating Docker cleanup.
+5. **Show Traefik File System**: Displays the contents of the remote server's directory.
+6. **Show Docker Containers**: Shows the Docker containers running on the remote server.
+
+
+ Remote server monitoring is not supported due to performance reasons.
+
diff --git a/apps/docs/content/docs/core/templates/overview.mdx b/apps/docs/content/docs/core/templates/overview.mdx
index 363650f6a..6c8a7e8cb 100644
--- a/apps/docs/content/docs/core/templates/overview.mdx
+++ b/apps/docs/content/docs/core/templates/overview.mdx
@@ -31,8 +31,7 @@ The following templates are available:
- **Wordpress**: Open Source Content Management System
- **Open WebUI**: Free and Open Source ChatGPT Alternative
- **Teable**: Open Source Airtable Alternative, Developer Friendly, No-code Database Built on Postgres
-
-
+- **Roundcube**: Free and open source webmail software for the masses, written in PHP, uses SMTP[^1].
## Create your own template
@@ -41,3 +40,5 @@ We accept contributions to upload new templates to the dokploy repository.
Make sure to follow the guidelines for creating a template:
[Steps to create your own template](https://github.com/Dokploy/dokploy/blob/canary/CONTRIBUTING.md#templates)
+
+[^1]: Please note that if you're self-hosting a mail server you need port 25 to be open for SMTP (Mail Transmission Protocol that allows you to send and receive) to work properly. Some VPS providers like [Hetzner](https://docs.hetzner.com/cloud/servers/faq/#why-can-i-not-send-any-mails-from-my-server) block this port by default for new clients.
diff --git a/apps/docs/content/docs/core/troubleshooting/overview.mdx b/apps/docs/content/docs/core/troubleshooting/overview.mdx
index d44d3d668..66a9fe80f 100644
--- a/apps/docs/content/docs/core/troubleshooting/overview.mdx
+++ b/apps/docs/content/docs/core/troubleshooting/overview.mdx
@@ -3,4 +3,90 @@ title: Overview
description: Solve the most common problems that occur when using Dokploy.
---
-WIP
\ No newline at end of file
+## Applications Domain Not Working?
+
+You see the deployment succeeded, and logs are running, but the domain isn't working? Here's what to check:
+
+1. **Correct Port Mapping**: Ensure the domain is using the correct port for your application. For example, if you're using Next.js, the port should be `3000`, or for Laravel, it should be `8000`. If you change the app port, update the domain to reflect that.
+2. **Avoid Using `Ports` in Advanced Settings**: Generally, there's no need to use the `Ports` feature unless you want to access your app via `IP:port`. Leaving this feature enabled may interfere with your domain.
+
+3. **Let's Encrypt Certificates**: It's crucial to point the domain to your server’s IP **before** adding it in Dokploy. If the domain is added first, the certificate won’t be generated, and you may need to recreate the domain or restart Traefik.
+
+4. **Listen on 0.0.0.0, Not 127.0.0.1**: If your app is bound to `127.0.0.1` (which is common in Vite apps), switch it to `0.0.0.0` to allow external access.
+
+## Logs and Monitoring Not Working After Changing Application Placement?
+
+This is expected behavior. If the application is running on a different node (worker), the UI won’t have access to logs or monitoring, as they're not on the same node.
+
+## Mounts Are Causing My Application Not to Run?
+
+Docker Swarm won't run your application if there are invalid mounts, even if the deployment shows as successful. Double-check your mounts to ensure they are valid.
+
+## Volumes in Docker Compose Not Working?
+
+For Docker Compose, all file mounts defined in the `volumes` section will be stored in the `files` folder. This is the default directory structure:
+
+## I added a volume to my docker compose, but is not finding the volume?
+
+For docker compose all the file mounts you've created in the volumes section will be stored to files folder, this is the default structure of the docker compose.
+
+```
+/application-name
+ /code
+ /files
+```
+
+So instead of using this invalid way to mount a volume:
+
+```yaml
+volumes:
+ - "/folder:/path/in/container" ❌
+```
+
+You should use this format:
+
+```yaml
+volumes:
+ - "../files/my-database:/var/lib/mysql" ✅
+ - "../files/my-configs:/etc/my-app/config" ✅
+```
+
+## Logs Not Loading When Deploying to a Remote Server?
+
+There are a few potential reasons for this:
+
+1. **Slow Server:**: If the server is too slow, it may struggle to handle concurrent requests, leading to SSL handshake errors.
+2. **Insufficient Disk Space:** If the server doesn't have enough disk space, the logs may not load.
+
+## Docker Compose Domain Not Working?
+
+When adding a domain in your Docker Compose file, it’s not necessary to expose the ports directly. Simply specify the port where your app is running. Exposing the ports can lead to conflicts with other applications or ports.
+
+Example of what not to do:
+
+```yaml
+services:
+ app:
+ image: dokploy/dokploy:latest
+ ports:
+ - 3000:3000
+```
+
+Recommended approach:
+
+```yaml
+services:
+ app:
+ image: dokploy/dokploy:latest
+ ports:
+ - 3000
+ - 80
+```
+
+Then, when creating the domain in Dokploy, specify the service name and port, like this:
+
+```yaml
+domain: my-app.com
+serviceName: app
+port: 3000
+```
diff --git a/apps/docs/mdx-components.tsx b/apps/docs/mdx-components.tsx
index 96c5b74b3..10488dcab 100644
--- a/apps/docs/mdx-components.tsx
+++ b/apps/docs/mdx-components.tsx
@@ -10,8 +10,10 @@ export function useMDXComponents(components: MDXComponents): MDXComponents {
p: ({ children }) => (
{children}
),
- li: ({ children }) => (
- {children}
+ li: ({ children, id }) => (
+
+ {children}
+
),
};
}
diff --git a/apps/docs/public/assets/hostinger-add-sshkey.png b/apps/docs/public/assets/hostinger-add-sshkey.png
new file mode 100644
index 000000000..dfbb9a77e
Binary files /dev/null and b/apps/docs/public/assets/hostinger-add-sshkey.png differ
diff --git a/apps/docs/public/assets/multi-server-add-app.png b/apps/docs/public/assets/multi-server-add-app.png
new file mode 100644
index 000000000..d6f83882b
Binary files /dev/null and b/apps/docs/public/assets/multi-server-add-app.png differ
diff --git a/apps/docs/public/assets/multi-server-add-server.png b/apps/docs/public/assets/multi-server-add-server.png
new file mode 100644
index 000000000..48e346013
Binary files /dev/null and b/apps/docs/public/assets/multi-server-add-server.png differ
diff --git a/apps/docs/public/assets/multi-server-finish.png b/apps/docs/public/assets/multi-server-finish.png
new file mode 100644
index 000000000..3f0430106
Binary files /dev/null and b/apps/docs/public/assets/multi-server-finish.png differ
diff --git a/apps/docs/public/assets/multi-server-overview.png b/apps/docs/public/assets/multi-server-overview.png
new file mode 100644
index 000000000..e1d9c5e61
Binary files /dev/null and b/apps/docs/public/assets/multi-server-overview.png differ
diff --git a/apps/docs/public/assets/multi-server-setup-2.png b/apps/docs/public/assets/multi-server-setup-2.png
new file mode 100644
index 000000000..2f7f907eb
Binary files /dev/null and b/apps/docs/public/assets/multi-server-setup-2.png differ
diff --git a/apps/docs/public/assets/multi-server-setup-3.png b/apps/docs/public/assets/multi-server-setup-3.png
new file mode 100644
index 000000000..66f9cc1e9
Binary files /dev/null and b/apps/docs/public/assets/multi-server-setup-3.png differ
diff --git a/apps/docs/public/assets/multi-server-setup-app.png b/apps/docs/public/assets/multi-server-setup-app.png
new file mode 100644
index 000000000..88393cb34
Binary files /dev/null and b/apps/docs/public/assets/multi-server-setup-app.png differ
diff --git a/apps/docs/public/assets/multi-server-setup.png b/apps/docs/public/assets/multi-server-setup.png
new file mode 100644
index 000000000..c5bca6bf0
Binary files /dev/null and b/apps/docs/public/assets/multi-server-setup.png differ
diff --git a/apps/docs/public/assets/ssh-keys.png b/apps/docs/public/assets/ssh-keys.png
new file mode 100644
index 000000000..52ee5c5e0
Binary files /dev/null and b/apps/docs/public/assets/ssh-keys.png differ
diff --git a/apps/dokploy/LICENSE.MD b/apps/dokploy/LICENSE.MD
index 9031c94b9..59e9d822e 100644
--- a/apps/dokploy/LICENSE.MD
+++ b/apps/dokploy/LICENSE.MD
@@ -17,10 +17,10 @@ See the License for the specific language governing permissions and limitations
## Additional Terms for Specific Features
-The following additional terms apply to the multi-node support and Docker Compose file support features of Dokploy. In the event of a conflict, these provisions shall take precedence over those in the Apache License:
+The following additional terms apply to the multi-node support, Docker Compose file 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 and Docker Compose file support, will always be free to use in the self-hosted version.
-- **Restriction on Resale**: The multi-node support and Docker Compose file support 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 and Docker Compose file support features must be distributed freely and cannot be sold or offered as a service.
+- **Self-Hosted Version Free**: All features of Dokploy, including multi-node support, Docker Compose file support 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 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 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.
diff --git a/apps/dokploy/__test__/drop/drop.test.test.ts b/apps/dokploy/__test__/drop/drop.test.test.ts
index 5561999c5..c411566a4 100644
--- a/apps/dokploy/__test__/drop/drop.test.test.ts
+++ b/apps/dokploy/__test__/drop/drop.test.test.ts
@@ -1,6 +1,8 @@
import fs from "node:fs/promises";
import path from "node:path";
-import { APPLICATIONS_PATH } from "@/server/constants";
+import { paths } from "@/server/constants";
+const { APPLICATIONS_PATH } = paths();
+import type { ApplicationNested } from "@/server/utils/builders";
import { unzipDrop } from "@/server/utils/builders/drop";
import AdmZip from "adm-zip";
import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
@@ -11,11 +13,84 @@ if (typeof window === "undefined") {
globalThis.FileList = undici.FileList as any;
}
+const baseApp: ApplicationNested = {
+ applicationId: "",
+ applicationStatus: "done",
+ appName: "",
+ autoDeploy: true,
+ serverId: "",
+ branch: null,
+ dockerBuildStage: "",
+ buildArgs: null,
+ buildPath: "/",
+ gitlabPathNamespace: "",
+ buildType: "nixpacks",
+ bitbucketBranch: "",
+ bitbucketBuildPath: "",
+ bitbucketId: "",
+ bitbucketRepository: "",
+ bitbucketOwner: "",
+ githubId: "",
+ gitlabProjectId: 0,
+ gitlabBranch: "",
+ gitlabBuildPath: "",
+ gitlabId: "",
+ gitlabRepository: "",
+ gitlabOwner: "",
+ command: null,
+ cpuLimit: null,
+ cpuReservation: null,
+ createdAt: "",
+ customGitBranch: "",
+ customGitBuildPath: "",
+ customGitSSHKeyId: null,
+ customGitUrl: "",
+ description: "",
+ dockerfile: null,
+ dockerImage: null,
+ dropBuildPath: null,
+ enabled: null,
+ env: null,
+ healthCheckSwarm: null,
+ labelsSwarm: null,
+ memoryLimit: null,
+ memoryReservation: null,
+ modeSwarm: null,
+ mounts: [],
+ name: "",
+ networkSwarm: null,
+ owner: null,
+ password: null,
+ placementSwarm: null,
+ ports: [],
+ projectId: "",
+ publishDirectory: null,
+ redirects: [],
+ refreshToken: "",
+ registry: null,
+ registryId: null,
+ replicas: 1,
+ repository: null,
+ restartPolicySwarm: null,
+ rollbackConfigSwarm: null,
+ security: [],
+ sourceType: "git",
+ subtitle: null,
+ title: null,
+ updateConfigSwarm: null,
+ username: null,
+ dockerContextPath: null,
+};
+//
vi.mock("@/server/constants", () => ({
- APPLICATIONS_PATH: "./__test__/drop/zips/output",
+ paths: () => ({
+ APPLICATIONS_PATH: "./__test__/drop/zips/output",
+ }),
+ // APPLICATIONS_PATH: "./__test__/drop/zips/output",
}));
describe("unzipDrop using real zip files", () => {
+ // const { APPLICATIONS_PATH } = paths();
beforeAll(async () => {
await fs.rm(APPLICATIONS_PATH, { recursive: true, force: true });
});
@@ -25,39 +100,42 @@ describe("unzipDrop using real zip files", () => {
});
it("should correctly extract a zip with a single root folder", async () => {
- const appName = "single-file";
- const outputPath = path.join(APPLICATIONS_PATH, appName, "code");
+ baseApp.appName = "single-file";
+ // const appName = "single-file";
+ const outputPath = path.join(APPLICATIONS_PATH, baseApp.appName, "code");
const zip = new AdmZip("./__test__/drop/zips/single-file.zip");
const zipBuffer = zip.toBuffer();
const file = new File([zipBuffer], "single.zip");
- await unzipDrop(file, appName);
+ await unzipDrop(file, baseApp);
const files = await fs.readdir(outputPath, { withFileTypes: true });
expect(files.some((f) => f.name === "test.txt")).toBe(true);
});
it("should correctly extract a zip with a single root folder and a subfolder", async () => {
- const appName = "folderwithfile";
- const outputPath = path.join(APPLICATIONS_PATH, appName, "code");
+ baseApp.appName = "folderwithfile";
+ // const appName = "folderwithfile";
+ const outputPath = path.join(APPLICATIONS_PATH, baseApp.appName, "code");
const zip = new AdmZip("./__test__/drop/zips/folder-with-file.zip");
const zipBuffer = zip.toBuffer();
const file = new File([zipBuffer], "single.zip");
- await unzipDrop(file, appName);
+ await unzipDrop(file, baseApp);
const files = await fs.readdir(outputPath, { withFileTypes: true });
expect(files.some((f) => f.name === "folder1.txt")).toBe(true);
});
it("should correctly extract a zip with multiple root folders", async () => {
- const appName = "two-folders";
- const outputPath = path.join(APPLICATIONS_PATH, appName, "code");
+ baseApp.appName = "two-folders";
+ // const appName = "two-folders";
+ const outputPath = path.join(APPLICATIONS_PATH, baseApp.appName, "code");
const zip = new AdmZip("./__test__/drop/zips/two-folders.zip");
const zipBuffer = zip.toBuffer();
const file = new File([zipBuffer], "single.zip");
- await unzipDrop(file, appName);
+ await unzipDrop(file, baseApp);
const files = await fs.readdir(outputPath, { withFileTypes: true });
@@ -66,13 +144,14 @@ describe("unzipDrop using real zip files", () => {
});
it("should correctly extract a zip with a single root with a file", async () => {
- const appName = "nested";
- const outputPath = path.join(APPLICATIONS_PATH, appName, "code");
+ baseApp.appName = "nested";
+ // const appName = "nested";
+ const outputPath = path.join(APPLICATIONS_PATH, baseApp.appName, "code");
const zip = new AdmZip("./__test__/drop/zips/nested.zip");
const zipBuffer = zip.toBuffer();
const file = new File([zipBuffer], "single.zip");
- await unzipDrop(file, appName);
+ await unzipDrop(file, baseApp);
const files = await fs.readdir(outputPath, { withFileTypes: true });
@@ -82,13 +161,14 @@ describe("unzipDrop using real zip files", () => {
});
it("should correctly extract a zip with a single root with a folder", async () => {
- const appName = "folder-with-sibling-file";
- const outputPath = path.join(APPLICATIONS_PATH, appName, "code");
+ baseApp.appName = "folder-with-sibling-file";
+ // const appName = "folder-with-sibling-file";
+ const outputPath = path.join(APPLICATIONS_PATH, baseApp.appName, "code");
const zip = new AdmZip("./__test__/drop/zips/folder-with-sibling-file.zip");
const zipBuffer = zip.toBuffer();
const file = new File([zipBuffer], "single.zip");
- await unzipDrop(file, appName);
+ await unzipDrop(file, baseApp);
const files = await fs.readdir(outputPath, { withFileTypes: true });
diff --git a/apps/dokploy/__test__/traefik/traefik.test.ts b/apps/dokploy/__test__/traefik/traefik.test.ts
index 7ca9f1693..222f8fd73 100644
--- a/apps/dokploy/__test__/traefik/traefik.test.ts
+++ b/apps/dokploy/__test__/traefik/traefik.test.ts
@@ -9,6 +9,7 @@ const baseApp: ApplicationNested = {
applicationStatus: "done",
appName: "",
autoDeploy: true,
+ serverId: "",
branch: null,
dockerBuildStage: "",
buildArgs: null,
diff --git a/apps/dokploy/components/dashboard/application/advanced/cluster/modify-swarm-settings.tsx b/apps/dokploy/components/dashboard/application/advanced/cluster/modify-swarm-settings.tsx
index fd91703b2..6750527d2 100644
--- a/apps/dokploy/components/dashboard/application/advanced/cluster/modify-swarm-settings.tsx
+++ b/apps/dokploy/components/dashboard/application/advanced/cluster/modify-swarm-settings.tsx
@@ -278,6 +278,12 @@ export const AddSwarmSettings = ({ applicationId }: Props) => {
{isError && {error?.message} }
+
+
+ Changing settings such as placements may cause the logs/monitoring
+ to be unavailable.
+
+
diff --git a/apps/dokploy/components/dashboard/compose/general/actions.tsx b/apps/dokploy/components/dashboard/compose/general/actions.tsx
index 754fb3451..365e37f51 100644
--- a/apps/dokploy/components/dashboard/compose/general/actions.tsx
+++ b/apps/dokploy/components/dashboard/compose/general/actions.tsx
@@ -75,7 +75,10 @@ export const ComposeActions = ({ composeId }: Props) => {
)}
-
+
Open Terminal
diff --git a/apps/dokploy/components/dashboard/compose/logs/show.tsx b/apps/dokploy/components/dashboard/compose/logs/show.tsx
index 19a7c4e6d..992086945 100644
--- a/apps/dokploy/components/dashboard/compose/logs/show.tsx
+++ b/apps/dokploy/components/dashboard/compose/logs/show.tsx
@@ -16,6 +16,7 @@ import {
SelectValue,
} from "@/components/ui/select";
import { api } from "@/utils/api";
+import { Loader, Loader2 } from "lucide-react";
import dynamic from "next/dynamic";
import { useEffect, useState } from "react";
export const DockerLogs = dynamic(
@@ -30,14 +31,20 @@ export const DockerLogs = dynamic(
interface Props {
appName: string;
+ serverId?: string;
appType: "stack" | "docker-compose";
}
-export const ShowDockerLogsCompose = ({ appName, appType }: Props) => {
- const { data } = api.docker.getContainersByAppNameMatch.useQuery(
+export const ShowDockerLogsCompose = ({
+ appName,
+ appType,
+ serverId,
+}: Props) => {
+ const { data, isLoading } = api.docker.getContainersByAppNameMatch.useQuery(
{
appName,
appType,
+ serverId,
},
{
enabled: !!appName,
@@ -64,7 +71,14 @@ export const ShowDockerLogsCompose = ({ appName, appType }: Props) => {
Select a container to view logs
-
+ {isLoading ? (
+
+ Loading...
+
+
+ ) : (
+
+ )}
@@ -81,6 +95,7 @@ export const ShowDockerLogsCompose = ({ appName, appType }: Props) => {
diff --git a/apps/dokploy/components/dashboard/compose/monitoring/show.tsx b/apps/dokploy/components/dashboard/compose/monitoring/show.tsx
index 428c74e4c..898af8ec6 100644
--- a/apps/dokploy/components/dashboard/compose/monitoring/show.tsx
+++ b/apps/dokploy/components/dashboard/compose/monitoring/show.tsx
@@ -17,23 +17,27 @@ import {
SelectValue,
} from "@/components/ui/select";
import { api } from "@/utils/api";
+import { Loader2 } from "lucide-react";
import { useEffect, useState } from "react";
import { toast } from "sonner";
import { DockerMonitoring } from "../../monitoring/docker/show";
interface Props {
appName: string;
+ serverId?: string;
appType: "stack" | "docker-compose";
}
export const ShowMonitoringCompose = ({
appName,
appType = "stack",
+ serverId,
}: Props) => {
- const { data } = api.docker.getContainersByAppNameMatch.useQuery(
+ const { data, isLoading } = api.docker.getContainersByAppNameMatch.useQuery(
{
appName: appName,
appType,
+ serverId,
},
{
enabled: !!appName,
@@ -46,7 +50,7 @@ export const ShowMonitoringCompose = ({
const [containerId, setContainerId] = useState();
- const { mutateAsync: restart, isLoading } =
+ const { mutateAsync: restart, isLoading: isRestarting } =
api.docker.restartContainer.useMutation();
useEffect(() => {
@@ -77,7 +81,14 @@ export const ShowMonitoringCompose = ({
value={containerAppName}
>
-
+ {isLoading ? (
+
+ Loading...
+
+
+ ) : (
+
+ )}
@@ -95,7 +106,7 @@ export const ShowMonitoringCompose = ({
{
if (!containerId) return;
toast.success(`Restarting container ${containerAppName}`);
diff --git a/apps/dokploy/components/dashboard/docker/config/show-container-config.tsx b/apps/dokploy/components/dashboard/docker/config/show-container-config.tsx
index 9aee9f760..25d78dd7d 100644
--- a/apps/dokploy/components/dashboard/docker/config/show-container-config.tsx
+++ b/apps/dokploy/components/dashboard/docker/config/show-container-config.tsx
@@ -11,12 +11,14 @@ import { api } from "@/utils/api";
interface Props {
containerId: string;
+ serverId?: string;
}
-export const ShowContainerConfig = ({ containerId }: Props) => {
+export const ShowContainerConfig = ({ containerId, serverId }: Props) => {
const { data } = api.docker.getConfig.useQuery(
{
containerId,
+ serverId,
},
{
enabled: !!containerId,
diff --git a/apps/dokploy/components/dashboard/docker/logs/docker-logs-id.tsx b/apps/dokploy/components/dashboard/docker/logs/docker-logs-id.tsx
index a269cc0c5..d9bce7274 100644
--- a/apps/dokploy/components/dashboard/docker/logs/docker-logs-id.tsx
+++ b/apps/dokploy/components/dashboard/docker/logs/docker-logs-id.tsx
@@ -8,9 +8,14 @@ import "@xterm/xterm/css/xterm.css";
interface Props {
id: string;
containerId: string;
+ serverId?: string | null;
}
-export const DockerLogsId: React.FC = ({ id, containerId }) => {
+export const DockerLogsId: React.FC = ({
+ id,
+ containerId,
+ serverId,
+}) => {
const [term, setTerm] = React.useState();
const [lines, setLines] = React.useState(40);
@@ -38,7 +43,7 @@ export const DockerLogsId: React.FC = ({ id, containerId }) => {
const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
- const wsUrl = `${protocol}//${window.location.host}/docker-container-logs?containerId=${containerId}&tail=${lines}`;
+ const wsUrl = `${protocol}//${window.location.host}/docker-container-logs?containerId=${containerId}&tail=${lines}${serverId ? `&serverId=${serverId}` : ""}`;
const ws = new WebSocket(wsUrl);
const fitAddon = new FitAddon();
diff --git a/apps/dokploy/components/dashboard/docker/logs/show-docker-modal-logs.tsx b/apps/dokploy/components/dashboard/docker/logs/show-docker-modal-logs.tsx
index 07678b6fd..c3d38d986 100644
--- a/apps/dokploy/components/dashboard/docker/logs/show-docker-modal-logs.tsx
+++ b/apps/dokploy/components/dashboard/docker/logs/show-docker-modal-logs.tsx
@@ -22,9 +22,14 @@ export const DockerLogsId = dynamic(
interface Props {
containerId: string;
children?: React.ReactNode;
+ serverId?: string | null;
}
-export const ShowDockerModalLogs = ({ containerId, children }: Props) => {
+export const ShowDockerModalLogs = ({
+ containerId,
+ children,
+ serverId,
+}: Props) => {
return (
@@ -41,7 +46,11 @@ export const ShowDockerModalLogs = ({ containerId, children }: Props) => {
View the logs for {containerId}
-
+
diff --git a/apps/dokploy/components/dashboard/docker/show/colums.tsx b/apps/dokploy/components/dashboard/docker/show/colums.tsx
index 243ea4b3f..3feae176a 100644
--- a/apps/dokploy/components/dashboard/docker/show/colums.tsx
+++ b/apps/dokploy/components/dashboard/docker/show/colums.tsx
@@ -114,11 +114,20 @@ export const columns: ColumnDef[] = [
Actions
-
+
View Logs
-
-
+
+
Terminal
diff --git a/apps/dokploy/components/dashboard/docker/show/show-containers.tsx b/apps/dokploy/components/dashboard/docker/show/show-containers.tsx
index e8b56daee..e55e6271f 100644
--- a/apps/dokploy/components/dashboard/docker/show/show-containers.tsx
+++ b/apps/dokploy/components/dashboard/docker/show/show-containers.tsx
@@ -34,8 +34,15 @@ export type Container = NonNullable<
RouterOutputs["docker"]["getContainers"]
>[0];
-export const ShowContainers = () => {
- const { data, isLoading } = api.docker.getContainers.useQuery();
+interface Props {
+ serverId?: string;
+}
+
+export const ShowContainers = ({ serverId }: Props) => {
+ const { data, isLoading } = api.docker.getContainers.useQuery({
+ serverId,
+ });
+
const [sorting, setSorting] = React.useState([]);
const [columnFilters, setColumnFilters] = React.useState(
[],
@@ -103,83 +110,99 @@ export const ShowContainers = () => {
-
-
- {table.getHeaderGroups().map((headerGroup) => (
-
- {headerGroup.headers.map((header) => {
- return (
-
- {header.isPlaceholder
- ? null
- : flexRender(
- header.column.columnDef.header,
- header.getContext(),
- )}
-
- );
- })}
-
- ))}
-
-
- {table.getRowModel().rows?.length ? (
- table.getRowModel().rows.map((row) => (
-
- {row.getVisibleCells().map((cell) => (
-
- {flexRender(
- cell.column.columnDef.cell,
- cell.getContext(),
- )}
-
- ))}
+ {isLoading ? (
+
+
+ Loading...
+
+
+ ) : data?.length === 0 ? (
+
+
+ No results.
+
+
+ ) : (
+
+
+ {table.getHeaderGroups().map((headerGroup) => (
+
+ {headerGroup.headers.map((header) => {
+ return (
+
+ {header.isPlaceholder
+ ? null
+ : flexRender(
+ header.column.columnDef.header,
+ header.getContext(),
+ )}
+
+ );
+ })}
- ))
- ) : (
-
-
- {isLoading ? (
-
-
- Loading...
-
-
- ) : (
- <>No results.>
- )}
-
-
- )}
-
-
+ ))}
+
+
+ {table?.getRowModel()?.rows?.length ? (
+ table.getRowModel().rows.map((row) => (
+
+ {row.getVisibleCells().map((cell) => (
+
+ {flexRender(
+ cell.column.columnDef.cell,
+ cell.getContext(),
+ )}
+
+ ))}
+
+ ))
+ ) : (
+
+
+ {isLoading ? (
+
+
+ Loading...
+
+
+ ) : (
+ <>No results.>
+ )}
+
+
+ )}
+
+
+ )}
-
-
-
table.previousPage()}
- disabled={!table.getCanPreviousPage()}
- >
- Previous
-
-
table.nextPage()}
- disabled={!table.getCanNextPage()}
- >
- Next
-
+ {data && data?.length > 0 && (
+
+
+ table.previousPage()}
+ disabled={!table.getCanPreviousPage()}
+ >
+ Previous
+
+ table.nextPage()}
+ disabled={!table.getCanNextPage()}
+ >
+ Next
+
+
-
+ )}
);
diff --git a/apps/dokploy/components/dashboard/docker/terminal/docker-terminal-modal.tsx b/apps/dokploy/components/dashboard/docker/terminal/docker-terminal-modal.tsx
index d8f87f393..876d6838b 100644
--- a/apps/dokploy/components/dashboard/docker/terminal/docker-terminal-modal.tsx
+++ b/apps/dokploy/components/dashboard/docker/terminal/docker-terminal-modal.tsx
@@ -18,10 +18,15 @@ const Terminal = dynamic(
interface Props {
containerId: string;
+ serverId?: string;
children?: React.ReactNode;
}
-export const DockerTerminalModal = ({ children, containerId }: Props) => {
+export const DockerTerminalModal = ({
+ children,
+ containerId,
+ serverId,
+}: Props) => {
return (
@@ -40,7 +45,11 @@ export const DockerTerminalModal = ({ children, containerId }: Props) => {
-
+
);
diff --git a/apps/dokploy/components/dashboard/docker/terminal/docker-terminal.tsx b/apps/dokploy/components/dashboard/docker/terminal/docker-terminal.tsx
index 03001af70..4008d6fd5 100644
--- a/apps/dokploy/components/dashboard/docker/terminal/docker-terminal.tsx
+++ b/apps/dokploy/components/dashboard/docker/terminal/docker-terminal.tsx
@@ -8,9 +8,14 @@ import { AttachAddon } from "@xterm/addon-attach";
interface Props {
id: string;
containerId: string;
+ serverId?: string;
}
-export const DockerTerminal: React.FC = ({ id, containerId }) => {
+export const DockerTerminal: React.FC = ({
+ id,
+ containerId,
+ serverId,
+}) => {
const termRef = useRef(null);
const [activeWay, setActiveWay] = React.useState("bash");
useEffect(() => {
@@ -33,7 +38,7 @@ export const DockerTerminal: React.FC = ({ id, containerId }) => {
const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
- const wsUrl = `${protocol}//${window.location.host}/docker-container-terminal?containerId=${containerId}&activeWay=${activeWay}`;
+ const wsUrl = `${protocol}//${window.location.host}/docker-container-terminal?containerId=${containerId}&activeWay=${activeWay}${serverId ? `&serverId=${serverId}` : ""}`;
const ws = new WebSocket(wsUrl);
diff --git a/apps/dokploy/components/dashboard/file-system/show-traefik-file.tsx b/apps/dokploy/components/dashboard/file-system/show-traefik-file.tsx
index d2dc1d78a..3dfe98754 100644
--- a/apps/dokploy/components/dashboard/file-system/show-traefik-file.tsx
+++ b/apps/dokploy/components/dashboard/file-system/show-traefik-file.tsx
@@ -13,6 +13,7 @@ import {
} from "@/components/ui/form";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
+import { Loader2 } from "lucide-react";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
@@ -29,12 +30,18 @@ type UpdateServerMiddlewareConfig = z.infer<
interface Props {
path: string;
+ serverId?: string;
}
-export const ShowTraefikFile = ({ path }: Props) => {
- const { data, refetch } = api.settings.readTraefikFile.useQuery(
+export const ShowTraefikFile = ({ path, serverId }: Props) => {
+ const {
+ data,
+ refetch,
+ isLoading: isLoadingFile,
+ } = api.settings.readTraefikFile.useQuery(
{
path,
+ serverId,
},
{
enabled: !!path,
@@ -54,11 +61,9 @@ export const ShowTraefikFile = ({ path }: Props) => {
});
useEffect(() => {
- if (data) {
- form.reset({
- traefikConfig: data || "",
- });
- }
+ form.reset({
+ traefikConfig: data || "",
+ });
}, [form, form.reset, data]);
const onSubmit = async (data: UpdateServerMiddlewareConfig) => {
@@ -74,6 +79,7 @@ export const ShowTraefikFile = ({ path }: Props) => {
await mutateAsync({
traefikConfig: data.traefikConfig,
path,
+ serverId,
})
.then(async () => {
toast.success("Traefik config Updated");
@@ -93,20 +99,28 @@ export const ShowTraefikFile = ({ path }: Props) => {
className="w-full relative z-[5]"
>
- (
-
- Traefik config
-
- {path}
-
-
-
+
+ Loading...
+
+
+
+ ) : (
+ (
+
+ Traefik config
+
+ {path}
+
+
+
-
+ {...field}
+ />
+
-
-
-
-
- {
- setCanEdit(!canEdit);
- }}
- >
- {canEdit ? "Unlock" : "Lock"}
-
-
-
- )}
- />
+
+
+
+
+ {
+ setCanEdit(!canEdit);
+ }}
+ >
+ {canEdit ? "Unlock" : "Lock"}
+
+
+
+ )}
+ />
+ )}
-
+
Update
diff --git a/apps/dokploy/components/dashboard/file-system/show-traefik-system.tsx b/apps/dokploy/components/dashboard/file-system/show-traefik-system.tsx
index e3e874c5a..0aaf9990b 100644
--- a/apps/dokploy/components/dashboard/file-system/show-traefik-system.tsx
+++ b/apps/dokploy/components/dashboard/file-system/show-traefik-system.tsx
@@ -1,20 +1,47 @@
-import React from "react";
-
+import { AlertBlock } from "@/components/shared/alert-block";
import { Tree } from "@/components/ui/file-tree";
-import { api } from "@/utils/api";
-import { FileIcon, Folder, Workflow } from "lucide-react";
-
import { cn } from "@/lib/utils";
+import { api } from "@/utils/api";
+import { FileIcon, Folder, Loader2, Workflow } from "lucide-react";
+import React from "react";
import { ShowTraefikFile } from "./show-traefik-file";
-export const ShowTraefikSystem = () => {
+interface Props {
+ serverId?: string;
+}
+export const ShowTraefikSystem = ({ serverId }: Props) => {
const [file, setFile] = React.useState(null);
- const { data: directories } = api.settings.readDirectories.useQuery();
+ const {
+ data: directories,
+ isLoading,
+ error,
+ isError,
+ } = api.settings.readDirectories.useQuery(
+ {
+ serverId,
+ },
+ {
+ retry: 2,
+ },
+ );
return (
+ {isError && (
+
+ {error?.message}
+
+ )}
+ {isLoading && (
+
+
+ Loading...
+
+
+
+ )}
{directories?.length === 0 && (
@@ -34,7 +61,7 @@ export const ShowTraefikSystem = () => {
/>
{file ? (
-
+
) : (
diff --git a/apps/dokploy/components/dashboard/mariadb/general/show-external-mariadb-credentials.tsx b/apps/dokploy/components/dashboard/mariadb/general/show-external-mariadb-credentials.tsx
index a908de077..c06cacaa8 100644
--- a/apps/dokploy/components/dashboard/mariadb/general/show-external-mariadb-credentials.tsx
+++ b/apps/dokploy/components/dashboard/mariadb/general/show-external-mariadb-credentials.tsx
@@ -48,6 +48,7 @@ export const ShowExternalMariadbCredentials = ({ mariadbId }: Props) => {
const { data, refetch } = api.mariadb.one.useQuery({ mariadbId });
const { mutateAsync, isLoading } = api.mariadb.saveExternalPort.useMutation();
const [connectionUrl, setConnectionUrl] = useState("");
+ const getIp = data?.server?.ipAddress || ip;
const form = useForm({
defaultValues: {},
resolver: zodResolver(DockerProviderSchema),
@@ -79,7 +80,7 @@ export const ShowExternalMariadbCredentials = ({ mariadbId }: Props) => {
const buildConnectionUrl = () => {
const port = form.watch("externalPort") || data?.externalPort;
- return `mariadb://${data?.databaseUser}:${data?.databasePassword}@${ip}:${port}/${data?.databaseName}`;
+ return `mariadb://${data?.databaseUser}:${data?.databasePassword}@${getIp}:${port}/${data?.databaseName}`;
};
setConnectionUrl(buildConnectionUrl());
@@ -90,7 +91,7 @@ export const ShowExternalMariadbCredentials = ({ mariadbId }: Props) => {
form,
data?.databaseName,
data?.databaseUser,
- ip,
+ getIp,
]);
return (
<>
diff --git a/apps/dokploy/components/dashboard/mariadb/general/show-general-mariadb.tsx b/apps/dokploy/components/dashboard/mariadb/general/show-general-mariadb.tsx
index 44b6e39cb..925e213d8 100644
--- a/apps/dokploy/components/dashboard/mariadb/general/show-general-mariadb.tsx
+++ b/apps/dokploy/components/dashboard/mariadb/general/show-general-mariadb.tsx
@@ -36,7 +36,10 @@ export const ShowGeneralMariadb = ({ mariadbId }: Props) => {
) : (
)}
-
+
Open Terminal
diff --git a/apps/dokploy/components/dashboard/mongo/general/show-external-mongo-credentials.tsx b/apps/dokploy/components/dashboard/mongo/general/show-external-mongo-credentials.tsx
index 36d04c9cb..7cfab289d 100644
--- a/apps/dokploy/components/dashboard/mongo/general/show-external-mongo-credentials.tsx
+++ b/apps/dokploy/components/dashboard/mongo/general/show-external-mongo-credentials.tsx
@@ -48,7 +48,7 @@ export const ShowExternalMongoCredentials = ({ mongoId }: Props) => {
const { data, refetch } = api.mongo.one.useQuery({ mongoId });
const { mutateAsync, isLoading } = api.mongo.saveExternalPort.useMutation();
const [connectionUrl, setConnectionUrl] = useState("");
-
+ const getIp = data?.server?.ipAddress || ip;
const form = useForm({
defaultValues: {},
resolver: zodResolver(DockerProviderSchema),
@@ -80,7 +80,7 @@ export const ShowExternalMongoCredentials = ({ mongoId }: Props) => {
const buildConnectionUrl = () => {
const port = form.watch("externalPort") || data?.externalPort;
- return `mongodb://${data?.databaseUser}:${data?.databasePassword}@${ip}:${port}`;
+ return `mongodb://${data?.databaseUser}:${data?.databasePassword}@${getIp}:${port}`;
};
setConnectionUrl(buildConnectionUrl());
@@ -90,7 +90,7 @@ export const ShowExternalMongoCredentials = ({ mongoId }: Props) => {
data?.databasePassword,
form,
data?.databaseUser,
- ip,
+ getIp,
]);
return (
diff --git a/apps/dokploy/components/dashboard/mongo/general/show-general-mongo.tsx b/apps/dokploy/components/dashboard/mongo/general/show-general-mongo.tsx
index 2e181c5fa..c8ae007af 100644
--- a/apps/dokploy/components/dashboard/mongo/general/show-general-mongo.tsx
+++ b/apps/dokploy/components/dashboard/mongo/general/show-general-mongo.tsx
@@ -34,7 +34,10 @@ export const ShowGeneralMongo = ({ mongoId }: Props) => {
) : (
)}
-
+
Open Terminal
diff --git a/apps/dokploy/components/dashboard/monitoring/docker/docker-memory-chart.tsx b/apps/dokploy/components/dashboard/monitoring/docker/docker-memory-chart.tsx
index 78791ce1d..36f1edb81 100644
--- a/apps/dokploy/components/dashboard/monitoring/docker/docker-memory-chart.tsx
+++ b/apps/dokploy/components/dashboard/monitoring/docker/docker-memory-chart.tsx
@@ -23,7 +23,7 @@ export const DockerMemoryChart = ({
return {
time: item.time,
name: `Point ${index + 1}`,
- usage: (item.value.used / 1024).toFixed(2),
+ usage: (item.value.used / 1024 ** 3).toFixed(2),
};
});
return (
diff --git a/apps/dokploy/components/dashboard/monitoring/docker/show.tsx b/apps/dokploy/components/dashboard/monitoring/docker/show.tsx
index c54e7c0ca..365615ced 100644
--- a/apps/dokploy/components/dashboard/monitoring/docker/show.tsx
+++ b/apps/dokploy/components/dashboard/monitoring/docker/show.tsx
@@ -208,9 +208,7 @@ export const DockerMonitoring = ({
Memory
- {`Used: ${(currentData.memory.value.used / 1024).toFixed(
- 2,
- )} GB / Limit: ${(currentData.memory.value.total / 1024).toFixed(2)} GB`}
+ {`Used: ${(currentData.memory.value.used / 1024 ** 3).toFixed(2)} GB / Limit: ${(currentData.memory.value.total / 1024 ** 3).toFixed(2)} GB`}
{appName === "dokploy" && (
@@ -240,9 +238,9 @@ export const DockerMonitoring = ({
Block I/O
- {`Used: ${currentData.block.value.readMb.toFixed(
+ {`Read: ${currentData.block.value.readMb.toFixed(
2,
- )} MB / Limit: ${currentData.block.value.writeMb.toFixed(
+ )} MB / Write: ${currentData.block.value.writeMb.toFixed(
3,
)} MB`}
diff --git a/apps/dokploy/components/dashboard/mysql/general/show-external-mysql-credentials.tsx b/apps/dokploy/components/dashboard/mysql/general/show-external-mysql-credentials.tsx
index 18c1adafe..009c8c3a5 100644
--- a/apps/dokploy/components/dashboard/mysql/general/show-external-mysql-credentials.tsx
+++ b/apps/dokploy/components/dashboard/mysql/general/show-external-mysql-credentials.tsx
@@ -48,7 +48,7 @@ export const ShowExternalMysqlCredentials = ({ mysqlId }: Props) => {
const { data, refetch } = api.mysql.one.useQuery({ mysqlId });
const { mutateAsync, isLoading } = api.mysql.saveExternalPort.useMutation();
const [connectionUrl, setConnectionUrl] = useState("");
-
+ const getIp = data?.server?.ipAddress || ip;
const form = useForm({
defaultValues: {},
resolver: zodResolver(DockerProviderSchema),
@@ -80,7 +80,7 @@ export const ShowExternalMysqlCredentials = ({ mysqlId }: Props) => {
const buildConnectionUrl = () => {
const port = form.watch("externalPort") || data?.externalPort;
- return `mysql://${data?.databaseUser}:${data?.databasePassword}@${ip}:${port}/${data?.databaseName}`;
+ return `mysql://${data?.databaseUser}:${data?.databasePassword}@${getIp}:${port}/${data?.databaseName}`;
};
setConnectionUrl(buildConnectionUrl());
@@ -91,7 +91,7 @@ export const ShowExternalMysqlCredentials = ({ mysqlId }: Props) => {
data?.databaseName,
data?.databaseUser,
form,
- ip,
+ getIp,
]);
return (
<>
diff --git a/apps/dokploy/components/dashboard/mysql/general/show-general-mysql.tsx b/apps/dokploy/components/dashboard/mysql/general/show-general-mysql.tsx
index 1a5d723ab..f9928cb1d 100644
--- a/apps/dokploy/components/dashboard/mysql/general/show-general-mysql.tsx
+++ b/apps/dokploy/components/dashboard/mysql/general/show-general-mysql.tsx
@@ -35,7 +35,10 @@ export const ShowGeneralMysql = ({ mysqlId }: Props) => {
)}
-
+
Open Terminal
diff --git a/apps/dokploy/components/dashboard/postgres/general/show-external-postgres-credentials.tsx b/apps/dokploy/components/dashboard/postgres/general/show-external-postgres-credentials.tsx
index 28a96eb24..e1b4369a6 100644
--- a/apps/dokploy/components/dashboard/postgres/general/show-external-postgres-credentials.tsx
+++ b/apps/dokploy/components/dashboard/postgres/general/show-external-postgres-credentials.tsx
@@ -48,6 +48,7 @@ export const ShowExternalPostgresCredentials = ({ postgresId }: Props) => {
const { data, refetch } = api.postgres.one.useQuery({ postgresId });
const { mutateAsync, isLoading } =
api.postgres.saveExternalPort.useMutation();
+ const getIp = data?.server?.ipAddress || ip;
const [connectionUrl, setConnectionUrl] = useState("");
const form = useForm({
@@ -79,10 +80,9 @@ export const ShowExternalPostgresCredentials = ({ postgresId }: Props) => {
useEffect(() => {
const buildConnectionUrl = () => {
- const hostname = window.location.hostname;
const port = form.watch("externalPort") || data?.externalPort;
- return `postgresql://${data?.databaseUser}:${data?.databasePassword}@${ip}:${port}/${data?.databaseName}`;
+ return `postgresql://${data?.databaseUser}:${data?.databasePassword}@${getIp}:${port}/${data?.databaseName}`;
};
setConnectionUrl(buildConnectionUrl());
@@ -92,7 +92,7 @@ export const ShowExternalPostgresCredentials = ({ postgresId }: Props) => {
data?.databasePassword,
form,
data?.databaseName,
- ip,
+ getIp,
]);
return (
diff --git a/apps/dokploy/components/dashboard/postgres/general/show-general-postgres.tsx b/apps/dokploy/components/dashboard/postgres/general/show-general-postgres.tsx
index a2aa17cbc..781080ee5 100644
--- a/apps/dokploy/components/dashboard/postgres/general/show-general-postgres.tsx
+++ b/apps/dokploy/components/dashboard/postgres/general/show-general-postgres.tsx
@@ -38,7 +38,10 @@ export const ShowGeneralPostgres = ({ postgresId }: Props) => {
)}
-
+
Open Terminal
diff --git a/apps/dokploy/components/dashboard/project/add-application.tsx b/apps/dokploy/components/dashboard/project/add-application.tsx
index cbe48b6dc..2ecacdf63 100644
--- a/apps/dokploy/components/dashboard/project/add-application.tsx
+++ b/apps/dokploy/components/dashboard/project/add-application.tsx
@@ -19,11 +19,26 @@ import {
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
+import {
+ Select,
+ SelectContent,
+ SelectGroup,
+ SelectItem,
+ SelectLabel,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui/select";
import { Textarea } from "@/components/ui/textarea";
+import {
+ Tooltip,
+ TooltipContent,
+ TooltipProvider,
+ TooltipTrigger,
+} from "@/components/ui/tooltip";
import { slugify } from "@/lib/slug";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
-import { Folder } from "lucide-react";
+import { Folder, HelpCircle } from "lucide-react";
import { useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
@@ -43,6 +58,7 @@ const AddTemplateSchema = z.object({
"App name supports lowercase letters, numbers, '-' and can only start and end letters, and does not support continuous '-'",
}),
description: z.string().optional(),
+ serverId: z.string().optional(),
});
type AddTemplate = z.infer;
@@ -54,8 +70,10 @@ interface Props {
export const AddApplication = ({ projectId, projectName }: Props) => {
const utils = api.useUtils();
+
const [visible, setVisible] = useState(false);
const slug = slugify(projectName);
+ const { data: servers } = api.server.withSSHKey.useQuery();
const { mutateAsync, isLoading, error, isError } =
api.application.create.useMutation();
@@ -75,6 +93,7 @@ export const AddApplication = ({ projectId, projectName }: Props) => {
appName: data.appName,
description: data.description,
projectId,
+ serverId: data.serverId,
})
.then(async () => {
toast.success("Service Created");
@@ -126,7 +145,10 @@ export const AddApplication = ({ projectId, projectName }: Props) => {
{...field}
onChange={(e) => {
const val = e.target.value?.trim() || "";
- form.setValue("appName", `${slug}-${val}`);
+ form.setValue(
+ "appName",
+ `${slug}-${val.toLowerCase().replaceAll(" ", "-")}`,
+ );
field.onChange(val);
}}
/>
@@ -135,6 +157,57 @@ export const AddApplication = ({ projectId, projectName }: Props) => {
)}
/>
+ (
+
+
+
+
+
+ Select a Server (Optional)
+
+
+
+
+
+ If not server is selected, the application will be
+ deployed on the server where the user is logged in.
+
+
+
+
+
+
+
+
+
+
+
+ {servers?.map((server) => (
+
+ {server.name}
+
+ ))}
+ Servers ({servers?.length})
+
+
+
+
+
+ )}
+ />
;
@@ -62,7 +71,9 @@ interface Props {
export const AddCompose = ({ projectId, projectName }: Props) => {
const utils = api.useUtils();
+ const [visible, setVisible] = useState(false);
const slug = slugify(projectName);
+ const { data: servers } = api.server.withSSHKey.useQuery();
const { mutateAsync, isLoading, error, isError } =
api.compose.create.useMutation();
@@ -87,9 +98,11 @@ export const AddCompose = ({ projectId, projectName }: Props) => {
projectId,
composeType: data.composeType,
appName: data.appName,
+ serverId: data.serverId,
})
.then(async () => {
toast.success("Compose Created");
+ setVisible(false);
await utils.project.one.invalidate({
projectId,
});
@@ -100,7 +113,7 @@ export const AddCompose = ({ projectId, projectName }: Props) => {
};
return (
-
+
{
{...field}
onChange={(e) => {
const val = e.target.value?.trim() || "";
- form.setValue("appName", `${slug}-${val}`);
+ form.setValue(
+ "appName",
+ `${slug}-${val.toLowerCase()}`,
+ );
field.onChange(val);
}}
/>
@@ -148,6 +164,57 @@ export const AddCompose = ({ projectId, projectName }: Props) => {
)}
/>
+ (
+
+
+
+
+
+ Select a Server (Optional)
+
+
+
+
+
+ If not server is selected, the application will be
+ deployed on the server where the user is logged in.
+
+
+
+
+
+
+
+
+
+
+
+ {servers?.map((server) => (
+
+ {server.name}
+
+ ))}
+ Servers ({servers?.length})
+
+
+
+
+
+ )}
+ />
{
const utils = api.useUtils();
const [visible, setVisible] = useState(false);
const slug = slugify(projectName);
+ const { data: servers } = api.server.withSSHKey.useQuery();
const postgresMutation = api.postgres.create.useMutation();
const mongoMutation = api.mongo.create.useMutation();
const redisMutation = api.redis.create.useMutation();
@@ -161,6 +172,7 @@ export const AddDatabase = ({ projectId, projectName }: Props) => {
description: "",
databaseName: "",
databaseUser: "",
+ serverId: null,
},
resolver: zodResolver(mySchema),
});
@@ -183,6 +195,7 @@ export const AddDatabase = ({ projectId, projectName }: Props) => {
appName: data.appName,
dockerImage: defaultDockerImage,
projectId,
+ serverId: data.serverId,
description: data.description,
};
@@ -191,8 +204,10 @@ export const AddDatabase = ({ projectId, projectName }: Props) => {
...commonParams,
databasePassword: data.databasePassword,
databaseName: data.databaseName,
+
databaseUser:
data.databaseUser || databasesUserDefaultPlaceholder[data.type],
+ serverId: data.serverId,
});
} else if (data.type === "mongo") {
promise = mongoMutation.mutateAsync({
@@ -200,11 +215,13 @@ export const AddDatabase = ({ projectId, projectName }: Props) => {
databasePassword: data.databasePassword,
databaseUser:
data.databaseUser || databasesUserDefaultPlaceholder[data.type],
+ serverId: data.serverId,
});
} else if (data.type === "redis") {
promise = redisMutation.mutateAsync({
...commonParams,
databasePassword: data.databasePassword,
+ serverId: data.serverId,
projectId,
});
} else if (data.type === "mariadb") {
@@ -215,6 +232,7 @@ export const AddDatabase = ({ projectId, projectName }: Props) => {
databaseName: data.databaseName,
databaseUser:
data.databaseUser || databasesUserDefaultPlaceholder[data.type],
+ serverId: data.serverId,
});
} else if (data.type === "mysql") {
promise = mysqlMutation.mutateAsync({
@@ -224,6 +242,7 @@ export const AddDatabase = ({ projectId, projectName }: Props) => {
databaseUser:
data.databaseUser || databasesUserDefaultPlaceholder[data.type],
databaseRootPassword: data.databaseRootPassword,
+ serverId: data.serverId,
});
}
@@ -342,7 +361,10 @@ export const AddDatabase = ({ projectId, projectName }: Props) => {
{...field}
onChange={(e) => {
const val = e.target.value?.trim() || "";
- form.setValue("appName", `${slug}-${val}`);
+ form.setValue(
+ "appName",
+ `${slug}-${val.toLowerCase()}`,
+ );
field.onChange(val);
}}
/>
@@ -352,6 +374,39 @@ export const AddDatabase = ({ projectId, projectName }: Props) => {
)}
/>
+ (
+
+ Select a Server
+
+
+
+
+
+
+ {servers?.map((server) => (
+
+ {server.name}
+
+ ))}
+
+ Servers ({servers?.length})
+
+
+
+
+
+
+ )}
+ />
{
const [open, setOpen] = useState(false);
const { data } = api.compose.templates.useQuery();
const [selectedTags, setSelectedTags] = useState([]);
+ const { data: servers } = api.server.withSSHKey.useQuery();
const { data: tags, isLoading: isLoadingTags } =
api.compose.getTags.useQuery();
const utils = api.useUtils();
+
+ const [serverId, setServerId] = useState(undefined);
const { mutateAsync, isLoading, error, isError } =
api.compose.deployTemplate.useMutation();
@@ -109,7 +129,6 @@ export const AddTemplate = ({ projectId }: Props) => {
role="combobox"
className={cn(
"md:max-w-[15rem] w-full justify-between !bg-input",
- // !field.value && "text-muted-foreground",
)}
>
{isLoadingTags
@@ -268,30 +287,79 @@ export const AddTemplate = ({ projectId }: Props) => {
{template.name} template and add it to your
project.
+
+
+
+
+
+
+ Select a Server (Optional)
+
+
+
+
+
+ If not server is selected, the
+ application will be deployed on the
+ server where the user is logged in.
+
+
+
+
+
+ {
+ setServerId(e);
+ }}
+ >
+
+
+
+
+
+ {servers?.map((server) => (
+
+ {server.name}
+
+ ))}
+
+ Servers ({servers?.length})
+
+
+
+
+
Cancel
{
- await mutateAsync({
+ const promise = mutateAsync({
projectId,
+ serverId: serverId || undefined,
id: template.id,
- })
- .then(async () => {
- toast.success(
- `Succesfully created ${template.name} application from template`,
- );
-
+ });
+ toast.promise(promise, {
+ loading: "Setting up...",
+ success: (data) => {
utils.project.one.invalidate({
projectId,
});
setOpen(false);
- })
- .catch(() => {
- toast.error(
- `Error creating ${template.name} application from template`,
- );
- });
+ return `${template.name} template created succesfully`;
+ },
+ error: (err) => {
+ return `Ocurred an error deploying ${template.name} template`;
+ },
+ });
}}
>
Confirm
diff --git a/apps/dokploy/components/dashboard/projects/add.tsx b/apps/dokploy/components/dashboard/projects/add.tsx
index 1b9f37f8d..bd8f268fd 100644
--- a/apps/dokploy/components/dashboard/projects/add.tsx
+++ b/apps/dokploy/components/dashboard/projects/add.tsx
@@ -17,6 +17,7 @@ import {
FormLabel,
FormMessage,
} from "@/components/ui/form";
+
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import { api } from "@/utils/api";
@@ -109,6 +110,7 @@ export const AddProject = () => {
)}
/>
+
{
const { data, refetch } = api.redis.one.useQuery({ redisId });
const { mutateAsync, isLoading } = api.redis.saveExternalPort.useMutation();
const [connectionUrl, setConnectionUrl] = useState("");
+ const getIp = data?.server?.ipAddress || ip;
const form = useForm({
defaultValues: {},
@@ -81,11 +82,11 @@ export const ShowExternalRedisCredentials = ({ redisId }: Props) => {
const hostname = window.location.hostname;
const port = form.watch("externalPort") || data?.externalPort;
- return `redis://default:${data?.databasePassword}@${ip}:${port}`;
+ return `redis://default:${data?.databasePassword}@${getIp}:${port}`;
};
setConnectionUrl(buildConnectionUrl());
- }, [data?.appName, data?.externalPort, data?.databasePassword, form, ip]);
+ }, [data?.appName, data?.externalPort, data?.databasePassword, form, getIp]);
return (
<>
diff --git a/apps/dokploy/components/dashboard/redis/general/show-general-redis.tsx b/apps/dokploy/components/dashboard/redis/general/show-general-redis.tsx
index 555451d9d..696b21430 100644
--- a/apps/dokploy/components/dashboard/redis/general/show-general-redis.tsx
+++ b/apps/dokploy/components/dashboard/redis/general/show-general-redis.tsx
@@ -37,7 +37,10 @@ export const ShowGeneralRedis = ({ redisId }: Props) => {
)}
-
+
Open Terminal
diff --git a/apps/dokploy/components/dashboard/settings/cluster/registry/add-docker-registry.tsx b/apps/dokploy/components/dashboard/settings/cluster/registry/add-docker-registry.tsx
index 193bf6723..0a1ee6143 100644
--- a/apps/dokploy/components/dashboard/settings/cluster/registry/add-docker-registry.tsx
+++ b/apps/dokploy/components/dashboard/settings/cluster/registry/add-docker-registry.tsx
@@ -17,10 +17,18 @@ import {
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
+import {
+ Select,
+ SelectContent,
+ SelectGroup,
+ SelectItem,
+ SelectLabel,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui/select";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { AlertTriangle, Container } from "lucide-react";
-import { useRouter } from "next/router";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
@@ -36,10 +44,9 @@ const AddRegistrySchema = z.object({
password: z.string().min(1, {
message: "Password is required",
}),
- registryUrl: z.string().min(1, {
- message: "Registry URL is required",
- }),
+ registryUrl: z.string(),
imagePrefix: z.string(),
+ serverId: z.string().optional(),
});
type AddRegistry = z.infer;
@@ -48,9 +55,9 @@ export const AddRegistry = () => {
const utils = api.useUtils();
const [isOpen, setIsOpen] = useState(false);
const { mutateAsync, error, isError } = api.registry.create.useMutation();
+ const { data: servers } = api.server.withSSHKey.useQuery();
const { mutateAsync: testRegistry, isLoading } =
api.registry.testRegistry.useMutation();
- const router = useRouter();
const form = useForm({
defaultValues: {
username: "",
@@ -58,6 +65,7 @@ export const AddRegistry = () => {
registryUrl: "",
imagePrefix: "",
registryName: "",
+ serverId: "",
},
resolver: zodResolver(AddRegistrySchema),
});
@@ -67,6 +75,7 @@ export const AddRegistry = () => {
const registryUrl = form.watch("registryUrl");
const registryName = form.watch("registryName");
const imagePrefix = form.watch("imagePrefix");
+ const serverId = form.watch("serverId");
useEffect(() => {
form.reset({
@@ -74,6 +83,7 @@ export const AddRegistry = () => {
password: "",
registryUrl: "",
imagePrefix: "",
+ serverId: "",
});
}, [form, form.reset, form.formState.isSubmitSuccessful]);
@@ -85,6 +95,7 @@ export const AddRegistry = () => {
registryUrl: data.registryUrl,
registryType: "cloud",
imagePrefix: data.imagePrefix,
+ serverId: data.serverId,
})
.then(async (data) => {
await utils.registry.all.invalidate();
@@ -211,34 +222,77 @@ export const AddRegistry = () => {
)}
/>
-
- {
- await testRegistry({
- username: username,
- password: password,
- registryUrl: registryUrl,
- registryName: registryName,
- registryType: "cloud",
- imagePrefix: imagePrefix,
- })
- .then((data) => {
- if (data) {
- toast.success("Registry Tested Successfully");
- } else {
- toast.error("Registry Test Failed");
- }
+
+
+
+ Select a server to test the registry. If you don't have a
+ server choose the default one.
+
+ (
+
+ Server (Optional)
+
+
+
+
+
+
+
+ Servers
+ {servers?.map((server) => (
+
+ {server.name}
+
+ ))}
+ None
+
+
+
+
+
+
+
+ )}
+ />
+ {
+ await testRegistry({
+ username: username,
+ password: password,
+ registryUrl: registryUrl,
+ registryName: registryName,
+ registryType: "cloud",
+ imagePrefix: imagePrefix,
+ serverId: serverId,
})
- .catch(() => {
- toast.error("Error to test the registry");
- });
- }}
- >
- Test Registry
-
+ .then((data) => {
+ if (data) {
+ toast.success("Registry Tested Successfully");
+ } else {
+ toast.error("Registry Test Failed");
+ }
+ })
+ .catch(() => {
+ toast.error("Error to test the registry");
+ });
+ }}
+ >
+ Test Registry
+
+
+
Create
diff --git a/apps/dokploy/components/dashboard/settings/cluster/registry/update-docker-registry.tsx b/apps/dokploy/components/dashboard/settings/cluster/registry/update-docker-registry.tsx
index c84c019ac..9c5f281e5 100644
--- a/apps/dokploy/components/dashboard/settings/cluster/registry/update-docker-registry.tsx
+++ b/apps/dokploy/components/dashboard/settings/cluster/registry/update-docker-registry.tsx
@@ -17,6 +17,15 @@ import {
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
+import {
+ Select,
+ SelectContent,
+ SelectGroup,
+ SelectItem,
+ SelectLabel,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui/select";
import { cn } from "@/lib/utils";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
@@ -34,10 +43,9 @@ const updateRegistry = z.object({
message: "Username is required",
}),
password: z.string(),
- registryUrl: z.string().min(1, {
- message: "Registry URL is required",
- }),
+ registryUrl: z.string(),
imagePrefix: z.string(),
+ serverId: z.string().optional(),
});
type UpdateRegistry = z.infer;
@@ -48,6 +56,8 @@ interface Props {
export const UpdateDockerRegistry = ({ registryId }: Props) => {
const utils = api.useUtils();
+ const { data: servers } = api.server.withSSHKey.useQuery();
+
const { mutateAsync: testRegistry, isLoading } =
api.registry.testRegistry.useMutation();
const { data, refetch } = api.registry.one.useQuery(
@@ -69,15 +79,19 @@ export const UpdateDockerRegistry = ({ registryId }: Props) => {
username: "",
password: "",
registryUrl: "",
+ serverId: "",
},
resolver: zodResolver(updateRegistry),
});
+ console.log(form.formState.errors);
+
const password = form.watch("password");
const username = form.watch("username");
const registryUrl = form.watch("registryUrl");
const registryName = form.watch("registryName");
const imagePrefix = form.watch("imagePrefix");
+ const serverId = form.watch("serverId");
useEffect(() => {
if (data) {
@@ -87,6 +101,7 @@ export const UpdateDockerRegistry = ({ registryId }: Props) => {
username: data.username || "",
password: "",
registryUrl: data.registryUrl || "",
+ serverId: "",
});
}
}, [form, form.reset, data]);
@@ -99,6 +114,7 @@ export const UpdateDockerRegistry = ({ registryId }: Props) => {
username: data.username,
registryUrl: data.registryUrl,
imagePrefix: data.imagePrefix,
+ serverId: data.serverId,
})
.then(async (data) => {
toast.success("Registry Updated");
@@ -224,13 +240,47 @@ export const UpdateDockerRegistry = ({ registryId }: Props) => {
-
- {isCloud && (
+
+
+
+ Select a server to test the registry. If you don't have a server
+ choose the default one.
+
+ (
+
+ Server (Optional)
+
+
+
+
+
+
+
+ Servers
+ {servers?.map((server) => (
+
+ {server.name}
+
+ ))}
+ None
+
+
+
+
+
+
+
+ )}
+ />
{
registryName: registryName,
registryType: "cloud",
imagePrefix: imagePrefix,
+ serverId: serverId,
})
.then((data) => {
if (data) {
@@ -258,12 +309,12 @@ export const UpdateDockerRegistry = ({ registryId }: Props) => {
>
Test Registry
- )}
+
Update
diff --git a/apps/dokploy/components/dashboard/settings/git/gitlab/edit-gitlab-provider.tsx b/apps/dokploy/components/dashboard/settings/git/gitlab/edit-gitlab-provider.tsx
index be38d78b7..db19e6292 100644
--- a/apps/dokploy/components/dashboard/settings/git/gitlab/edit-gitlab-provider.tsx
+++ b/apps/dokploy/components/dashboard/settings/git/gitlab/edit-gitlab-provider.tsx
@@ -19,10 +19,8 @@ import {
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { api } from "@/utils/api";
-import { useUrl } from "@/utils/hooks/use-url";
import { zodResolver } from "@hookform/resolvers/zod";
import { Edit } from "lucide-react";
-import { useRouter } from "next/router";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
diff --git a/apps/dokploy/components/dashboard/settings/servers/actions/show-dokploy-actions.tsx b/apps/dokploy/components/dashboard/settings/servers/actions/show-dokploy-actions.tsx
new file mode 100644
index 000000000..49f6772b5
--- /dev/null
+++ b/apps/dokploy/components/dashboard/settings/servers/actions/show-dokploy-actions.tsx
@@ -0,0 +1,52 @@
+import { Button } from "@/components/ui/button";
+import React from "react";
+
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuGroup,
+ DropdownMenuItem,
+ DropdownMenuLabel,
+ DropdownMenuSeparator,
+ DropdownMenuTrigger,
+} from "@/components/ui/dropdown-menu";
+import { api } from "@/utils/api";
+import { toast } from "sonner";
+import { ShowModalLogs } from "../../web-server/show-modal-logs";
+
+export const ShowDokployActions = () => {
+ const { mutateAsync: reloadServer, isLoading } =
+ api.settings.reloadServer.useMutation();
+
+ return (
+
+
+
+ Server
+
+
+
+ Actions
+
+
+ {
+ await reloadServer()
+ .then(async () => {
+ toast.success("Server Reloaded");
+ })
+ .catch(() => {
+ toast.success("Server Reloaded");
+ });
+ }}
+ >
+ Reload
+
+
+ Watch logs
+
+
+
+
+ );
+};
diff --git a/apps/dokploy/components/dashboard/settings/servers/actions/show-server-actions.tsx b/apps/dokploy/components/dashboard/settings/servers/actions/show-server-actions.tsx
new file mode 100644
index 000000000..1f7f2d146
--- /dev/null
+++ b/apps/dokploy/components/dashboard/settings/servers/actions/show-server-actions.tsx
@@ -0,0 +1,44 @@
+import { CardDescription, CardTitle } from "@/components/ui/card";
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogTitle,
+ DialogTrigger,
+} from "@/components/ui/dialog";
+import { DropdownMenuItem } from "@/components/ui/dropdown-menu";
+import { useState } from "react";
+import { ShowStorageActions } from "./show-storage-actions";
+import { ShowTraefikActions } from "./show-traefik-actions";
+import { ToggleDockerCleanup } from "./toggle-docker-cleanup";
+interface Props {
+ serverId: string;
+}
+
+export const ShowServerActions = ({ serverId }: Props) => {
+ const [isOpen, setIsOpen] = useState(false);
+ return (
+
+
+ e.preventDefault()}
+ >
+ View Actions
+
+
+
+
+ Web server settings
+ Reload or clean the web server.
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/apps/dokploy/components/dashboard/settings/servers/actions/show-storage-actions.tsx b/apps/dokploy/components/dashboard/settings/servers/actions/show-storage-actions.tsx
new file mode 100644
index 000000000..b3f9c3345
--- /dev/null
+++ b/apps/dokploy/components/dashboard/settings/servers/actions/show-storage-actions.tsx
@@ -0,0 +1,177 @@
+import { Button } from "@/components/ui/button";
+import React from "react";
+
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuGroup,
+ DropdownMenuItem,
+ DropdownMenuLabel,
+ DropdownMenuSeparator,
+ DropdownMenuTrigger,
+} from "@/components/ui/dropdown-menu";
+import { api } from "@/utils/api";
+import { toast } from "sonner";
+
+interface Props {
+ serverId?: string;
+}
+export const ShowStorageActions = ({ serverId }: Props) => {
+ const { mutateAsync: cleanAll, isLoading: cleanAllIsLoading } =
+ api.settings.cleanAll.useMutation();
+
+ const {
+ mutateAsync: cleanDockerBuilder,
+ isLoading: cleanDockerBuilderIsLoading,
+ } = api.settings.cleanDockerBuilder.useMutation();
+
+ const { mutateAsync: cleanMonitoring, isLoading: cleanMonitoringIsLoading } =
+ api.settings.cleanMonitoring.useMutation();
+ const {
+ mutateAsync: cleanUnusedImages,
+ isLoading: cleanUnusedImagesIsLoading,
+ } = api.settings.cleanUnusedImages.useMutation();
+
+ const {
+ mutateAsync: cleanUnusedVolumes,
+ isLoading: cleanUnusedVolumesIsLoading,
+ } = api.settings.cleanUnusedVolumes.useMutation();
+
+ const {
+ mutateAsync: cleanStoppedContainers,
+ isLoading: cleanStoppedContainersIsLoading,
+ } = api.settings.cleanStoppedContainers.useMutation();
+
+ return (
+
+
+
+ Space
+
+
+
+ Actions
+
+
+ {
+ await cleanUnusedImages({
+ serverId: serverId,
+ })
+ .then(async () => {
+ toast.success("Cleaned images");
+ })
+ .catch(() => {
+ toast.error("Error to clean images");
+ });
+ }}
+ >
+ Clean unused images
+
+ {
+ await cleanUnusedVolumes({
+ serverId: serverId,
+ })
+ .then(async () => {
+ toast.success("Cleaned volumes");
+ })
+ .catch(() => {
+ toast.error("Error to clean volumes");
+ });
+ }}
+ >
+ Clean unused volumes
+
+
+ {
+ await cleanStoppedContainers({
+ serverId: serverId,
+ })
+ .then(async () => {
+ toast.success("Stopped containers cleaned");
+ })
+ .catch(() => {
+ toast.error("Error to clean stopped containers");
+ });
+ }}
+ >
+ Clean stopped containers
+
+
+ {
+ await cleanDockerBuilder({
+ serverId: serverId,
+ })
+ .then(async () => {
+ toast.success("Cleaned Docker Builder");
+ })
+ .catch(() => {
+ toast.error("Error to clean Docker Builder");
+ });
+ }}
+ >
+ Clean Docker Builder & System
+
+ {!serverId && (
+ {
+ await cleanMonitoring()
+ .then(async () => {
+ toast.success("Cleaned Monitoring");
+ })
+ .catch(() => {
+ toast.error("Error to clean Monitoring");
+ });
+ }}
+ >
+ Clean Monitoring
+
+ )}
+
+ {
+ await cleanAll({
+ serverId: serverId,
+ })
+ .then(async () => {
+ toast.success("Cleaned all");
+ })
+ .catch(() => {
+ toast.error("Error to clean all");
+ });
+ }}
+ >
+ Clean all
+
+
+
+
+ );
+};
diff --git a/apps/dokploy/components/dashboard/settings/servers/actions/show-traefik-actions.tsx b/apps/dokploy/components/dashboard/settings/servers/actions/show-traefik-actions.tsx
new file mode 100644
index 000000000..a0ea3f5e8
--- /dev/null
+++ b/apps/dokploy/components/dashboard/settings/servers/actions/show-traefik-actions.tsx
@@ -0,0 +1,125 @@
+import { Button } from "@/components/ui/button";
+import {
+ Card,
+ CardContent,
+ CardDescription,
+ CardHeader,
+ CardTitle,
+} from "@/components/ui/card";
+import { Label } from "@/components/ui/label";
+import { Switch } from "@/components/ui/switch";
+import React from "react";
+
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuGroup,
+ DropdownMenuItem,
+ DropdownMenuLabel,
+ DropdownMenuSeparator,
+ DropdownMenuTrigger,
+} from "@/components/ui/dropdown-menu";
+import { api } from "@/utils/api";
+import { toast } from "sonner";
+
+import { cn } from "@/lib/utils";
+import { EditTraefikEnv } from "../../web-server/edit-traefik-env";
+import { ShowModalLogs } from "../../web-server/show-modal-logs";
+
+interface Props {
+ serverId?: string;
+}
+export const ShowTraefikActions = ({ serverId }: Props) => {
+ const { mutateAsync: reloadTraefik, isLoading: reloadTraefikIsLoading } =
+ api.settings.reloadTraefik.useMutation();
+
+ const { mutateAsync: toggleDashboard, isLoading: toggleDashboardIsLoading } =
+ api.settings.toggleDashboard.useMutation();
+
+ const { data: haveTraefikDashboardPortEnabled, refetch: refetchDashboard } =
+ api.settings.haveTraefikDashboardPortEnabled.useQuery({
+ serverId,
+ });
+
+ return (
+
+
+
+ Traefik
+
+
+
+ Actions
+
+
+ {
+ await reloadTraefik({
+ serverId: serverId,
+ })
+ .then(async () => {
+ toast.success("Traefik Reloaded");
+ })
+ .catch(() => {
+ toast.error("Error to reload the traefik");
+ });
+ }}
+ >
+ Reload
+
+
+ Watch logs
+
+
+ e.preventDefault()}
+ className="w-full cursor-pointer space-x-3"
+ >
+ Modify Env
+
+
+
+ {
+ await toggleDashboard({
+ enableDashboard: !haveTraefikDashboardPortEnabled,
+ serverId: serverId,
+ })
+ .then(async () => {
+ toast.success(
+ `${haveTraefikDashboardPortEnabled ? "Disabled" : "Enabled"} Dashboard`,
+ );
+ refetchDashboard();
+ })
+ .catch(() => {
+ toast.error(
+ `${haveTraefikDashboardPortEnabled ? "Disabled" : "Enabled"} Dashboard`,
+ );
+ });
+ }}
+ className="w-full cursor-pointer space-x-3"
+ >
+
+ {haveTraefikDashboardPortEnabled ? "Disable" : "Enable"} Dashboard
+
+
+ {/*
+
+ e.preventDefault()}
+ >
+ Enter the terminal
+
+ */}
+
+
+
+ );
+};
diff --git a/apps/dokploy/components/dashboard/settings/servers/actions/toggle-docker-cleanup.tsx b/apps/dokploy/components/dashboard/settings/servers/actions/toggle-docker-cleanup.tsx
new file mode 100644
index 000000000..17edaa991
--- /dev/null
+++ b/apps/dokploy/components/dashboard/settings/servers/actions/toggle-docker-cleanup.tsx
@@ -0,0 +1,52 @@
+import { Label } from "@/components/ui/label";
+import { Switch } from "@/components/ui/switch";
+import { api } from "@/utils/api";
+import { toast } from "sonner";
+
+interface Props {
+ serverId?: string;
+}
+export const ToggleDockerCleanup = ({ serverId }: Props) => {
+ const { data, refetch } = api.admin.one.useQuery(undefined, {
+ enabled: !serverId,
+ });
+
+ const { data: server, refetch: refetchServer } = api.server.one.useQuery(
+ {
+ serverId: serverId || "",
+ },
+ {
+ enabled: !!serverId,
+ },
+ );
+
+ const enabled = data?.enableDockerCleanup || server?.enableDockerCleanup;
+
+ const { mutateAsync } = api.settings.updateDockerCleanup.useMutation();
+ return (
+
+ {
+ await mutateAsync({
+ enableDockerCleanup: e,
+ serverId: serverId,
+ })
+ .then(async () => {
+ toast.success("Docker Cleanup Enabled");
+ })
+ .catch(() => {
+ toast.error("Docker Cleanup Error");
+ });
+
+ if (serverId) {
+ refetchServer();
+ } else {
+ refetch();
+ }
+ }}
+ />
+ Daily Docker Cleanup
+
+ );
+};
diff --git a/apps/dokploy/components/dashboard/settings/servers/add-server.tsx b/apps/dokploy/components/dashboard/settings/servers/add-server.tsx
new file mode 100644
index 000000000..8cb71167f
--- /dev/null
+++ b/apps/dokploy/components/dashboard/settings/servers/add-server.tsx
@@ -0,0 +1,267 @@
+import { AlertBlock } from "@/components/shared/alert-block";
+import { Button } from "@/components/ui/button";
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+} from "@/components/ui/dialog";
+import {
+ Form,
+ FormControl,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+} from "@/components/ui/form";
+import { Input } from "@/components/ui/input";
+import {
+ Select,
+ SelectContent,
+ SelectGroup,
+ SelectItem,
+ SelectLabel,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui/select";
+import { Textarea } from "@/components/ui/textarea";
+import { api } from "@/utils/api";
+import { zodResolver } from "@hookform/resolvers/zod";
+import { PlusIcon } from "lucide-react";
+import { useRouter } from "next/router";
+import { useEffect, useState } from "react";
+import { useForm } from "react-hook-form";
+import { toast } from "sonner";
+import { z } from "zod";
+
+const Schema = z.object({
+ name: z.string().min(1, {
+ message: "Name is required",
+ }),
+ description: z.string().optional(),
+ ipAddress: z.string().min(1, {
+ message: "IP Address is required",
+ }),
+ port: z.number().optional(),
+ username: z.string().optional(),
+ sshKeyId: z.string().min(1, {
+ message: "SSH Key is required",
+ }),
+});
+
+type Schema = z.infer;
+
+export const AddServer = () => {
+ const utils = api.useUtils();
+ const [isOpen, setIsOpen] = useState(false);
+ const { data: sshKeys } = api.sshKey.all.useQuery();
+ const { mutateAsync, error, isError } = api.server.create.useMutation();
+ const form = useForm({
+ defaultValues: {
+ description: "",
+ name: "",
+ ipAddress: "",
+ port: 22,
+ username: "root",
+ sshKeyId: "",
+ },
+ resolver: zodResolver(Schema),
+ });
+
+ useEffect(() => {
+ form.reset({
+ description: "",
+ name: "",
+ ipAddress: "",
+ port: 22,
+ username: "root",
+ sshKeyId: "",
+ });
+ }, [form, form.reset, form.formState.isSubmitSuccessful]);
+
+ const onSubmit = async (data: Schema) => {
+ await mutateAsync({
+ name: data.name,
+ description: data.description || "",
+ ipAddress: data.ipAddress || "",
+ port: data.port || 22,
+ username: data.username || "root",
+ sshKeyId: data.sshKeyId || "",
+ })
+ .then(async (data) => {
+ await utils.server.all.invalidate();
+ toast.success("Server Created");
+ setIsOpen(false);
+ })
+ .catch(() => {
+ toast.error("Error to create a server");
+ });
+ };
+
+ return (
+
+
+
+
+ Create Server
+
+
+
+
+ Add Server
+
+ Add a server to deploy your applications remotely.
+
+
+ {isError && {error?.message} }
+
+
+
+ (
+
+ Name
+
+
+
+
+
+
+ )}
+ />
+
+ (
+
+ Description
+
+
+
+
+
+
+ )}
+ />
+ (
+
+ Select a SSH Key
+
+
+
+
+
+
+ {sshKeys?.map((sshKey) => (
+
+ {sshKey.name}
+
+ ))}
+
+ Registries ({sshKeys?.length})
+
+
+
+
+
+
+ )}
+ />
+
+ (
+
+ IP Address
+
+
+
+
+
+
+ )}
+ />
+ (
+
+ Port
+
+ {
+ const value = e.target.value;
+ if (value === "") {
+ field.onChange(0);
+ } else {
+ const number = Number.parseInt(value, 10);
+ if (!Number.isNaN(number)) {
+ field.onChange(number);
+ }
+ }
+ }}
+ />
+
+
+
+
+ )}
+ />
+
+
+ (
+
+ Username
+
+
+
+
+
+
+ )}
+ />
+
+
+
+
+ Create
+
+
+
+
+
+ );
+};
diff --git a/apps/dokploy/components/dashboard/settings/servers/setup-server.tsx b/apps/dokploy/components/dashboard/settings/servers/setup-server.tsx
new file mode 100644
index 000000000..bf1a298be
--- /dev/null
+++ b/apps/dokploy/components/dashboard/settings/servers/setup-server.tsx
@@ -0,0 +1,301 @@
+import { AlertBlock } from "@/components/shared/alert-block";
+import { CodeEditor } from "@/components/shared/code-editor";
+import { DateTooltip } from "@/components/shared/date-tooltip";
+import { DialogAction } from "@/components/shared/dialog-action";
+import { StatusTooltip } from "@/components/shared/status-tooltip";
+import { Button } from "@/components/ui/button";
+import {
+ Card,
+ CardContent,
+ CardDescription,
+ CardHeader,
+ CardTitle,
+} from "@/components/ui/card";
+import {
+ Dialog,
+ DialogContent,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+} from "@/components/ui/dialog";
+import { DropdownMenuItem } from "@/components/ui/dropdown-menu";
+import { Separator } from "@/components/ui/separator";
+import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
+import { api } from "@/utils/api";
+import copy from "copy-to-clipboard";
+import {
+ CopyIcon,
+ ExternalLinkIcon,
+ RocketIcon,
+ ServerIcon,
+} from "lucide-react";
+import Link from "next/link";
+import { useState } from "react";
+import { toast } from "sonner";
+import { ShowDeployment } from "../../application/deployments/show-deployment";
+
+interface Props {
+ serverId: string;
+}
+
+export const SetupServer = ({ serverId }: Props) => {
+ const [isOpen, setIsOpen] = useState(false);
+ const { data: server } = api.server.one.useQuery(
+ {
+ serverId,
+ },
+ {
+ enabled: !!serverId,
+ },
+ );
+
+ const [activeLog, setActiveLog] = useState(null);
+ const { data: deployments, refetch } = api.deployment.allByServer.useQuery(
+ { serverId },
+ {
+ enabled: !!serverId,
+ },
+ );
+
+ const { mutateAsync, isLoading } = api.server.setup.useMutation();
+
+ return (
+
+
+ e.preventDefault()}
+ >
+ Setup Server
+
+
+
+
+
+
+ Setup Server
+
+
+ To setup a server, please click on the button below.
+
+
+
+ {!server?.sshKeyId ? (
+
+
+ Please add a SSH Key to your server before setting up the server.
+ you can assign a SSH Key to your server in Edit Server.
+
+
+ ) : (
+
+ )}
+
+
+ );
+};
diff --git a/apps/dokploy/components/dashboard/settings/servers/show-docker-containers-modal.tsx b/apps/dokploy/components/dashboard/settings/servers/show-docker-containers-modal.tsx
new file mode 100644
index 000000000..ecdfd2ca9
--- /dev/null
+++ b/apps/dokploy/components/dashboard/settings/servers/show-docker-containers-modal.tsx
@@ -0,0 +1,48 @@
+import {
+ Dialog,
+ DialogContent,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+} from "@/components/ui/dialog";
+import { DropdownMenuItem } from "@/components/ui/dropdown-menu";
+import { ContainerIcon } from "lucide-react";
+import { useState } from "react";
+import { ShowContainers } from "../../docker/show/show-containers";
+
+interface Props {
+ serverId: string;
+}
+
+export const ShowDockerContainersModal = ({ serverId }: Props) => {
+ const [isOpen, setIsOpen] = useState(false);
+
+ return (
+
+
+ e.preventDefault()}
+ >
+ Show Docker Containers
+
+
+
+
+
+
+ Docker Containers
+
+
+ See all the containers of your remote server
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/apps/dokploy/components/dashboard/settings/servers/show-servers.tsx b/apps/dokploy/components/dashboard/settings/servers/show-servers.tsx
new file mode 100644
index 000000000..74a941db0
--- /dev/null
+++ b/apps/dokploy/components/dashboard/settings/servers/show-servers.tsx
@@ -0,0 +1,216 @@
+import { AlertBlock } from "@/components/shared/alert-block";
+import { DialogAction } from "@/components/shared/dialog-action";
+import { Badge } from "@/components/ui/badge";
+import { Button } from "@/components/ui/button";
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuLabel,
+ DropdownMenuSeparator,
+ DropdownMenuTrigger,
+} from "@/components/ui/dropdown-menu";
+import {
+ Table,
+ TableBody,
+ TableCaption,
+ TableCell,
+ TableHead,
+ TableHeader,
+ TableRow,
+} from "@/components/ui/table";
+import { api } from "@/utils/api";
+import { format } from "date-fns";
+import { KeyIcon, MoreHorizontal, ServerIcon } from "lucide-react";
+import Link from "next/link";
+import { toast } from "sonner";
+import { TerminalModal } from "../web-server/terminal-modal";
+import { ShowServerActions } from "./actions/show-server-actions";
+import { AddServer } from "./add-server";
+import { SetupServer } from "./setup-server";
+import { ShowDockerContainersModal } from "./show-docker-containers-modal";
+import { ShowTraefikFileSystemModal } from "./show-traefik-file-system-modal";
+import { UpdateServer } from "./update-server";
+
+export const ShowServers = () => {
+ const { data, refetch } = api.server.all.useQuery();
+ const { mutateAsync } = api.server.remove.useMutation();
+ const { data: sshKeys } = api.sshKey.all.useQuery();
+
+ return (
+
+
+
+
Servers
+
+ Add servers to deploy your applications remotely.
+
+
+
+ {sshKeys && sshKeys?.length > 0 && (
+
+ )}
+
+
+
+ {sshKeys?.length === 0 && data?.length === 0 ? (
+
+
+
+ No SSH Keys found. Add a SSH Key to start adding servers.{" "}
+
+ Add SSH Key
+
+
+
+ ) : (
+ data &&
+ data.length === 0 && (
+
+
+
+ No Servers found. Add a server to deploy your applications
+ remotely.
+
+
+ )
+ )}
+ {data && data?.length > 0 && (
+
+
+ See all servers
+
+
+ Name
+ IP Address
+ Port
+ Username
+ SSH Key
+ Created
+ Actions
+
+
+
+ {data?.map((server) => {
+ const canDelete = server.totalSum === 0;
+ return (
+
+ {server.name}
+
+ {server.ipAddress}
+
+
+ {server.port}
+
+
+ {server.username}
+
+
+
+ {server.sshKeyId ? "Yes" : "No"}
+
+
+
+
+ {format(new Date(server.createdAt), "PPpp")}
+
+
+
+
+
+
+
+ Open menu
+
+
+
+
+ Actions
+ {server.sshKeyId && (
+
+ Enter the terminal
+
+ )}
+
+
+
+
+ {server.sshKeyId && (
+
+ )}
+
+ You can not delete this server because it
+ has active services.
+
+ You have active services associated with
+ this server, please delete them first.
+
+
+ )
+ }
+ onClick={async () => {
+ await mutateAsync({
+ serverId: server.serverId,
+ })
+ .then(() => {
+ refetch();
+ toast.success(
+ `Server ${server.name} deleted succesfully`,
+ );
+ })
+ .catch((err) => {
+ toast.error(err.message);
+ });
+ }}
+ >
+ e.preventDefault()}
+ >
+ Delete Server
+
+
+
+ {server.sshKeyId && (
+ <>
+
+ Extra
+
+
+
+ >
+ )}
+
+
+
+
+ );
+ })}
+
+
+
+ )}
+
+
+ );
+};
diff --git a/apps/dokploy/components/dashboard/settings/servers/show-traefik-file-system-modal.tsx b/apps/dokploy/components/dashboard/settings/servers/show-traefik-file-system-modal.tsx
new file mode 100644
index 000000000..cff9d2148
--- /dev/null
+++ b/apps/dokploy/components/dashboard/settings/servers/show-traefik-file-system-modal.tsx
@@ -0,0 +1,48 @@
+import {
+ Dialog,
+ DialogContent,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+} from "@/components/ui/dialog";
+import { DropdownMenuItem } from "@/components/ui/dropdown-menu";
+import { FileTextIcon } from "lucide-react";
+import { useState } from "react";
+import { ShowTraefikSystem } from "../../file-system/show-traefik-system";
+
+interface Props {
+ serverId: string;
+}
+
+export const ShowTraefikFileSystemModal = ({ serverId }: Props) => {
+ const [isOpen, setIsOpen] = useState(false);
+
+ return (
+
+
+ e.preventDefault()}
+ >
+ Show Traefik File System
+
+
+
+
+
+
+ Traefik File System
+
+
+ See all the files and directories of your traefik configuration
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/apps/dokploy/components/dashboard/settings/servers/update-server.tsx b/apps/dokploy/components/dashboard/settings/servers/update-server.tsx
new file mode 100644
index 000000000..16eb5ba7d
--- /dev/null
+++ b/apps/dokploy/components/dashboard/settings/servers/update-server.tsx
@@ -0,0 +1,283 @@
+import { AlertBlock } from "@/components/shared/alert-block";
+import { Button } from "@/components/ui/button";
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+} from "@/components/ui/dialog";
+import { DropdownMenuItem } from "@/components/ui/dropdown-menu";
+import {
+ Form,
+ FormControl,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+} from "@/components/ui/form";
+import { Input } from "@/components/ui/input";
+import {
+ Select,
+ SelectContent,
+ SelectGroup,
+ SelectItem,
+ SelectLabel,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui/select";
+import { Textarea } from "@/components/ui/textarea";
+import { api } from "@/utils/api";
+import { zodResolver } from "@hookform/resolvers/zod";
+import { PlusIcon } from "lucide-react";
+import { useRouter } from "next/router";
+import { useEffect, useState } from "react";
+import { useForm } from "react-hook-form";
+import { toast } from "sonner";
+import { z } from "zod";
+
+const Schema = z.object({
+ name: z.string().min(1, {
+ message: "Name is required",
+ }),
+ description: z.string().optional(),
+ ipAddress: z.string().min(1, {
+ message: "IP Address is required",
+ }),
+ port: z.number().optional(),
+ username: z.string().optional(),
+ sshKeyId: z.string().min(1, {
+ message: "SSH Key is required",
+ }),
+});
+
+type Schema = z.infer;
+
+interface Props {
+ serverId: string;
+}
+
+export const UpdateServer = ({ serverId }: Props) => {
+ const utils = api.useUtils();
+ const [isOpen, setIsOpen] = useState(false);
+ const { data, isLoading } = api.server.one.useQuery(
+ {
+ serverId,
+ },
+ {
+ enabled: !!serverId,
+ },
+ );
+ const { data: sshKeys } = api.sshKey.all.useQuery();
+ const { mutateAsync, error, isError } = api.server.update.useMutation();
+ const form = useForm({
+ defaultValues: {
+ description: "",
+ name: "",
+ ipAddress: "",
+ port: 22,
+ username: "root",
+ sshKeyId: "",
+ },
+ resolver: zodResolver(Schema),
+ });
+
+ useEffect(() => {
+ form.reset({
+ description: data?.description || "",
+ name: data?.name || "",
+ ipAddress: data?.ipAddress || "",
+ port: data?.port || 22,
+ username: data?.username || "root",
+ sshKeyId: data?.sshKeyId || "",
+ });
+ }, [form, form.reset, form.formState.isSubmitSuccessful, data]);
+
+ const onSubmit = async (formData: Schema) => {
+ await mutateAsync({
+ name: formData.name,
+ description: formData.description || "",
+ ipAddress: formData.ipAddress || "",
+ port: formData.port || 22,
+ username: formData.username || "root",
+ sshKeyId: formData.sshKeyId || "",
+ serverId: serverId,
+ })
+ .then(async (data) => {
+ await utils.server.all.invalidate();
+ toast.success("Server Updated");
+ setIsOpen(false);
+ })
+ .catch(() => {
+ toast.error("Error to update a server");
+ });
+ };
+
+ return (
+
+
+ e.preventDefault()}
+ >
+ Edit Server
+
+
+
+
+ Update Server
+
+ Update a server to deploy your applications remotely.
+
+
+ {isError && {error?.message} }
+
+
+
+ (
+
+ Name
+
+
+
+
+
+
+ )}
+ />
+
+ (
+
+ Description
+
+
+
+
+
+
+ )}
+ />
+ (
+
+ Select a SSH Key
+
+
+
+
+
+
+ {sshKeys?.map((sshKey) => (
+
+ {sshKey.name}
+
+ ))}
+
+ Registries ({sshKeys?.length})
+
+
+
+
+
+
+ )}
+ />
+
+ (
+
+ IP Address
+
+
+
+
+
+
+ )}
+ />
+ (
+
+ Port
+
+ {
+ const value = e.target.value;
+ if (value === "") {
+ field.onChange(0);
+ } else {
+ const number = Number.parseInt(value, 10);
+ if (!Number.isNaN(number)) {
+ field.onChange(number);
+ }
+ }
+ }}
+ />
+
+
+
+
+ )}
+ />
+
+
+ (
+
+ Username
+
+
+
+
+
+
+ )}
+ />
+
+
+
+
+ Update
+
+
+
+
+
+ );
+};
diff --git a/apps/dokploy/components/dashboard/settings/users/show-users.tsx b/apps/dokploy/components/dashboard/settings/users/show-users.tsx
index cad28487c..7795b42d4 100644
--- a/apps/dokploy/components/dashboard/settings/users/show-users.tsx
+++ b/apps/dokploy/components/dashboard/settings/users/show-users.tsx
@@ -41,8 +41,8 @@ export const ShowUsers = () => {
}, []);
return (
-
-
+
+
Users
@@ -55,9 +55,9 @@ export const ShowUsers = () => {
)}
-
+
{data?.length === 0 ? (
-
+
To create a user, you need to add:
diff --git a/apps/dokploy/components/dashboard/settings/web-server.tsx b/apps/dokploy/components/dashboard/settings/web-server.tsx
index b1d99388f..188b3db8a 100644
--- a/apps/dokploy/components/dashboard/settings/web-server.tsx
+++ b/apps/dokploy/components/dashboard/settings/web-server.tsx
@@ -1,4 +1,3 @@
-import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
@@ -6,334 +5,34 @@ import {
CardHeader,
CardTitle,
} from "@/components/ui/card";
-import { Label } from "@/components/ui/label";
-import { Switch } from "@/components/ui/switch";
-import React from "react";
-
-import {
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuGroup,
- DropdownMenuItem,
- DropdownMenuLabel,
- DropdownMenuSeparator,
- DropdownMenuTrigger,
-} from "@/components/ui/dropdown-menu";
+import { cn } from "@/lib/utils";
import { api } from "@/utils/api";
-import { toast } from "sonner";
-import { DockerTerminalModal } from "./web-server/docker-terminal-modal";
-import { EditTraefikEnv } from "./web-server/edit-traefik-env";
-import { ShowMainTraefikConfig } from "./web-server/show-main-traefik-config";
-import { ShowModalLogs } from "./web-server/show-modal-logs";
-import { ShowServerMiddlewareConfig } from "./web-server/show-server-middleware-config";
-import { ShowServerTraefikConfig } from "./web-server/show-server-traefik-config";
-import { TerminalModal } from "./web-server/terminal-modal";
+import React from "react";
+import { ShowDokployActions } from "./servers/actions/show-dokploy-actions";
+import { ShowStorageActions } from "./servers/actions/show-storage-actions";
+import { ShowTraefikActions } from "./servers/actions/show-traefik-actions";
+import { ToggleDockerCleanup } from "./servers/actions/toggle-docker-cleanup";
import { UpdateServer } from "./web-server/update-server";
-export const WebServer = () => {
- const { data, refetch } = api.admin.one.useQuery();
- const { mutateAsync: reloadServer, isLoading } =
- api.settings.reloadServer.useMutation();
- const { mutateAsync: reloadTraefik, isLoading: reloadTraefikIsLoading } =
- api.settings.reloadTraefik.useMutation();
- const { mutateAsync: cleanAll, isLoading: cleanAllIsLoading } =
- api.settings.cleanAll.useMutation();
- const { mutateAsync: toggleDashboard, isLoading: toggleDashboardIsLoading } =
- api.settings.toggleDashboard.useMutation();
-
- const {
- mutateAsync: cleanDockerBuilder,
- isLoading: cleanDockerBuilderIsLoading,
- } = api.settings.cleanDockerBuilder.useMutation();
-
- const { mutateAsync: cleanMonitoring, isLoading: cleanMonitoringIsLoading } =
- api.settings.cleanMonitoring.useMutation();
- const {
- mutateAsync: cleanUnusedImages,
- isLoading: cleanUnusedImagesIsLoading,
- } = api.settings.cleanUnusedImages.useMutation();
-
- const {
- mutateAsync: cleanUnusedVolumes,
- isLoading: cleanUnusedVolumesIsLoading,
- } = api.settings.cleanUnusedVolumes.useMutation();
-
- const {
- mutateAsync: cleanStoppedContainers,
- isLoading: cleanStoppedContainersIsLoading,
- } = api.settings.cleanStoppedContainers.useMutation();
+interface Props {
+ className?: string;
+}
+export const WebServer = ({ className }: Props) => {
+ const { data } = api.admin.one.useQuery();
const { data: dokployVersion } = api.settings.getDokployVersion.useQuery();
- const { mutateAsync: updateDockerCleanup } =
- api.settings.updateDockerCleanup.useMutation();
-
- const { data: haveTraefikDashboardPortEnabled, refetch: refetchDashboard } =
- api.settings.haveTraefikDashboardPortEnabled.useQuery();
-
return (
-
+
Web server settings
Reload or clean the web server.
-
+
-
-
-
- Server
-
-
-
- Actions
-
-
- {
- await reloadServer()
- .then(async () => {
- toast.success("Server Reloaded");
- })
- .catch(() => {
- toast.success("Server Reloaded");
- });
- }}
- >
- Reload
-
-
- Watch logs
-
-
-
- e.preventDefault()}
- className="w-full cursor-pointer space-x-3"
- >
- View Traefik config
-
-
-
-
- e.preventDefault()}
- className="w-full cursor-pointer space-x-3"
- >
- View middlewares config
-
-
-
-
- Enter the terminal
-
-
-
-
-
-
-
-
- Traefik
-
-
-
- Actions
-
-
- {
- await reloadTraefik()
- .then(async () => {
- toast.success("Traefik Reloaded");
- })
- .catch(() => {
- toast.error("Error to reload the traefik");
- });
- }}
- >
- Reload
-
-
- Watch logs
-
-
- e.preventDefault()}
- className="w-full cursor-pointer space-x-3"
- >
- View Traefik config
-
-
-
- e.preventDefault()}
- className="w-full cursor-pointer space-x-3"
- >
- Modify Env
-
-
-
- {
- await toggleDashboard({
- enableDashboard: !haveTraefikDashboardPortEnabled,
- })
- .then(async () => {
- toast.success(
- `${haveTraefikDashboardPortEnabled ? "Disabled" : "Enabled"} Dashboard`,
- );
- refetchDashboard();
- })
- .catch(() => {
- toast.error(
- `${haveTraefikDashboardPortEnabled ? "Disabled" : "Enabled"} Dashboard`,
- );
- });
- }}
- className="w-full cursor-pointer space-x-3"
- >
-
- {haveTraefikDashboardPortEnabled ? "Disable" : "Enable"}{" "}
- Dashboard
-
-
-
-
- e.preventDefault()}
- >
- Enter the terminal
-
-
-
-
-
-
-
-
-
- Space
-
-
-
- Actions
-
-
- {
- await cleanUnusedImages()
- .then(async () => {
- toast.success("Cleaned images");
- })
- .catch(() => {
- toast.error("Error to clean images");
- });
- }}
- >
- Clean unused images
-
- {
- await cleanUnusedVolumes()
- .then(async () => {
- toast.success("Cleaned volumes");
- })
- .catch(() => {
- toast.error("Error to clean volumes");
- });
- }}
- >
- Clean unused volumes
-
-
- {
- await cleanStoppedContainers()
- .then(async () => {
- toast.success("Stopped containers cleaned");
- })
- .catch(() => {
- toast.error("Error to clean stopped containers");
- });
- }}
- >
- Clean stopped containers
-
-
- {
- await cleanDockerBuilder()
- .then(async () => {
- toast.success("Cleaned Docker Builder");
- })
- .catch(() => {
- toast.error("Error to clean Docker Builder");
- });
- }}
- >
- Clean Docker Builder & System
-
- {
- await cleanMonitoring()
- .then(async () => {
- toast.success("Cleaned Monitoring");
- })
- .catch(() => {
- toast.error("Error to clean Monitoring");
- });
- }}
- >
- Clean Monitoring
-
- {
- await cleanAll()
- .then(async () => {
- toast.success("Cleaned all");
- })
- .catch(() => {
- toast.error("Error to clean all");
- });
- }}
- >
- Clean all
-
-
-
-
+
+
+
@@ -345,25 +44,8 @@ export const WebServer = () => {
Version: {dokployVersion}
-
- {
- await updateDockerCleanup({
- enableDockerCleanup: e,
- })
- .then(async () => {
- toast.success("Docker Cleanup Enabled");
- })
- .catch(() => {
- toast.error("Docker Cleanup Error");
- });
- refetch();
- }}
- />
- Daily Docker Cleanup
-
+
diff --git a/apps/dokploy/components/dashboard/settings/web-server/docker-terminal-modal.tsx b/apps/dokploy/components/dashboard/settings/web-server/docker-terminal-modal.tsx
index 2fc217284..f38ecf128 100644
--- a/apps/dokploy/components/dashboard/settings/web-server/docker-terminal-modal.tsx
+++ b/apps/dokploy/components/dashboard/settings/web-server/docker-terminal-modal.tsx
@@ -17,6 +17,7 @@ import {
SelectValue,
} from "@/components/ui/select";
import { api } from "@/utils/api";
+import { Loader2 } from "lucide-react";
import dynamic from "next/dynamic";
import type React from "react";
import { useEffect, useState } from "react";
@@ -34,12 +35,14 @@ const Terminal = dynamic(
interface Props {
appName: string;
children?: React.ReactNode;
+ serverId?: string;
}
-export const DockerTerminalModal = ({ children, appName }: Props) => {
- const { data } = api.docker.getContainersByAppNameMatch.useQuery(
+export const DockerTerminalModal = ({ children, appName, serverId }: Props) => {
+ const { data, isLoading } = api.docker.getContainersByAppNameMatch.useQuery(
{
appName,
+ serverId,
},
{
enabled: !!appName,
@@ -65,7 +68,14 @@ export const DockerTerminalModal = ({ children, appName }: Props) => {
Select a container to view logs
-
+ {isLoading ? (
+
+ Loading...
+
+
+ ) : (
+
+ )}
@@ -82,6 +92,7 @@ export const DockerTerminalModal = ({ children, appName }: Props) => {
diff --git a/apps/dokploy/components/dashboard/settings/web-server/edit-traefik-env.tsx b/apps/dokploy/components/dashboard/settings/web-server/edit-traefik-env.tsx
index 2e6eede60..645eda903 100644
--- a/apps/dokploy/components/dashboard/settings/web-server/edit-traefik-env.tsx
+++ b/apps/dokploy/components/dashboard/settings/web-server/edit-traefik-env.tsx
@@ -33,12 +33,15 @@ type Schema = z.infer
;
interface Props {
children?: React.ReactNode;
+ serverId?: string;
}
-export const EditTraefikEnv = ({ children }: Props) => {
+export const EditTraefikEnv = ({ children, serverId }: Props) => {
const [canEdit, setCanEdit] = useState(true);
- const { data } = api.settings.readTraefikEnv.useQuery();
+ const { data } = api.settings.readTraefikEnv.useQuery({
+ serverId,
+ });
const { mutateAsync, isLoading, error, isError } =
api.settings.writeTraefikEnv.useMutation();
@@ -62,6 +65,7 @@ export const EditTraefikEnv = ({ children }: Props) => {
const onSubmit = async (data: Schema) => {
await mutateAsync({
env: data.env,
+ serverId,
})
.then(async () => {
toast.success("Traefik Env Updated");
diff --git a/apps/dokploy/components/dashboard/settings/web-server/show-main-traefik-config.tsx b/apps/dokploy/components/dashboard/settings/web-server/show-main-traefik-config.tsx
deleted file mode 100644
index becda3ade..000000000
--- a/apps/dokploy/components/dashboard/settings/web-server/show-main-traefik-config.tsx
+++ /dev/null
@@ -1,165 +0,0 @@
-import { AlertBlock } from "@/components/shared/alert-block";
-import { CodeEditor } from "@/components/shared/code-editor";
-import { Button } from "@/components/ui/button";
-import {
- Dialog,
- DialogContent,
- DialogDescription,
- DialogFooter,
- DialogHeader,
- DialogTitle,
- DialogTrigger,
-} from "@/components/ui/dialog";
-import {
- Form,
- FormControl,
- FormField,
- FormItem,
- FormLabel,
- FormMessage,
-} from "@/components/ui/form";
-import { api } from "@/utils/api";
-import { zodResolver } from "@hookform/resolvers/zod";
-import { useEffect, useState } from "react";
-import { useForm } from "react-hook-form";
-import { toast } from "sonner";
-import { z } from "zod";
-import { validateAndFormatYAML } from "../../application/advanced/traefik/update-traefik-config";
-
-const UpdateMainTraefikConfigSchema = z.object({
- traefikConfig: z.string(),
-});
-
-type UpdateTraefikConfig = z.infer;
-
-interface Props {
- children?: React.ReactNode;
-}
-
-export const ShowMainTraefikConfig = ({ children }: Props) => {
- const { data, refetch } = api.settings.readTraefikConfig.useQuery();
- const [canEdit, setCanEdit] = useState(true);
-
- const { mutateAsync, isLoading, error, isError } =
- api.settings.updateTraefikConfig.useMutation();
-
- const form = useForm({
- defaultValues: {
- traefikConfig: "",
- },
- disabled: canEdit,
- resolver: zodResolver(UpdateMainTraefikConfigSchema),
- });
-
- useEffect(() => {
- if (data) {
- form.reset({
- traefikConfig: data || "",
- });
- }
- }, [form, form.reset, data]);
-
- const onSubmit = async (data: UpdateTraefikConfig) => {
- const { valid, error } = validateAndFormatYAML(data.traefikConfig);
- if (!valid) {
- form.setError("traefikConfig", {
- type: "manual",
- message: error || "Invalid YAML",
- });
- return;
- }
- form.clearErrors("traefikConfig");
- await mutateAsync({
- traefikConfig: data.traefikConfig,
- })
- .then(async () => {
- toast.success("Traefik config Updated");
- refetch();
- })
- .catch(() => {
- toast.error("Error to update the traefik config");
- });
- };
-
- return (
-
- {children}
-
-
- Update traefik config
- Update the traefik config
-
- {isError && {error?.message} }
-
-
-
-
-
(
-
- Traefik config
-
-
-
-
-
-
-
-
- {
- setCanEdit(!canEdit);
- }}
- >
- {canEdit ? "Unlock" : "Lock"}
-
-
-
- )}
- />
-
-
-
-
-
- Update
-
-
-
-
-
- );
-};
diff --git a/apps/dokploy/components/dashboard/settings/web-server/show-modal-logs.tsx b/apps/dokploy/components/dashboard/settings/web-server/show-modal-logs.tsx
index 2a9cc0fa3..607ff7b2f 100644
--- a/apps/dokploy/components/dashboard/settings/web-server/show-modal-logs.tsx
+++ b/apps/dokploy/components/dashboard/settings/web-server/show-modal-logs.tsx
@@ -18,6 +18,7 @@ import {
SelectValue,
} from "@/components/ui/select";
import { api } from "@/utils/api";
+import { Loader2 } from "lucide-react";
import dynamic from "next/dynamic";
import type React from "react";
import { useEffect, useState } from "react";
@@ -35,12 +36,14 @@ export const DockerLogsId = dynamic(
interface Props {
appName: string;
children?: React.ReactNode;
+ serverId?: string;
}
-export const ShowModalLogs = ({ appName, children }: Props) => {
- const { data } = api.docker.getContainersByAppLabel.useQuery(
+export const ShowModalLogs = ({ appName, children, serverId }: Props) => {
+ const { data, isLoading } = api.docker.getContainersByAppLabel.useQuery(
{
appName,
+ serverId,
},
{
enabled: !!appName,
@@ -72,7 +75,14 @@ export const ShowModalLogs = ({ appName, children }: Props) => {
Select a container to view logs
-
+ {isLoading ? (
+
+ Loading...
+
+
+ ) : (
+
+ )}
@@ -88,7 +98,11 @@ export const ShowModalLogs = ({ appName, children }: Props) => {
-
+
diff --git a/apps/dokploy/components/dashboard/settings/web-server/show-server-middleware-config.tsx b/apps/dokploy/components/dashboard/settings/web-server/show-server-middleware-config.tsx
deleted file mode 100644
index d65291e46..000000000
--- a/apps/dokploy/components/dashboard/settings/web-server/show-server-middleware-config.tsx
+++ /dev/null
@@ -1,162 +0,0 @@
-import { AlertBlock } from "@/components/shared/alert-block";
-import { CodeEditor } from "@/components/shared/code-editor";
-import { Button } from "@/components/ui/button";
-import {
- Dialog,
- DialogContent,
- DialogDescription,
- DialogFooter,
- DialogHeader,
- DialogTitle,
- DialogTrigger,
-} from "@/components/ui/dialog";
-import {
- Form,
- FormControl,
- FormField,
- FormItem,
- FormLabel,
- FormMessage,
-} from "@/components/ui/form";
-import { api } from "@/utils/api";
-import { zodResolver } from "@hookform/resolvers/zod";
-import { useEffect, useState } from "react";
-import { useForm } from "react-hook-form";
-import { toast } from "sonner";
-import { z } from "zod";
-import { validateAndFormatYAML } from "../../application/advanced/traefik/update-traefik-config";
-
-const UpdateServerMiddlewareConfigSchema = z.object({
- traefikConfig: z.string(),
-});
-
-type UpdateServerMiddlewareConfig = z.infer<
- typeof UpdateServerMiddlewareConfigSchema
->;
-
-interface Props {
- children?: React.ReactNode;
-}
-
-export const ShowServerMiddlewareConfig = ({ children }: Props) => {
- const { data, refetch } = api.settings.readMiddlewareTraefikConfig.useQuery();
- const [canEdit, setCanEdit] = useState(true);
-
- const { mutateAsync, isLoading, error, isError } =
- api.settings.updateMiddlewareTraefikConfig.useMutation();
-
- const form = useForm({
- defaultValues: {
- traefikConfig: "",
- },
- disabled: canEdit,
- resolver: zodResolver(UpdateServerMiddlewareConfigSchema),
- });
-
- useEffect(() => {
- if (data) {
- form.reset({
- traefikConfig: data || "",
- });
- }
- }, [form, form.reset, data]);
-
- const onSubmit = async (data: UpdateServerMiddlewareConfig) => {
- const { valid, error } = validateAndFormatYAML(data.traefikConfig);
- console.log(error);
- if (!valid) {
- form.setError("traefikConfig", {
- type: "manual",
- message: error || "Invalid YAML",
- });
- return;
- }
- form.clearErrors("traefikConfig");
- await mutateAsync({
- traefikConfig: data.traefikConfig,
- })
- .then(async () => {
- toast.success("Middleware config Updated");
- refetch();
- })
- .catch(() => {
- toast.error("Error to update the middleware traefik config");
- });
- };
-
- return (
-
- {children}
-
-
- Update Middleware config
- Update the middleware config
-
- {isError && {error?.message} }
-
-
-
-
-
(
-
- Traefik config
-
-
-
-
-
-
-
-
- {
- setCanEdit(!canEdit);
- }}
- >
- {canEdit ? "Unlock" : "Lock"}
-
-
-
- )}
- />
-
-
-
-
-
- Update
-
-
-
-
-
- );
-};
diff --git a/apps/dokploy/components/dashboard/settings/web-server/show-server-traefik-config.tsx b/apps/dokploy/components/dashboard/settings/web-server/show-server-traefik-config.tsx
deleted file mode 100644
index ea1608697..000000000
--- a/apps/dokploy/components/dashboard/settings/web-server/show-server-traefik-config.tsx
+++ /dev/null
@@ -1,163 +0,0 @@
-import { AlertBlock } from "@/components/shared/alert-block";
-import { CodeEditor } from "@/components/shared/code-editor";
-import { Button } from "@/components/ui/button";
-import {
- Dialog,
- DialogContent,
- DialogDescription,
- DialogFooter,
- DialogHeader,
- DialogTitle,
- DialogTrigger,
-} from "@/components/ui/dialog";
-import {
- Form,
- FormControl,
- FormField,
- FormItem,
- FormLabel,
- FormMessage,
-} from "@/components/ui/form";
-import { api } from "@/utils/api";
-import { zodResolver } from "@hookform/resolvers/zod";
-import { useEffect, useState } from "react";
-import { useForm } from "react-hook-form";
-import { toast } from "sonner";
-import { z } from "zod";
-import { validateAndFormatYAML } from "../../application/advanced/traefik/update-traefik-config";
-
-const UpdateServerTraefikConfigSchema = z.object({
- traefikConfig: z.string(),
-});
-
-type UpdateServerTraefikConfig = z.infer<
- typeof UpdateServerTraefikConfigSchema
->;
-
-interface Props {
- children?: React.ReactNode;
-}
-
-export const ShowServerTraefikConfig = ({ children }: Props) => {
- const { data, refetch } = api.settings.readWebServerTraefikConfig.useQuery();
- const [canEdit, setCanEdit] = useState(true);
-
- const { mutateAsync, isLoading, error, isError } =
- api.settings.updateWebServerTraefikConfig.useMutation();
-
- const form = useForm({
- defaultValues: {
- traefikConfig: "",
- },
- disabled: canEdit,
- resolver: zodResolver(UpdateServerTraefikConfigSchema),
- });
-
- useEffect(() => {
- if (data) {
- form.reset({
- traefikConfig: data || "",
- });
- }
- }, [form, form.reset, data]);
-
- const onSubmit = async (data: UpdateServerTraefikConfig) => {
- const { valid, error } = validateAndFormatYAML(data.traefikConfig);
- console.log(error);
- if (!valid) {
- form.setError("traefikConfig", {
- type: "manual",
- message: error || "Invalid YAML",
- });
- return;
- }
- form.clearErrors("traefikConfig");
- await mutateAsync({
- traefikConfig: data.traefikConfig,
- })
- .then(async () => {
- toast.success("Traefik config Updated");
- refetch();
- })
- .catch(() => {
- toast.error("Error to update the traefik config");
- });
- };
-
- return (
-
- {children}
-
-
- Update traefik config
- Update the traefik config
-
- {isError && {error?.message} }
-
-
-
-
-
(
-
- Traefik config
-
-
-
-
-
-
-
-
- {
- setCanEdit(!canEdit);
- }}
- >
- {canEdit ? "Unlock" : "Lock"}
-
-
-
- )}
- />
-
-
-
-
-
- Update
-
-
-
-
-
- );
-};
diff --git a/apps/dokploy/components/dashboard/settings/web-server/terminal-modal.tsx b/apps/dokploy/components/dashboard/settings/web-server/terminal-modal.tsx
index d9485ee53..e1ccc0be6 100644
--- a/apps/dokploy/components/dashboard/settings/web-server/terminal-modal.tsx
+++ b/apps/dokploy/components/dashboard/settings/web-server/terminal-modal.tsx
@@ -1,4 +1,3 @@
-import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
@@ -8,79 +7,27 @@ import {
DialogTrigger,
} from "@/components/ui/dialog";
import { DropdownMenuItem } from "@/components/ui/dropdown-menu";
-import {
- Form,
- FormControl,
- FormDescription,
- FormField,
- FormItem,
- FormLabel,
- FormMessage,
-} from "@/components/ui/form";
-import { Input } from "@/components/ui/input";
-import { Label } from "@/components/ui/label";
-import { Textarea } from "@/components/ui/textarea";
import { api } from "@/utils/api";
-import { zodResolver } from "@hookform/resolvers/zod";
import dynamic from "next/dynamic";
import type React from "react";
-import { useEffect, useState } from "react";
-import { useForm } from "react-hook-form";
-import { toast } from "sonner";
-import { z } from "zod";
-import { RemoveSSHPrivateKey } from "./remove-ssh-private-key";
const Terminal = dynamic(() => import("./terminal").then((e) => e.Terminal), {
ssr: false,
});
-const addSSHPrivateKey = z.object({
- sshPrivateKey: z
- .string({
- required_error: "SSH private key is required",
- })
- .min(1, "SSH private key is required"),
-});
-
-type AddSSHPrivateKey = z.infer;
-
interface Props {
children?: React.ReactNode;
+ serverId: string;
}
-export const TerminalModal = ({ children }: Props) => {
- const { data, refetch } = api.admin.one.useQuery();
- const [user, setUser] = useState("root");
- const [terminalUser, setTerminalUser] = useState("root");
-
- const { mutateAsync, isLoading } =
- api.settings.saveSSHPrivateKey.useMutation();
-
- const form = useForm({
- defaultValues: {
- sshPrivateKey: "",
+export const TerminalModal = ({ children, serverId }: Props) => {
+ const { data } = api.server.one.useQuery(
+ {
+ serverId,
},
- resolver: zodResolver(addSSHPrivateKey),
- });
+ { enabled: !!serverId },
+ );
- useEffect(() => {
- if (data) {
- form.reset({});
- }
- }, [data, form, form.reset]);
-
- const onSubmit = async (formData: AddSSHPrivateKey) => {
- await mutateAsync({
- sshPrivateKey: formData.sshPrivateKey,
- })
- .then(async () => {
- toast.success("SSH Key Updated");
- await refetch();
- })
- .catch(() => {
- toast.error("Error to Update the ssh key");
- });
- };
return (
@@ -92,75 +39,14 @@ export const TerminalModal = ({ children }: Props) => {
-
-
- Terminal
- Easy way to access the server
-
- {data?.haveSSH && (
-
-
-
- )}
+
+ Terminal ({data?.name})
+ Easy way to access the server
- {!data?.haveSSH ? (
-
-
-
-
-
- {
- return (
-
- SSH Private Key
-
- In order to access the server you need to add an
- ssh private key
-
-
-
-
-
-
- );
- }}
- />
-
-
-
- Save
-
-
-
-
-
-
- ) : (
-
-
-
Log in as
-
- setUser(e.target.value)} />
- setTerminalUser(user)}>Login
-
-
-
-
- )}
+
+
+
);
diff --git a/apps/dokploy/components/dashboard/settings/web-server/terminal.tsx b/apps/dokploy/components/dashboard/settings/web-server/terminal.tsx
index c225be9ce..2fe7f83cf 100644
--- a/apps/dokploy/components/dashboard/settings/web-server/terminal.tsx
+++ b/apps/dokploy/components/dashboard/settings/web-server/terminal.tsx
@@ -7,10 +7,10 @@ import { AttachAddon } from "@xterm/addon-attach";
interface Props {
id: string;
- userSSH?: string;
+ serverId: string;
}
-export const Terminal: React.FC = ({ id, userSSH = "root" }) => {
+export const Terminal: React.FC = ({ id, serverId }) => {
const termRef = useRef(null);
useEffect(() => {
@@ -33,7 +33,7 @@ export const Terminal: React.FC = ({ id, userSSH = "root" }) => {
const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
- const wsUrl = `${protocol}//${window.location.host}/terminal?userSSH=${userSSH}`;
+ const wsUrl = `${protocol}//${window.location.host}/terminal?serverId=${serverId}`;
const ws = new WebSocket(wsUrl);
const addonAttach = new AttachAddon(ws);
@@ -46,7 +46,7 @@ export const Terminal: React.FC = ({ id, userSSH = "root" }) => {
return () => {
ws.readyState === WebSocket.OPEN && ws.close();
};
- }, [id, userSSH]);
+ }, [id, serverId]);
return (
diff --git a/apps/dokploy/components/layouts/service-layout.tsx b/apps/dokploy/components/layouts/service-layout.tsx
deleted file mode 100644
index e69de29bb..000000000
diff --git a/apps/dokploy/components/layouts/settings-layout.tsx b/apps/dokploy/components/layouts/settings-layout.tsx
index cfe600f4e..3dadbe97d 100644
--- a/apps/dokploy/components/layouts/settings-layout.tsx
+++ b/apps/dokploy/components/layouts/settings-layout.tsx
@@ -74,7 +74,7 @@ export const SettingsLayout = ({ children }: Props) => {
{
title: "Cluster",
label: "",
- icon: Server,
+ icon: BoxesIcon,
href: "/dashboard/settings/cluster",
},
{
@@ -83,6 +83,12 @@ export const SettingsLayout = ({ children }: Props) => {
icon: Bell,
href: "/dashboard/settings/notifications",
},
+ {
+ title: "Servers",
+ label: "",
+ icon: Server,
+ href: "/dashboard/settings/servers",
+ },
]
: []),
...(user?.canAccessToSSHKeys
@@ -117,6 +123,7 @@ export const SettingsLayout = ({ children }: Props) => {
import {
Activity,
Bell,
+ BoxesIcon,
Database,
GitBranch,
KeyIcon,
diff --git a/apps/dokploy/components/shared/dialog-action.tsx b/apps/dokploy/components/shared/dialog-action.tsx
index 8b6f28472..d3c596936 100644
--- a/apps/dokploy/components/shared/dialog-action.tsx
+++ b/apps/dokploy/components/shared/dialog-action.tsx
@@ -11,10 +11,11 @@ import {
} from "@/components/ui/alert-dialog";
interface Props {
- title?: string;
- description?: string;
+ title?: string | React.ReactNode;
+ description?: string | React.ReactNode;
onClick: () => void;
children?: React.ReactNode;
+ disabled?: boolean;
}
export const DialogAction = ({
@@ -22,6 +23,7 @@ export const DialogAction = ({
children,
description,
title,
+ disabled,
}: Props) => {
return (
@@ -37,7 +39,9 @@ export const DialogAction = ({
Cancel
- Confirm
+
+ Confirm
+
diff --git a/apps/dokploy/components/support/show-support.tsx b/apps/dokploy/components/support/show-support.tsx
deleted file mode 100644
index b43a14446..000000000
--- a/apps/dokploy/components/support/show-support.tsx
+++ /dev/null
@@ -1,35 +0,0 @@
-import { Button } from "@/components/ui/button";
-import {
- Dialog,
- DialogContent,
- DialogDescription,
- DialogFooter,
- DialogHeader,
- DialogTitle,
- DialogTrigger,
-} from "@/components/ui/dialog";
-import { HeartIcon } from "lucide-react";
-
-export const ShowSupport = () => {
- return (
-
-
-
- Support
-
-
-
-
-
- Dokploy Support
- Consider supporting Dokploy
-
-
-
-
- );
-};
diff --git a/apps/dokploy/components/ui/input.tsx b/apps/dokploy/components/ui/input.tsx
index 55b46e6de..8fe7ab282 100644
--- a/apps/dokploy/components/ui/input.tsx
+++ b/apps/dokploy/components/ui/input.tsx
@@ -31,4 +31,39 @@ const Input = React.forwardRef
(
);
Input.displayName = "Input";
-export { Input };
+const NumberInput = React.forwardRef(
+ ({ className, errorMessage, ...props }, ref) => {
+ return (
+ {
+ const value = e.target.value;
+ if (value === "") {
+ props.onChange?.(e);
+ } else {
+ const number = Number.parseInt(value, 10);
+ if (!Number.isNaN(number)) {
+ const syntheticEvent = {
+ ...e,
+ target: {
+ ...e.target,
+ value: number,
+ },
+ };
+ props.onChange?.(
+ syntheticEvent as unknown as React.ChangeEvent,
+ );
+ }
+ }
+ }}
+ />
+ );
+ },
+);
+NumberInput.displayName = "NumberInput";
+
+export { Input, NumberInput };
diff --git a/apps/dokploy/drizzle/0037_legal_namor.sql b/apps/dokploy/drizzle/0037_legal_namor.sql
new file mode 100644
index 000000000..02cbead38
--- /dev/null
+++ b/apps/dokploy/drizzle/0037_legal_namor.sql
@@ -0,0 +1,81 @@
+CREATE TABLE IF NOT EXISTS "server" (
+ "serverId" text PRIMARY KEY NOT NULL,
+ "name" text NOT NULL,
+ "description" text,
+ "ipAddress" text NOT NULL,
+ "port" integer NOT NULL,
+ "username" text DEFAULT 'root' NOT NULL,
+ "appName" text NOT NULL,
+ "enableDockerCleanup" boolean DEFAULT false NOT NULL,
+ "createdAt" text NOT NULL,
+ "adminId" text NOT NULL,
+ "sshKeyId" text
+);
+--> statement-breakpoint
+ALTER TABLE "application" ADD COLUMN "serverId" text;--> statement-breakpoint
+ALTER TABLE "postgres" ADD COLUMN "serverId" text;--> statement-breakpoint
+ALTER TABLE "mariadb" ADD COLUMN "serverId" text;--> statement-breakpoint
+ALTER TABLE "mongo" ADD COLUMN "serverId" text;--> statement-breakpoint
+ALTER TABLE "mysql" ADD COLUMN "serverId" text;--> statement-breakpoint
+ALTER TABLE "deployment" ADD COLUMN "serverId" text;--> statement-breakpoint
+ALTER TABLE "redis" ADD COLUMN "serverId" text;--> statement-breakpoint
+ALTER TABLE "compose" ADD COLUMN "serverId" text;--> statement-breakpoint
+DO $$ BEGIN
+ ALTER TABLE "server" ADD CONSTRAINT "server_adminId_admin_adminId_fk" FOREIGN KEY ("adminId") REFERENCES "public"."admin"("adminId") ON DELETE cascade ON UPDATE no action;
+EXCEPTION
+ WHEN duplicate_object THEN null;
+END $$;
+--> statement-breakpoint
+DO $$ BEGIN
+ ALTER TABLE "server" ADD CONSTRAINT "server_sshKeyId_ssh-key_sshKeyId_fk" FOREIGN KEY ("sshKeyId") REFERENCES "public"."ssh-key"("sshKeyId") ON DELETE set null ON UPDATE no action;
+EXCEPTION
+ WHEN duplicate_object THEN null;
+END $$;
+--> statement-breakpoint
+DO $$ BEGIN
+ ALTER TABLE "application" ADD CONSTRAINT "application_serverId_server_serverId_fk" FOREIGN KEY ("serverId") REFERENCES "public"."server"("serverId") ON DELETE cascade ON UPDATE no action;
+EXCEPTION
+ WHEN duplicate_object THEN null;
+END $$;
+--> statement-breakpoint
+DO $$ BEGIN
+ ALTER TABLE "postgres" ADD CONSTRAINT "postgres_serverId_server_serverId_fk" FOREIGN KEY ("serverId") REFERENCES "public"."server"("serverId") ON DELETE cascade ON UPDATE no action;
+EXCEPTION
+ WHEN duplicate_object THEN null;
+END $$;
+--> statement-breakpoint
+DO $$ BEGIN
+ ALTER TABLE "mariadb" ADD CONSTRAINT "mariadb_serverId_server_serverId_fk" FOREIGN KEY ("serverId") REFERENCES "public"."server"("serverId") ON DELETE cascade ON UPDATE no action;
+EXCEPTION
+ WHEN duplicate_object THEN null;
+END $$;
+--> statement-breakpoint
+DO $$ BEGIN
+ ALTER TABLE "mongo" ADD CONSTRAINT "mongo_serverId_server_serverId_fk" FOREIGN KEY ("serverId") REFERENCES "public"."server"("serverId") ON DELETE cascade ON UPDATE no action;
+EXCEPTION
+ WHEN duplicate_object THEN null;
+END $$;
+--> statement-breakpoint
+DO $$ BEGIN
+ ALTER TABLE "mysql" ADD CONSTRAINT "mysql_serverId_server_serverId_fk" FOREIGN KEY ("serverId") REFERENCES "public"."server"("serverId") ON DELETE cascade ON UPDATE no action;
+EXCEPTION
+ WHEN duplicate_object THEN null;
+END $$;
+--> statement-breakpoint
+DO $$ BEGIN
+ ALTER TABLE "deployment" ADD CONSTRAINT "deployment_serverId_server_serverId_fk" FOREIGN KEY ("serverId") REFERENCES "public"."server"("serverId") ON DELETE cascade ON UPDATE no action;
+EXCEPTION
+ WHEN duplicate_object THEN null;
+END $$;
+--> statement-breakpoint
+DO $$ BEGIN
+ ALTER TABLE "redis" ADD CONSTRAINT "redis_serverId_server_serverId_fk" FOREIGN KEY ("serverId") REFERENCES "public"."server"("serverId") ON DELETE cascade ON UPDATE no action;
+EXCEPTION
+ WHEN duplicate_object THEN null;
+END $$;
+--> statement-breakpoint
+DO $$ BEGIN
+ ALTER TABLE "compose" ADD CONSTRAINT "compose_serverId_server_serverId_fk" FOREIGN KEY ("serverId") REFERENCES "public"."server"("serverId") ON DELETE cascade ON UPDATE no action;
+EXCEPTION
+ WHEN duplicate_object THEN null;
+END $$;
diff --git a/apps/dokploy/drizzle/0038_rapid_landau.sql b/apps/dokploy/drizzle/0038_rapid_landau.sql
new file mode 100644
index 000000000..4f756c7c1
--- /dev/null
+++ b/apps/dokploy/drizzle/0038_rapid_landau.sql
@@ -0,0 +1 @@
+ALTER TABLE "registry" ALTER COLUMN "registryUrl" SET DEFAULT '';
\ No newline at end of file
diff --git a/apps/dokploy/drizzle/meta/0037_snapshot.json b/apps/dokploy/drizzle/meta/0037_snapshot.json
new file mode 100644
index 000000000..5ca60d26d
--- /dev/null
+++ b/apps/dokploy/drizzle/meta/0037_snapshot.json
@@ -0,0 +1,3823 @@
+{
+ "id": "19a70a39-f719-400b-b61e-6ddf1bcc6ac5",
+ "prevId": "74cd1475-b79c-4226-b4e6-e5ddb9576025",
+ "version": "6",
+ "dialect": "postgresql",
+ "tables": {
+ "public.application": {
+ "name": "application",
+ "schema": "",
+ "columns": {
+ "applicationId": {
+ "name": "applicationId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "appName": {
+ "name": "appName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "env": {
+ "name": "env",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "buildArgs": {
+ "name": "buildArgs",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "memoryReservation": {
+ "name": "memoryReservation",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "memoryLimit": {
+ "name": "memoryLimit",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cpuReservation": {
+ "name": "cpuReservation",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cpuLimit": {
+ "name": "cpuLimit",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "subtitle": {
+ "name": "subtitle",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "command": {
+ "name": "command",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refreshToken": {
+ "name": "refreshToken",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sourceType": {
+ "name": "sourceType",
+ "type": "sourceType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'github'"
+ },
+ "repository": {
+ "name": "repository",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "owner": {
+ "name": "owner",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "branch": {
+ "name": "branch",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "buildPath": {
+ "name": "buildPath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'/'"
+ },
+ "autoDeploy": {
+ "name": "autoDeploy",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitlabProjectId": {
+ "name": "gitlabProjectId",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitlabRepository": {
+ "name": "gitlabRepository",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitlabOwner": {
+ "name": "gitlabOwner",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitlabBranch": {
+ "name": "gitlabBranch",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitlabBuildPath": {
+ "name": "gitlabBuildPath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'/'"
+ },
+ "gitlabPathNamespace": {
+ "name": "gitlabPathNamespace",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bitbucketRepository": {
+ "name": "bitbucketRepository",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bitbucketOwner": {
+ "name": "bitbucketOwner",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bitbucketBranch": {
+ "name": "bitbucketBranch",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bitbucketBuildPath": {
+ "name": "bitbucketBuildPath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'/'"
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "dockerImage": {
+ "name": "dockerImage",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "customGitUrl": {
+ "name": "customGitUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "customGitBranch": {
+ "name": "customGitBranch",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "customGitBuildPath": {
+ "name": "customGitBuildPath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "customGitSSHKeyId": {
+ "name": "customGitSSHKeyId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "dockerfile": {
+ "name": "dockerfile",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "dockerContextPath": {
+ "name": "dockerContextPath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "dockerBuildStage": {
+ "name": "dockerBuildStage",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "dropBuildPath": {
+ "name": "dropBuildPath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "healthCheckSwarm": {
+ "name": "healthCheckSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "restartPolicySwarm": {
+ "name": "restartPolicySwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "placementSwarm": {
+ "name": "placementSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updateConfigSwarm": {
+ "name": "updateConfigSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rollbackConfigSwarm": {
+ "name": "rollbackConfigSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "modeSwarm": {
+ "name": "modeSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "labelsSwarm": {
+ "name": "labelsSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "networkSwarm": {
+ "name": "networkSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "replicas": {
+ "name": "replicas",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 1
+ },
+ "applicationStatus": {
+ "name": "applicationStatus",
+ "type": "applicationStatus",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'idle'"
+ },
+ "buildType": {
+ "name": "buildType",
+ "type": "buildType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'nixpacks'"
+ },
+ "publishDirectory": {
+ "name": "publishDirectory",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "registryId": {
+ "name": "registryId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "projectId": {
+ "name": "projectId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "githubId": {
+ "name": "githubId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitlabId": {
+ "name": "gitlabId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bitbucketId": {
+ "name": "bitbucketId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "serverId": {
+ "name": "serverId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "application_customGitSSHKeyId_ssh-key_sshKeyId_fk": {
+ "name": "application_customGitSSHKeyId_ssh-key_sshKeyId_fk",
+ "tableFrom": "application",
+ "tableTo": "ssh-key",
+ "columnsFrom": [
+ "customGitSSHKeyId"
+ ],
+ "columnsTo": [
+ "sshKeyId"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "application_registryId_registry_registryId_fk": {
+ "name": "application_registryId_registry_registryId_fk",
+ "tableFrom": "application",
+ "tableTo": "registry",
+ "columnsFrom": [
+ "registryId"
+ ],
+ "columnsTo": [
+ "registryId"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "application_projectId_project_projectId_fk": {
+ "name": "application_projectId_project_projectId_fk",
+ "tableFrom": "application",
+ "tableTo": "project",
+ "columnsFrom": [
+ "projectId"
+ ],
+ "columnsTo": [
+ "projectId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "application_githubId_github_githubId_fk": {
+ "name": "application_githubId_github_githubId_fk",
+ "tableFrom": "application",
+ "tableTo": "github",
+ "columnsFrom": [
+ "githubId"
+ ],
+ "columnsTo": [
+ "githubId"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "application_gitlabId_gitlab_gitlabId_fk": {
+ "name": "application_gitlabId_gitlab_gitlabId_fk",
+ "tableFrom": "application",
+ "tableTo": "gitlab",
+ "columnsFrom": [
+ "gitlabId"
+ ],
+ "columnsTo": [
+ "gitlabId"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "application_bitbucketId_bitbucket_bitbucketId_fk": {
+ "name": "application_bitbucketId_bitbucket_bitbucketId_fk",
+ "tableFrom": "application",
+ "tableTo": "bitbucket",
+ "columnsFrom": [
+ "bitbucketId"
+ ],
+ "columnsTo": [
+ "bitbucketId"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "application_serverId_server_serverId_fk": {
+ "name": "application_serverId_server_serverId_fk",
+ "tableFrom": "application",
+ "tableTo": "server",
+ "columnsFrom": [
+ "serverId"
+ ],
+ "columnsTo": [
+ "serverId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "application_appName_unique": {
+ "name": "application_appName_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "appName"
+ ]
+ }
+ }
+ },
+ "public.postgres": {
+ "name": "postgres",
+ "schema": "",
+ "columns": {
+ "postgresId": {
+ "name": "postgresId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "appName": {
+ "name": "appName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "databaseName": {
+ "name": "databaseName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "databaseUser": {
+ "name": "databaseUser",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "databasePassword": {
+ "name": "databasePassword",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "dockerImage": {
+ "name": "dockerImage",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "command": {
+ "name": "command",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "env": {
+ "name": "env",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "memoryReservation": {
+ "name": "memoryReservation",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "externalPort": {
+ "name": "externalPort",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "memoryLimit": {
+ "name": "memoryLimit",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cpuReservation": {
+ "name": "cpuReservation",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cpuLimit": {
+ "name": "cpuLimit",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "applicationStatus": {
+ "name": "applicationStatus",
+ "type": "applicationStatus",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'idle'"
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "projectId": {
+ "name": "projectId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "serverId": {
+ "name": "serverId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "postgres_projectId_project_projectId_fk": {
+ "name": "postgres_projectId_project_projectId_fk",
+ "tableFrom": "postgres",
+ "tableTo": "project",
+ "columnsFrom": [
+ "projectId"
+ ],
+ "columnsTo": [
+ "projectId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "postgres_serverId_server_serverId_fk": {
+ "name": "postgres_serverId_server_serverId_fk",
+ "tableFrom": "postgres",
+ "tableTo": "server",
+ "columnsFrom": [
+ "serverId"
+ ],
+ "columnsTo": [
+ "serverId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "postgres_appName_unique": {
+ "name": "postgres_appName_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "appName"
+ ]
+ }
+ }
+ },
+ "public.user": {
+ "name": "user",
+ "schema": "",
+ "columns": {
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "isRegistered": {
+ "name": "isRegistered",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "expirationDate": {
+ "name": "expirationDate",
+ "type": "timestamp(3)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "canCreateProjects": {
+ "name": "canCreateProjects",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "canAccessToSSHKeys": {
+ "name": "canAccessToSSHKeys",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "canCreateServices": {
+ "name": "canCreateServices",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "canDeleteProjects": {
+ "name": "canDeleteProjects",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "canDeleteServices": {
+ "name": "canDeleteServices",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "canAccessToDocker": {
+ "name": "canAccessToDocker",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "canAccessToAPI": {
+ "name": "canAccessToAPI",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "canAccessToGitProviders": {
+ "name": "canAccessToGitProviders",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "canAccessToTraefikFiles": {
+ "name": "canAccessToTraefikFiles",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "accesedProjects": {
+ "name": "accesedProjects",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "ARRAY[]::text[]"
+ },
+ "accesedServices": {
+ "name": "accesedServices",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "ARRAY[]::text[]"
+ },
+ "adminId": {
+ "name": "adminId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "authId": {
+ "name": "authId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "user_adminId_admin_adminId_fk": {
+ "name": "user_adminId_admin_adminId_fk",
+ "tableFrom": "user",
+ "tableTo": "admin",
+ "columnsFrom": [
+ "adminId"
+ ],
+ "columnsTo": [
+ "adminId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "user_authId_auth_id_fk": {
+ "name": "user_authId_auth_id_fk",
+ "tableFrom": "user",
+ "tableTo": "auth",
+ "columnsFrom": [
+ "authId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "public.admin": {
+ "name": "admin",
+ "schema": "",
+ "columns": {
+ "adminId": {
+ "name": "adminId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "serverIp": {
+ "name": "serverIp",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "certificateType": {
+ "name": "certificateType",
+ "type": "certificateType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'none'"
+ },
+ "host": {
+ "name": "host",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "letsEncryptEmail": {
+ "name": "letsEncryptEmail",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sshPrivateKey": {
+ "name": "sshPrivateKey",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "enableDockerCleanup": {
+ "name": "enableDockerCleanup",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "enableLogRotation": {
+ "name": "enableLogRotation",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "authId": {
+ "name": "authId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "admin_authId_auth_id_fk": {
+ "name": "admin_authId_auth_id_fk",
+ "tableFrom": "admin",
+ "tableTo": "auth",
+ "columnsFrom": [
+ "authId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "public.auth": {
+ "name": "auth",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "rol": {
+ "name": "rol",
+ "type": "Roles",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "image": {
+ "name": "image",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "secret": {
+ "name": "secret",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is2FAEnabled": {
+ "name": "is2FAEnabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "auth_email_unique": {
+ "name": "auth_email_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "email"
+ ]
+ }
+ }
+ },
+ "public.project": {
+ "name": "project",
+ "schema": "",
+ "columns": {
+ "projectId": {
+ "name": "projectId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "adminId": {
+ "name": "adminId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "project_adminId_admin_adminId_fk": {
+ "name": "project_adminId_admin_adminId_fk",
+ "tableFrom": "project",
+ "tableTo": "admin",
+ "columnsFrom": [
+ "adminId"
+ ],
+ "columnsTo": [
+ "adminId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "public.domain": {
+ "name": "domain",
+ "schema": "",
+ "columns": {
+ "domainId": {
+ "name": "domainId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "host": {
+ "name": "host",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "https": {
+ "name": "https",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "port": {
+ "name": "port",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 3000
+ },
+ "path": {
+ "name": "path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'/'"
+ },
+ "serviceName": {
+ "name": "serviceName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "domainType": {
+ "name": "domainType",
+ "type": "domainType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'application'"
+ },
+ "uniqueConfigKey": {
+ "name": "uniqueConfigKey",
+ "type": "serial",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "composeId": {
+ "name": "composeId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "applicationId": {
+ "name": "applicationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "certificateType": {
+ "name": "certificateType",
+ "type": "certificateType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'none'"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "domain_composeId_compose_composeId_fk": {
+ "name": "domain_composeId_compose_composeId_fk",
+ "tableFrom": "domain",
+ "tableTo": "compose",
+ "columnsFrom": [
+ "composeId"
+ ],
+ "columnsTo": [
+ "composeId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "domain_applicationId_application_applicationId_fk": {
+ "name": "domain_applicationId_application_applicationId_fk",
+ "tableFrom": "domain",
+ "tableTo": "application",
+ "columnsFrom": [
+ "applicationId"
+ ],
+ "columnsTo": [
+ "applicationId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "public.mariadb": {
+ "name": "mariadb",
+ "schema": "",
+ "columns": {
+ "mariadbId": {
+ "name": "mariadbId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "appName": {
+ "name": "appName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "databaseName": {
+ "name": "databaseName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "databaseUser": {
+ "name": "databaseUser",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "databasePassword": {
+ "name": "databasePassword",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "rootPassword": {
+ "name": "rootPassword",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "dockerImage": {
+ "name": "dockerImage",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "command": {
+ "name": "command",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "env": {
+ "name": "env",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "memoryReservation": {
+ "name": "memoryReservation",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "memoryLimit": {
+ "name": "memoryLimit",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cpuReservation": {
+ "name": "cpuReservation",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cpuLimit": {
+ "name": "cpuLimit",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "externalPort": {
+ "name": "externalPort",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "applicationStatus": {
+ "name": "applicationStatus",
+ "type": "applicationStatus",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'idle'"
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "projectId": {
+ "name": "projectId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "serverId": {
+ "name": "serverId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "mariadb_projectId_project_projectId_fk": {
+ "name": "mariadb_projectId_project_projectId_fk",
+ "tableFrom": "mariadb",
+ "tableTo": "project",
+ "columnsFrom": [
+ "projectId"
+ ],
+ "columnsTo": [
+ "projectId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "mariadb_serverId_server_serverId_fk": {
+ "name": "mariadb_serverId_server_serverId_fk",
+ "tableFrom": "mariadb",
+ "tableTo": "server",
+ "columnsFrom": [
+ "serverId"
+ ],
+ "columnsTo": [
+ "serverId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "mariadb_appName_unique": {
+ "name": "mariadb_appName_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "appName"
+ ]
+ }
+ }
+ },
+ "public.mongo": {
+ "name": "mongo",
+ "schema": "",
+ "columns": {
+ "mongoId": {
+ "name": "mongoId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "appName": {
+ "name": "appName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "databaseUser": {
+ "name": "databaseUser",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "databasePassword": {
+ "name": "databasePassword",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "dockerImage": {
+ "name": "dockerImage",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "command": {
+ "name": "command",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "env": {
+ "name": "env",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "memoryReservation": {
+ "name": "memoryReservation",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "memoryLimit": {
+ "name": "memoryLimit",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cpuReservation": {
+ "name": "cpuReservation",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cpuLimit": {
+ "name": "cpuLimit",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "externalPort": {
+ "name": "externalPort",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "applicationStatus": {
+ "name": "applicationStatus",
+ "type": "applicationStatus",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'idle'"
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "projectId": {
+ "name": "projectId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "serverId": {
+ "name": "serverId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "mongo_projectId_project_projectId_fk": {
+ "name": "mongo_projectId_project_projectId_fk",
+ "tableFrom": "mongo",
+ "tableTo": "project",
+ "columnsFrom": [
+ "projectId"
+ ],
+ "columnsTo": [
+ "projectId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "mongo_serverId_server_serverId_fk": {
+ "name": "mongo_serverId_server_serverId_fk",
+ "tableFrom": "mongo",
+ "tableTo": "server",
+ "columnsFrom": [
+ "serverId"
+ ],
+ "columnsTo": [
+ "serverId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "mongo_appName_unique": {
+ "name": "mongo_appName_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "appName"
+ ]
+ }
+ }
+ },
+ "public.mysql": {
+ "name": "mysql",
+ "schema": "",
+ "columns": {
+ "mysqlId": {
+ "name": "mysqlId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "appName": {
+ "name": "appName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "databaseName": {
+ "name": "databaseName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "databaseUser": {
+ "name": "databaseUser",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "databasePassword": {
+ "name": "databasePassword",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "rootPassword": {
+ "name": "rootPassword",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "dockerImage": {
+ "name": "dockerImage",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "command": {
+ "name": "command",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "env": {
+ "name": "env",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "memoryReservation": {
+ "name": "memoryReservation",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "memoryLimit": {
+ "name": "memoryLimit",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cpuReservation": {
+ "name": "cpuReservation",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cpuLimit": {
+ "name": "cpuLimit",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "externalPort": {
+ "name": "externalPort",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "applicationStatus": {
+ "name": "applicationStatus",
+ "type": "applicationStatus",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'idle'"
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "projectId": {
+ "name": "projectId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "serverId": {
+ "name": "serverId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "mysql_projectId_project_projectId_fk": {
+ "name": "mysql_projectId_project_projectId_fk",
+ "tableFrom": "mysql",
+ "tableTo": "project",
+ "columnsFrom": [
+ "projectId"
+ ],
+ "columnsTo": [
+ "projectId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "mysql_serverId_server_serverId_fk": {
+ "name": "mysql_serverId_server_serverId_fk",
+ "tableFrom": "mysql",
+ "tableTo": "server",
+ "columnsFrom": [
+ "serverId"
+ ],
+ "columnsTo": [
+ "serverId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "mysql_appName_unique": {
+ "name": "mysql_appName_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "appName"
+ ]
+ }
+ }
+ },
+ "public.backup": {
+ "name": "backup",
+ "schema": "",
+ "columns": {
+ "backupId": {
+ "name": "backupId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "schedule": {
+ "name": "schedule",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "database": {
+ "name": "database",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "prefix": {
+ "name": "prefix",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "destinationId": {
+ "name": "destinationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "databaseType": {
+ "name": "databaseType",
+ "type": "databaseType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "postgresId": {
+ "name": "postgresId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "mariadbId": {
+ "name": "mariadbId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "mysqlId": {
+ "name": "mysqlId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "mongoId": {
+ "name": "mongoId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "backup_destinationId_destination_destinationId_fk": {
+ "name": "backup_destinationId_destination_destinationId_fk",
+ "tableFrom": "backup",
+ "tableTo": "destination",
+ "columnsFrom": [
+ "destinationId"
+ ],
+ "columnsTo": [
+ "destinationId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "backup_postgresId_postgres_postgresId_fk": {
+ "name": "backup_postgresId_postgres_postgresId_fk",
+ "tableFrom": "backup",
+ "tableTo": "postgres",
+ "columnsFrom": [
+ "postgresId"
+ ],
+ "columnsTo": [
+ "postgresId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "backup_mariadbId_mariadb_mariadbId_fk": {
+ "name": "backup_mariadbId_mariadb_mariadbId_fk",
+ "tableFrom": "backup",
+ "tableTo": "mariadb",
+ "columnsFrom": [
+ "mariadbId"
+ ],
+ "columnsTo": [
+ "mariadbId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "backup_mysqlId_mysql_mysqlId_fk": {
+ "name": "backup_mysqlId_mysql_mysqlId_fk",
+ "tableFrom": "backup",
+ "tableTo": "mysql",
+ "columnsFrom": [
+ "mysqlId"
+ ],
+ "columnsTo": [
+ "mysqlId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "backup_mongoId_mongo_mongoId_fk": {
+ "name": "backup_mongoId_mongo_mongoId_fk",
+ "tableFrom": "backup",
+ "tableTo": "mongo",
+ "columnsFrom": [
+ "mongoId"
+ ],
+ "columnsTo": [
+ "mongoId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "public.destination": {
+ "name": "destination",
+ "schema": "",
+ "columns": {
+ "destinationId": {
+ "name": "destinationId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "accessKey": {
+ "name": "accessKey",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "secretAccessKey": {
+ "name": "secretAccessKey",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "bucket": {
+ "name": "bucket",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "region": {
+ "name": "region",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "endpoint": {
+ "name": "endpoint",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "adminId": {
+ "name": "adminId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "destination_adminId_admin_adminId_fk": {
+ "name": "destination_adminId_admin_adminId_fk",
+ "tableFrom": "destination",
+ "tableTo": "admin",
+ "columnsFrom": [
+ "adminId"
+ ],
+ "columnsTo": [
+ "adminId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "public.deployment": {
+ "name": "deployment",
+ "schema": "",
+ "columns": {
+ "deploymentId": {
+ "name": "deploymentId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "deploymentStatus",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'running'"
+ },
+ "logPath": {
+ "name": "logPath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "applicationId": {
+ "name": "applicationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "composeId": {
+ "name": "composeId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "serverId": {
+ "name": "serverId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "deployment_applicationId_application_applicationId_fk": {
+ "name": "deployment_applicationId_application_applicationId_fk",
+ "tableFrom": "deployment",
+ "tableTo": "application",
+ "columnsFrom": [
+ "applicationId"
+ ],
+ "columnsTo": [
+ "applicationId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "deployment_composeId_compose_composeId_fk": {
+ "name": "deployment_composeId_compose_composeId_fk",
+ "tableFrom": "deployment",
+ "tableTo": "compose",
+ "columnsFrom": [
+ "composeId"
+ ],
+ "columnsTo": [
+ "composeId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "deployment_serverId_server_serverId_fk": {
+ "name": "deployment_serverId_server_serverId_fk",
+ "tableFrom": "deployment",
+ "tableTo": "server",
+ "columnsFrom": [
+ "serverId"
+ ],
+ "columnsTo": [
+ "serverId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "public.mount": {
+ "name": "mount",
+ "schema": "",
+ "columns": {
+ "mountId": {
+ "name": "mountId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "type": {
+ "name": "type",
+ "type": "mountType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "hostPath": {
+ "name": "hostPath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "volumeName": {
+ "name": "volumeName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "filePath": {
+ "name": "filePath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "content": {
+ "name": "content",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "serviceType": {
+ "name": "serviceType",
+ "type": "serviceType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'application'"
+ },
+ "mountPath": {
+ "name": "mountPath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "applicationId": {
+ "name": "applicationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "postgresId": {
+ "name": "postgresId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "mariadbId": {
+ "name": "mariadbId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "mongoId": {
+ "name": "mongoId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "mysqlId": {
+ "name": "mysqlId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "redisId": {
+ "name": "redisId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "composeId": {
+ "name": "composeId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "mount_applicationId_application_applicationId_fk": {
+ "name": "mount_applicationId_application_applicationId_fk",
+ "tableFrom": "mount",
+ "tableTo": "application",
+ "columnsFrom": [
+ "applicationId"
+ ],
+ "columnsTo": [
+ "applicationId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "mount_postgresId_postgres_postgresId_fk": {
+ "name": "mount_postgresId_postgres_postgresId_fk",
+ "tableFrom": "mount",
+ "tableTo": "postgres",
+ "columnsFrom": [
+ "postgresId"
+ ],
+ "columnsTo": [
+ "postgresId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "mount_mariadbId_mariadb_mariadbId_fk": {
+ "name": "mount_mariadbId_mariadb_mariadbId_fk",
+ "tableFrom": "mount",
+ "tableTo": "mariadb",
+ "columnsFrom": [
+ "mariadbId"
+ ],
+ "columnsTo": [
+ "mariadbId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "mount_mongoId_mongo_mongoId_fk": {
+ "name": "mount_mongoId_mongo_mongoId_fk",
+ "tableFrom": "mount",
+ "tableTo": "mongo",
+ "columnsFrom": [
+ "mongoId"
+ ],
+ "columnsTo": [
+ "mongoId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "mount_mysqlId_mysql_mysqlId_fk": {
+ "name": "mount_mysqlId_mysql_mysqlId_fk",
+ "tableFrom": "mount",
+ "tableTo": "mysql",
+ "columnsFrom": [
+ "mysqlId"
+ ],
+ "columnsTo": [
+ "mysqlId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "mount_redisId_redis_redisId_fk": {
+ "name": "mount_redisId_redis_redisId_fk",
+ "tableFrom": "mount",
+ "tableTo": "redis",
+ "columnsFrom": [
+ "redisId"
+ ],
+ "columnsTo": [
+ "redisId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "mount_composeId_compose_composeId_fk": {
+ "name": "mount_composeId_compose_composeId_fk",
+ "tableFrom": "mount",
+ "tableTo": "compose",
+ "columnsFrom": [
+ "composeId"
+ ],
+ "columnsTo": [
+ "composeId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "public.certificate": {
+ "name": "certificate",
+ "schema": "",
+ "columns": {
+ "certificateId": {
+ "name": "certificateId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "certificateData": {
+ "name": "certificateData",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "privateKey": {
+ "name": "privateKey",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "certificatePath": {
+ "name": "certificatePath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "autoRenew": {
+ "name": "autoRenew",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "certificate_certificatePath_unique": {
+ "name": "certificate_certificatePath_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "certificatePath"
+ ]
+ }
+ }
+ },
+ "public.session": {
+ "name": "session",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "session_user_id_auth_id_fk": {
+ "name": "session_user_id_auth_id_fk",
+ "tableFrom": "session",
+ "tableTo": "auth",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "public.redirect": {
+ "name": "redirect",
+ "schema": "",
+ "columns": {
+ "redirectId": {
+ "name": "redirectId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "regex": {
+ "name": "regex",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "replacement": {
+ "name": "replacement",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "permanent": {
+ "name": "permanent",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "uniqueConfigKey": {
+ "name": "uniqueConfigKey",
+ "type": "serial",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "applicationId": {
+ "name": "applicationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "redirect_applicationId_application_applicationId_fk": {
+ "name": "redirect_applicationId_application_applicationId_fk",
+ "tableFrom": "redirect",
+ "tableTo": "application",
+ "columnsFrom": [
+ "applicationId"
+ ],
+ "columnsTo": [
+ "applicationId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "public.security": {
+ "name": "security",
+ "schema": "",
+ "columns": {
+ "securityId": {
+ "name": "securityId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "applicationId": {
+ "name": "applicationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "security_applicationId_application_applicationId_fk": {
+ "name": "security_applicationId_application_applicationId_fk",
+ "tableFrom": "security",
+ "tableTo": "application",
+ "columnsFrom": [
+ "applicationId"
+ ],
+ "columnsTo": [
+ "applicationId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "security_username_applicationId_unique": {
+ "name": "security_username_applicationId_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "username",
+ "applicationId"
+ ]
+ }
+ }
+ },
+ "public.port": {
+ "name": "port",
+ "schema": "",
+ "columns": {
+ "portId": {
+ "name": "portId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "publishedPort": {
+ "name": "publishedPort",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "targetPort": {
+ "name": "targetPort",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "protocol": {
+ "name": "protocol",
+ "type": "protocolType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "applicationId": {
+ "name": "applicationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "port_applicationId_application_applicationId_fk": {
+ "name": "port_applicationId_application_applicationId_fk",
+ "tableFrom": "port",
+ "tableTo": "application",
+ "columnsFrom": [
+ "applicationId"
+ ],
+ "columnsTo": [
+ "applicationId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "public.redis": {
+ "name": "redis",
+ "schema": "",
+ "columns": {
+ "redisId": {
+ "name": "redisId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "appName": {
+ "name": "appName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "dockerImage": {
+ "name": "dockerImage",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "command": {
+ "name": "command",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "env": {
+ "name": "env",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "memoryReservation": {
+ "name": "memoryReservation",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "memoryLimit": {
+ "name": "memoryLimit",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cpuReservation": {
+ "name": "cpuReservation",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cpuLimit": {
+ "name": "cpuLimit",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "externalPort": {
+ "name": "externalPort",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "applicationStatus": {
+ "name": "applicationStatus",
+ "type": "applicationStatus",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'idle'"
+ },
+ "projectId": {
+ "name": "projectId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "serverId": {
+ "name": "serverId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "redis_projectId_project_projectId_fk": {
+ "name": "redis_projectId_project_projectId_fk",
+ "tableFrom": "redis",
+ "tableTo": "project",
+ "columnsFrom": [
+ "projectId"
+ ],
+ "columnsTo": [
+ "projectId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "redis_serverId_server_serverId_fk": {
+ "name": "redis_serverId_server_serverId_fk",
+ "tableFrom": "redis",
+ "tableTo": "server",
+ "columnsFrom": [
+ "serverId"
+ ],
+ "columnsTo": [
+ "serverId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "redis_appName_unique": {
+ "name": "redis_appName_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "appName"
+ ]
+ }
+ }
+ },
+ "public.compose": {
+ "name": "compose",
+ "schema": "",
+ "columns": {
+ "composeId": {
+ "name": "composeId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "appName": {
+ "name": "appName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "env": {
+ "name": "env",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "composeFile": {
+ "name": "composeFile",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "refreshToken": {
+ "name": "refreshToken",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sourceType": {
+ "name": "sourceType",
+ "type": "sourceTypeCompose",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'github'"
+ },
+ "composeType": {
+ "name": "composeType",
+ "type": "composeType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'docker-compose'"
+ },
+ "repository": {
+ "name": "repository",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "owner": {
+ "name": "owner",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "branch": {
+ "name": "branch",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "autoDeploy": {
+ "name": "autoDeploy",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitlabProjectId": {
+ "name": "gitlabProjectId",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitlabRepository": {
+ "name": "gitlabRepository",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitlabOwner": {
+ "name": "gitlabOwner",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitlabBranch": {
+ "name": "gitlabBranch",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitlabPathNamespace": {
+ "name": "gitlabPathNamespace",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bitbucketRepository": {
+ "name": "bitbucketRepository",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bitbucketOwner": {
+ "name": "bitbucketOwner",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bitbucketBranch": {
+ "name": "bitbucketBranch",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "customGitUrl": {
+ "name": "customGitUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "customGitBranch": {
+ "name": "customGitBranch",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "customGitSSHKeyId": {
+ "name": "customGitSSHKeyId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "command": {
+ "name": "command",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "composePath": {
+ "name": "composePath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'./docker-compose.yml'"
+ },
+ "suffix": {
+ "name": "suffix",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "randomize": {
+ "name": "randomize",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "composeStatus": {
+ "name": "composeStatus",
+ "type": "applicationStatus",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'idle'"
+ },
+ "projectId": {
+ "name": "projectId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "githubId": {
+ "name": "githubId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitlabId": {
+ "name": "gitlabId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bitbucketId": {
+ "name": "bitbucketId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "serverId": {
+ "name": "serverId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "compose_customGitSSHKeyId_ssh-key_sshKeyId_fk": {
+ "name": "compose_customGitSSHKeyId_ssh-key_sshKeyId_fk",
+ "tableFrom": "compose",
+ "tableTo": "ssh-key",
+ "columnsFrom": [
+ "customGitSSHKeyId"
+ ],
+ "columnsTo": [
+ "sshKeyId"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "compose_projectId_project_projectId_fk": {
+ "name": "compose_projectId_project_projectId_fk",
+ "tableFrom": "compose",
+ "tableTo": "project",
+ "columnsFrom": [
+ "projectId"
+ ],
+ "columnsTo": [
+ "projectId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "compose_githubId_github_githubId_fk": {
+ "name": "compose_githubId_github_githubId_fk",
+ "tableFrom": "compose",
+ "tableTo": "github",
+ "columnsFrom": [
+ "githubId"
+ ],
+ "columnsTo": [
+ "githubId"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "compose_gitlabId_gitlab_gitlabId_fk": {
+ "name": "compose_gitlabId_gitlab_gitlabId_fk",
+ "tableFrom": "compose",
+ "tableTo": "gitlab",
+ "columnsFrom": [
+ "gitlabId"
+ ],
+ "columnsTo": [
+ "gitlabId"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "compose_bitbucketId_bitbucket_bitbucketId_fk": {
+ "name": "compose_bitbucketId_bitbucket_bitbucketId_fk",
+ "tableFrom": "compose",
+ "tableTo": "bitbucket",
+ "columnsFrom": [
+ "bitbucketId"
+ ],
+ "columnsTo": [
+ "bitbucketId"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "compose_serverId_server_serverId_fk": {
+ "name": "compose_serverId_server_serverId_fk",
+ "tableFrom": "compose",
+ "tableTo": "server",
+ "columnsFrom": [
+ "serverId"
+ ],
+ "columnsTo": [
+ "serverId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "public.registry": {
+ "name": "registry",
+ "schema": "",
+ "columns": {
+ "registryId": {
+ "name": "registryId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "registryName": {
+ "name": "registryName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "imagePrefix": {
+ "name": "imagePrefix",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "registryUrl": {
+ "name": "registryUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "selfHosted": {
+ "name": "selfHosted",
+ "type": "RegistryType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'cloud'"
+ },
+ "adminId": {
+ "name": "adminId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "registry_adminId_admin_adminId_fk": {
+ "name": "registry_adminId_admin_adminId_fk",
+ "tableFrom": "registry",
+ "tableTo": "admin",
+ "columnsFrom": [
+ "adminId"
+ ],
+ "columnsTo": [
+ "adminId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "public.discord": {
+ "name": "discord",
+ "schema": "",
+ "columns": {
+ "discordId": {
+ "name": "discordId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "webhookUrl": {
+ "name": "webhookUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "public.email": {
+ "name": "email",
+ "schema": "",
+ "columns": {
+ "emailId": {
+ "name": "emailId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "smtpServer": {
+ "name": "smtpServer",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "smtpPort": {
+ "name": "smtpPort",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "fromAddress": {
+ "name": "fromAddress",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "toAddress": {
+ "name": "toAddress",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "public.notification": {
+ "name": "notification",
+ "schema": "",
+ "columns": {
+ "notificationId": {
+ "name": "notificationId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "appDeploy": {
+ "name": "appDeploy",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "appBuildError": {
+ "name": "appBuildError",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "databaseBackup": {
+ "name": "databaseBackup",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "dokployRestart": {
+ "name": "dokployRestart",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "dockerCleanup": {
+ "name": "dockerCleanup",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "notificationType": {
+ "name": "notificationType",
+ "type": "notificationType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "slackId": {
+ "name": "slackId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "telegramId": {
+ "name": "telegramId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "discordId": {
+ "name": "discordId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "emailId": {
+ "name": "emailId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "notification_slackId_slack_slackId_fk": {
+ "name": "notification_slackId_slack_slackId_fk",
+ "tableFrom": "notification",
+ "tableTo": "slack",
+ "columnsFrom": [
+ "slackId"
+ ],
+ "columnsTo": [
+ "slackId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "notification_telegramId_telegram_telegramId_fk": {
+ "name": "notification_telegramId_telegram_telegramId_fk",
+ "tableFrom": "notification",
+ "tableTo": "telegram",
+ "columnsFrom": [
+ "telegramId"
+ ],
+ "columnsTo": [
+ "telegramId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "notification_discordId_discord_discordId_fk": {
+ "name": "notification_discordId_discord_discordId_fk",
+ "tableFrom": "notification",
+ "tableTo": "discord",
+ "columnsFrom": [
+ "discordId"
+ ],
+ "columnsTo": [
+ "discordId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "notification_emailId_email_emailId_fk": {
+ "name": "notification_emailId_email_emailId_fk",
+ "tableFrom": "notification",
+ "tableTo": "email",
+ "columnsFrom": [
+ "emailId"
+ ],
+ "columnsTo": [
+ "emailId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "public.slack": {
+ "name": "slack",
+ "schema": "",
+ "columns": {
+ "slackId": {
+ "name": "slackId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "webhookUrl": {
+ "name": "webhookUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "channel": {
+ "name": "channel",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "public.telegram": {
+ "name": "telegram",
+ "schema": "",
+ "columns": {
+ "telegramId": {
+ "name": "telegramId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "botToken": {
+ "name": "botToken",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "chatId": {
+ "name": "chatId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "public.ssh-key": {
+ "name": "ssh-key",
+ "schema": "",
+ "columns": {
+ "sshKeyId": {
+ "name": "sshKeyId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "publicKey": {
+ "name": "publicKey",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "lastUsedAt": {
+ "name": "lastUsedAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "public.git_provider": {
+ "name": "git_provider",
+ "schema": "",
+ "columns": {
+ "gitProviderId": {
+ "name": "gitProviderId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "providerType": {
+ "name": "providerType",
+ "type": "gitProviderType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'github'"
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "authId": {
+ "name": "authId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "git_provider_authId_auth_id_fk": {
+ "name": "git_provider_authId_auth_id_fk",
+ "tableFrom": "git_provider",
+ "tableTo": "auth",
+ "columnsFrom": [
+ "authId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "public.bitbucket": {
+ "name": "bitbucket",
+ "schema": "",
+ "columns": {
+ "bitbucketId": {
+ "name": "bitbucketId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "bitbucketUsername": {
+ "name": "bitbucketUsername",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "appPassword": {
+ "name": "appPassword",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bitbucketWorkspaceName": {
+ "name": "bitbucketWorkspaceName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitProviderId": {
+ "name": "gitProviderId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "bitbucket_gitProviderId_git_provider_gitProviderId_fk": {
+ "name": "bitbucket_gitProviderId_git_provider_gitProviderId_fk",
+ "tableFrom": "bitbucket",
+ "tableTo": "git_provider",
+ "columnsFrom": [
+ "gitProviderId"
+ ],
+ "columnsTo": [
+ "gitProviderId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "public.github": {
+ "name": "github",
+ "schema": "",
+ "columns": {
+ "githubId": {
+ "name": "githubId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "githubAppName": {
+ "name": "githubAppName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "githubAppId": {
+ "name": "githubAppId",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "githubClientId": {
+ "name": "githubClientId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "githubClientSecret": {
+ "name": "githubClientSecret",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "githubInstallationId": {
+ "name": "githubInstallationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "githubPrivateKey": {
+ "name": "githubPrivateKey",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "githubWebhookSecret": {
+ "name": "githubWebhookSecret",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitProviderId": {
+ "name": "gitProviderId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "github_gitProviderId_git_provider_gitProviderId_fk": {
+ "name": "github_gitProviderId_git_provider_gitProviderId_fk",
+ "tableFrom": "github",
+ "tableTo": "git_provider",
+ "columnsFrom": [
+ "gitProviderId"
+ ],
+ "columnsTo": [
+ "gitProviderId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "public.gitlab": {
+ "name": "gitlab",
+ "schema": "",
+ "columns": {
+ "gitlabId": {
+ "name": "gitlabId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "application_id": {
+ "name": "application_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "redirect_uri": {
+ "name": "redirect_uri",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "secret": {
+ "name": "secret",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "access_token": {
+ "name": "access_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refresh_token": {
+ "name": "refresh_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "group_name": {
+ "name": "group_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitProviderId": {
+ "name": "gitProviderId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "gitlab_gitProviderId_git_provider_gitProviderId_fk": {
+ "name": "gitlab_gitProviderId_git_provider_gitProviderId_fk",
+ "tableFrom": "gitlab",
+ "tableTo": "git_provider",
+ "columnsFrom": [
+ "gitProviderId"
+ ],
+ "columnsTo": [
+ "gitProviderId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "public.server": {
+ "name": "server",
+ "schema": "",
+ "columns": {
+ "serverId": {
+ "name": "serverId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ipAddress": {
+ "name": "ipAddress",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "port": {
+ "name": "port",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'root'"
+ },
+ "appName": {
+ "name": "appName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "enableDockerCleanup": {
+ "name": "enableDockerCleanup",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "adminId": {
+ "name": "adminId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "sshKeyId": {
+ "name": "sshKeyId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "server_adminId_admin_adminId_fk": {
+ "name": "server_adminId_admin_adminId_fk",
+ "tableFrom": "server",
+ "tableTo": "admin",
+ "columnsFrom": [
+ "adminId"
+ ],
+ "columnsTo": [
+ "adminId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "server_sshKeyId_ssh-key_sshKeyId_fk": {
+ "name": "server_sshKeyId_ssh-key_sshKeyId_fk",
+ "tableFrom": "server",
+ "tableTo": "ssh-key",
+ "columnsFrom": [
+ "sshKeyId"
+ ],
+ "columnsTo": [
+ "sshKeyId"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ }
+ },
+ "enums": {
+ "public.buildType": {
+ "name": "buildType",
+ "schema": "public",
+ "values": [
+ "dockerfile",
+ "heroku_buildpacks",
+ "paketo_buildpacks",
+ "nixpacks",
+ "static"
+ ]
+ },
+ "public.sourceType": {
+ "name": "sourceType",
+ "schema": "public",
+ "values": [
+ "docker",
+ "git",
+ "github",
+ "gitlab",
+ "bitbucket",
+ "drop"
+ ]
+ },
+ "public.Roles": {
+ "name": "Roles",
+ "schema": "public",
+ "values": [
+ "admin",
+ "user"
+ ]
+ },
+ "public.domainType": {
+ "name": "domainType",
+ "schema": "public",
+ "values": [
+ "compose",
+ "application"
+ ]
+ },
+ "public.databaseType": {
+ "name": "databaseType",
+ "schema": "public",
+ "values": [
+ "postgres",
+ "mariadb",
+ "mysql",
+ "mongo"
+ ]
+ },
+ "public.deploymentStatus": {
+ "name": "deploymentStatus",
+ "schema": "public",
+ "values": [
+ "running",
+ "done",
+ "error"
+ ]
+ },
+ "public.mountType": {
+ "name": "mountType",
+ "schema": "public",
+ "values": [
+ "bind",
+ "volume",
+ "file"
+ ]
+ },
+ "public.serviceType": {
+ "name": "serviceType",
+ "schema": "public",
+ "values": [
+ "application",
+ "postgres",
+ "mysql",
+ "mariadb",
+ "mongo",
+ "redis",
+ "compose"
+ ]
+ },
+ "public.protocolType": {
+ "name": "protocolType",
+ "schema": "public",
+ "values": [
+ "tcp",
+ "udp"
+ ]
+ },
+ "public.applicationStatus": {
+ "name": "applicationStatus",
+ "schema": "public",
+ "values": [
+ "idle",
+ "running",
+ "done",
+ "error"
+ ]
+ },
+ "public.certificateType": {
+ "name": "certificateType",
+ "schema": "public",
+ "values": [
+ "letsencrypt",
+ "none"
+ ]
+ },
+ "public.composeType": {
+ "name": "composeType",
+ "schema": "public",
+ "values": [
+ "docker-compose",
+ "stack"
+ ]
+ },
+ "public.sourceTypeCompose": {
+ "name": "sourceTypeCompose",
+ "schema": "public",
+ "values": [
+ "git",
+ "github",
+ "gitlab",
+ "bitbucket",
+ "raw"
+ ]
+ },
+ "public.RegistryType": {
+ "name": "RegistryType",
+ "schema": "public",
+ "values": [
+ "selfHosted",
+ "cloud"
+ ]
+ },
+ "public.notificationType": {
+ "name": "notificationType",
+ "schema": "public",
+ "values": [
+ "slack",
+ "telegram",
+ "discord",
+ "email"
+ ]
+ },
+ "public.gitProviderType": {
+ "name": "gitProviderType",
+ "schema": "public",
+ "values": [
+ "github",
+ "gitlab",
+ "bitbucket"
+ ]
+ }
+ },
+ "schemas": {},
+ "_meta": {
+ "columns": {},
+ "schemas": {},
+ "tables": {}
+ }
+}
\ No newline at end of file
diff --git a/apps/dokploy/drizzle/meta/0038_snapshot.json b/apps/dokploy/drizzle/meta/0038_snapshot.json
new file mode 100644
index 000000000..4eb2b5eca
--- /dev/null
+++ b/apps/dokploy/drizzle/meta/0038_snapshot.json
@@ -0,0 +1,3824 @@
+{
+ "id": "8ffdfaff-f166-42dc-ac77-4fd9309d736a",
+ "prevId": "19a70a39-f719-400b-b61e-6ddf1bcc6ac5",
+ "version": "6",
+ "dialect": "postgresql",
+ "tables": {
+ "public.application": {
+ "name": "application",
+ "schema": "",
+ "columns": {
+ "applicationId": {
+ "name": "applicationId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "appName": {
+ "name": "appName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "env": {
+ "name": "env",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "buildArgs": {
+ "name": "buildArgs",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "memoryReservation": {
+ "name": "memoryReservation",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "memoryLimit": {
+ "name": "memoryLimit",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cpuReservation": {
+ "name": "cpuReservation",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cpuLimit": {
+ "name": "cpuLimit",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "subtitle": {
+ "name": "subtitle",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "command": {
+ "name": "command",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refreshToken": {
+ "name": "refreshToken",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sourceType": {
+ "name": "sourceType",
+ "type": "sourceType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'github'"
+ },
+ "repository": {
+ "name": "repository",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "owner": {
+ "name": "owner",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "branch": {
+ "name": "branch",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "buildPath": {
+ "name": "buildPath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'/'"
+ },
+ "autoDeploy": {
+ "name": "autoDeploy",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitlabProjectId": {
+ "name": "gitlabProjectId",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitlabRepository": {
+ "name": "gitlabRepository",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitlabOwner": {
+ "name": "gitlabOwner",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitlabBranch": {
+ "name": "gitlabBranch",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitlabBuildPath": {
+ "name": "gitlabBuildPath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'/'"
+ },
+ "gitlabPathNamespace": {
+ "name": "gitlabPathNamespace",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bitbucketRepository": {
+ "name": "bitbucketRepository",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bitbucketOwner": {
+ "name": "bitbucketOwner",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bitbucketBranch": {
+ "name": "bitbucketBranch",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bitbucketBuildPath": {
+ "name": "bitbucketBuildPath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'/'"
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "dockerImage": {
+ "name": "dockerImage",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "customGitUrl": {
+ "name": "customGitUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "customGitBranch": {
+ "name": "customGitBranch",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "customGitBuildPath": {
+ "name": "customGitBuildPath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "customGitSSHKeyId": {
+ "name": "customGitSSHKeyId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "dockerfile": {
+ "name": "dockerfile",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "dockerContextPath": {
+ "name": "dockerContextPath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "dockerBuildStage": {
+ "name": "dockerBuildStage",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "dropBuildPath": {
+ "name": "dropBuildPath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "healthCheckSwarm": {
+ "name": "healthCheckSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "restartPolicySwarm": {
+ "name": "restartPolicySwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "placementSwarm": {
+ "name": "placementSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updateConfigSwarm": {
+ "name": "updateConfigSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rollbackConfigSwarm": {
+ "name": "rollbackConfigSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "modeSwarm": {
+ "name": "modeSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "labelsSwarm": {
+ "name": "labelsSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "networkSwarm": {
+ "name": "networkSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "replicas": {
+ "name": "replicas",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 1
+ },
+ "applicationStatus": {
+ "name": "applicationStatus",
+ "type": "applicationStatus",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'idle'"
+ },
+ "buildType": {
+ "name": "buildType",
+ "type": "buildType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'nixpacks'"
+ },
+ "publishDirectory": {
+ "name": "publishDirectory",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "registryId": {
+ "name": "registryId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "projectId": {
+ "name": "projectId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "githubId": {
+ "name": "githubId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitlabId": {
+ "name": "gitlabId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bitbucketId": {
+ "name": "bitbucketId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "serverId": {
+ "name": "serverId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "application_customGitSSHKeyId_ssh-key_sshKeyId_fk": {
+ "name": "application_customGitSSHKeyId_ssh-key_sshKeyId_fk",
+ "tableFrom": "application",
+ "tableTo": "ssh-key",
+ "columnsFrom": [
+ "customGitSSHKeyId"
+ ],
+ "columnsTo": [
+ "sshKeyId"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "application_registryId_registry_registryId_fk": {
+ "name": "application_registryId_registry_registryId_fk",
+ "tableFrom": "application",
+ "tableTo": "registry",
+ "columnsFrom": [
+ "registryId"
+ ],
+ "columnsTo": [
+ "registryId"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "application_projectId_project_projectId_fk": {
+ "name": "application_projectId_project_projectId_fk",
+ "tableFrom": "application",
+ "tableTo": "project",
+ "columnsFrom": [
+ "projectId"
+ ],
+ "columnsTo": [
+ "projectId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "application_githubId_github_githubId_fk": {
+ "name": "application_githubId_github_githubId_fk",
+ "tableFrom": "application",
+ "tableTo": "github",
+ "columnsFrom": [
+ "githubId"
+ ],
+ "columnsTo": [
+ "githubId"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "application_gitlabId_gitlab_gitlabId_fk": {
+ "name": "application_gitlabId_gitlab_gitlabId_fk",
+ "tableFrom": "application",
+ "tableTo": "gitlab",
+ "columnsFrom": [
+ "gitlabId"
+ ],
+ "columnsTo": [
+ "gitlabId"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "application_bitbucketId_bitbucket_bitbucketId_fk": {
+ "name": "application_bitbucketId_bitbucket_bitbucketId_fk",
+ "tableFrom": "application",
+ "tableTo": "bitbucket",
+ "columnsFrom": [
+ "bitbucketId"
+ ],
+ "columnsTo": [
+ "bitbucketId"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "application_serverId_server_serverId_fk": {
+ "name": "application_serverId_server_serverId_fk",
+ "tableFrom": "application",
+ "tableTo": "server",
+ "columnsFrom": [
+ "serverId"
+ ],
+ "columnsTo": [
+ "serverId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "application_appName_unique": {
+ "name": "application_appName_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "appName"
+ ]
+ }
+ }
+ },
+ "public.postgres": {
+ "name": "postgres",
+ "schema": "",
+ "columns": {
+ "postgresId": {
+ "name": "postgresId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "appName": {
+ "name": "appName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "databaseName": {
+ "name": "databaseName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "databaseUser": {
+ "name": "databaseUser",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "databasePassword": {
+ "name": "databasePassword",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "dockerImage": {
+ "name": "dockerImage",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "command": {
+ "name": "command",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "env": {
+ "name": "env",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "memoryReservation": {
+ "name": "memoryReservation",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "externalPort": {
+ "name": "externalPort",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "memoryLimit": {
+ "name": "memoryLimit",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cpuReservation": {
+ "name": "cpuReservation",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cpuLimit": {
+ "name": "cpuLimit",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "applicationStatus": {
+ "name": "applicationStatus",
+ "type": "applicationStatus",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'idle'"
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "projectId": {
+ "name": "projectId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "serverId": {
+ "name": "serverId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "postgres_projectId_project_projectId_fk": {
+ "name": "postgres_projectId_project_projectId_fk",
+ "tableFrom": "postgres",
+ "tableTo": "project",
+ "columnsFrom": [
+ "projectId"
+ ],
+ "columnsTo": [
+ "projectId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "postgres_serverId_server_serverId_fk": {
+ "name": "postgres_serverId_server_serverId_fk",
+ "tableFrom": "postgres",
+ "tableTo": "server",
+ "columnsFrom": [
+ "serverId"
+ ],
+ "columnsTo": [
+ "serverId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "postgres_appName_unique": {
+ "name": "postgres_appName_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "appName"
+ ]
+ }
+ }
+ },
+ "public.user": {
+ "name": "user",
+ "schema": "",
+ "columns": {
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "isRegistered": {
+ "name": "isRegistered",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "expirationDate": {
+ "name": "expirationDate",
+ "type": "timestamp(3)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "canCreateProjects": {
+ "name": "canCreateProjects",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "canAccessToSSHKeys": {
+ "name": "canAccessToSSHKeys",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "canCreateServices": {
+ "name": "canCreateServices",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "canDeleteProjects": {
+ "name": "canDeleteProjects",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "canDeleteServices": {
+ "name": "canDeleteServices",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "canAccessToDocker": {
+ "name": "canAccessToDocker",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "canAccessToAPI": {
+ "name": "canAccessToAPI",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "canAccessToGitProviders": {
+ "name": "canAccessToGitProviders",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "canAccessToTraefikFiles": {
+ "name": "canAccessToTraefikFiles",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "accesedProjects": {
+ "name": "accesedProjects",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "ARRAY[]::text[]"
+ },
+ "accesedServices": {
+ "name": "accesedServices",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "ARRAY[]::text[]"
+ },
+ "adminId": {
+ "name": "adminId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "authId": {
+ "name": "authId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "user_adminId_admin_adminId_fk": {
+ "name": "user_adminId_admin_adminId_fk",
+ "tableFrom": "user",
+ "tableTo": "admin",
+ "columnsFrom": [
+ "adminId"
+ ],
+ "columnsTo": [
+ "adminId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "user_authId_auth_id_fk": {
+ "name": "user_authId_auth_id_fk",
+ "tableFrom": "user",
+ "tableTo": "auth",
+ "columnsFrom": [
+ "authId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "public.admin": {
+ "name": "admin",
+ "schema": "",
+ "columns": {
+ "adminId": {
+ "name": "adminId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "serverIp": {
+ "name": "serverIp",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "certificateType": {
+ "name": "certificateType",
+ "type": "certificateType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'none'"
+ },
+ "host": {
+ "name": "host",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "letsEncryptEmail": {
+ "name": "letsEncryptEmail",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sshPrivateKey": {
+ "name": "sshPrivateKey",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "enableDockerCleanup": {
+ "name": "enableDockerCleanup",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "enableLogRotation": {
+ "name": "enableLogRotation",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "authId": {
+ "name": "authId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "admin_authId_auth_id_fk": {
+ "name": "admin_authId_auth_id_fk",
+ "tableFrom": "admin",
+ "tableTo": "auth",
+ "columnsFrom": [
+ "authId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "public.auth": {
+ "name": "auth",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "rol": {
+ "name": "rol",
+ "type": "Roles",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "image": {
+ "name": "image",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "secret": {
+ "name": "secret",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is2FAEnabled": {
+ "name": "is2FAEnabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "auth_email_unique": {
+ "name": "auth_email_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "email"
+ ]
+ }
+ }
+ },
+ "public.project": {
+ "name": "project",
+ "schema": "",
+ "columns": {
+ "projectId": {
+ "name": "projectId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "adminId": {
+ "name": "adminId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "project_adminId_admin_adminId_fk": {
+ "name": "project_adminId_admin_adminId_fk",
+ "tableFrom": "project",
+ "tableTo": "admin",
+ "columnsFrom": [
+ "adminId"
+ ],
+ "columnsTo": [
+ "adminId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "public.domain": {
+ "name": "domain",
+ "schema": "",
+ "columns": {
+ "domainId": {
+ "name": "domainId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "host": {
+ "name": "host",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "https": {
+ "name": "https",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "port": {
+ "name": "port",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 3000
+ },
+ "path": {
+ "name": "path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'/'"
+ },
+ "serviceName": {
+ "name": "serviceName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "domainType": {
+ "name": "domainType",
+ "type": "domainType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'application'"
+ },
+ "uniqueConfigKey": {
+ "name": "uniqueConfigKey",
+ "type": "serial",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "composeId": {
+ "name": "composeId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "applicationId": {
+ "name": "applicationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "certificateType": {
+ "name": "certificateType",
+ "type": "certificateType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'none'"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "domain_composeId_compose_composeId_fk": {
+ "name": "domain_composeId_compose_composeId_fk",
+ "tableFrom": "domain",
+ "tableTo": "compose",
+ "columnsFrom": [
+ "composeId"
+ ],
+ "columnsTo": [
+ "composeId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "domain_applicationId_application_applicationId_fk": {
+ "name": "domain_applicationId_application_applicationId_fk",
+ "tableFrom": "domain",
+ "tableTo": "application",
+ "columnsFrom": [
+ "applicationId"
+ ],
+ "columnsTo": [
+ "applicationId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "public.mariadb": {
+ "name": "mariadb",
+ "schema": "",
+ "columns": {
+ "mariadbId": {
+ "name": "mariadbId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "appName": {
+ "name": "appName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "databaseName": {
+ "name": "databaseName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "databaseUser": {
+ "name": "databaseUser",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "databasePassword": {
+ "name": "databasePassword",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "rootPassword": {
+ "name": "rootPassword",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "dockerImage": {
+ "name": "dockerImage",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "command": {
+ "name": "command",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "env": {
+ "name": "env",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "memoryReservation": {
+ "name": "memoryReservation",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "memoryLimit": {
+ "name": "memoryLimit",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cpuReservation": {
+ "name": "cpuReservation",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cpuLimit": {
+ "name": "cpuLimit",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "externalPort": {
+ "name": "externalPort",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "applicationStatus": {
+ "name": "applicationStatus",
+ "type": "applicationStatus",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'idle'"
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "projectId": {
+ "name": "projectId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "serverId": {
+ "name": "serverId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "mariadb_projectId_project_projectId_fk": {
+ "name": "mariadb_projectId_project_projectId_fk",
+ "tableFrom": "mariadb",
+ "tableTo": "project",
+ "columnsFrom": [
+ "projectId"
+ ],
+ "columnsTo": [
+ "projectId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "mariadb_serverId_server_serverId_fk": {
+ "name": "mariadb_serverId_server_serverId_fk",
+ "tableFrom": "mariadb",
+ "tableTo": "server",
+ "columnsFrom": [
+ "serverId"
+ ],
+ "columnsTo": [
+ "serverId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "mariadb_appName_unique": {
+ "name": "mariadb_appName_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "appName"
+ ]
+ }
+ }
+ },
+ "public.mongo": {
+ "name": "mongo",
+ "schema": "",
+ "columns": {
+ "mongoId": {
+ "name": "mongoId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "appName": {
+ "name": "appName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "databaseUser": {
+ "name": "databaseUser",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "databasePassword": {
+ "name": "databasePassword",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "dockerImage": {
+ "name": "dockerImage",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "command": {
+ "name": "command",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "env": {
+ "name": "env",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "memoryReservation": {
+ "name": "memoryReservation",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "memoryLimit": {
+ "name": "memoryLimit",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cpuReservation": {
+ "name": "cpuReservation",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cpuLimit": {
+ "name": "cpuLimit",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "externalPort": {
+ "name": "externalPort",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "applicationStatus": {
+ "name": "applicationStatus",
+ "type": "applicationStatus",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'idle'"
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "projectId": {
+ "name": "projectId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "serverId": {
+ "name": "serverId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "mongo_projectId_project_projectId_fk": {
+ "name": "mongo_projectId_project_projectId_fk",
+ "tableFrom": "mongo",
+ "tableTo": "project",
+ "columnsFrom": [
+ "projectId"
+ ],
+ "columnsTo": [
+ "projectId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "mongo_serverId_server_serverId_fk": {
+ "name": "mongo_serverId_server_serverId_fk",
+ "tableFrom": "mongo",
+ "tableTo": "server",
+ "columnsFrom": [
+ "serverId"
+ ],
+ "columnsTo": [
+ "serverId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "mongo_appName_unique": {
+ "name": "mongo_appName_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "appName"
+ ]
+ }
+ }
+ },
+ "public.mysql": {
+ "name": "mysql",
+ "schema": "",
+ "columns": {
+ "mysqlId": {
+ "name": "mysqlId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "appName": {
+ "name": "appName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "databaseName": {
+ "name": "databaseName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "databaseUser": {
+ "name": "databaseUser",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "databasePassword": {
+ "name": "databasePassword",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "rootPassword": {
+ "name": "rootPassword",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "dockerImage": {
+ "name": "dockerImage",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "command": {
+ "name": "command",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "env": {
+ "name": "env",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "memoryReservation": {
+ "name": "memoryReservation",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "memoryLimit": {
+ "name": "memoryLimit",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cpuReservation": {
+ "name": "cpuReservation",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cpuLimit": {
+ "name": "cpuLimit",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "externalPort": {
+ "name": "externalPort",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "applicationStatus": {
+ "name": "applicationStatus",
+ "type": "applicationStatus",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'idle'"
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "projectId": {
+ "name": "projectId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "serverId": {
+ "name": "serverId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "mysql_projectId_project_projectId_fk": {
+ "name": "mysql_projectId_project_projectId_fk",
+ "tableFrom": "mysql",
+ "tableTo": "project",
+ "columnsFrom": [
+ "projectId"
+ ],
+ "columnsTo": [
+ "projectId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "mysql_serverId_server_serverId_fk": {
+ "name": "mysql_serverId_server_serverId_fk",
+ "tableFrom": "mysql",
+ "tableTo": "server",
+ "columnsFrom": [
+ "serverId"
+ ],
+ "columnsTo": [
+ "serverId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "mysql_appName_unique": {
+ "name": "mysql_appName_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "appName"
+ ]
+ }
+ }
+ },
+ "public.backup": {
+ "name": "backup",
+ "schema": "",
+ "columns": {
+ "backupId": {
+ "name": "backupId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "schedule": {
+ "name": "schedule",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "database": {
+ "name": "database",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "prefix": {
+ "name": "prefix",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "destinationId": {
+ "name": "destinationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "databaseType": {
+ "name": "databaseType",
+ "type": "databaseType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "postgresId": {
+ "name": "postgresId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "mariadbId": {
+ "name": "mariadbId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "mysqlId": {
+ "name": "mysqlId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "mongoId": {
+ "name": "mongoId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "backup_destinationId_destination_destinationId_fk": {
+ "name": "backup_destinationId_destination_destinationId_fk",
+ "tableFrom": "backup",
+ "tableTo": "destination",
+ "columnsFrom": [
+ "destinationId"
+ ],
+ "columnsTo": [
+ "destinationId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "backup_postgresId_postgres_postgresId_fk": {
+ "name": "backup_postgresId_postgres_postgresId_fk",
+ "tableFrom": "backup",
+ "tableTo": "postgres",
+ "columnsFrom": [
+ "postgresId"
+ ],
+ "columnsTo": [
+ "postgresId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "backup_mariadbId_mariadb_mariadbId_fk": {
+ "name": "backup_mariadbId_mariadb_mariadbId_fk",
+ "tableFrom": "backup",
+ "tableTo": "mariadb",
+ "columnsFrom": [
+ "mariadbId"
+ ],
+ "columnsTo": [
+ "mariadbId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "backup_mysqlId_mysql_mysqlId_fk": {
+ "name": "backup_mysqlId_mysql_mysqlId_fk",
+ "tableFrom": "backup",
+ "tableTo": "mysql",
+ "columnsFrom": [
+ "mysqlId"
+ ],
+ "columnsTo": [
+ "mysqlId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "backup_mongoId_mongo_mongoId_fk": {
+ "name": "backup_mongoId_mongo_mongoId_fk",
+ "tableFrom": "backup",
+ "tableTo": "mongo",
+ "columnsFrom": [
+ "mongoId"
+ ],
+ "columnsTo": [
+ "mongoId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "public.destination": {
+ "name": "destination",
+ "schema": "",
+ "columns": {
+ "destinationId": {
+ "name": "destinationId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "accessKey": {
+ "name": "accessKey",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "secretAccessKey": {
+ "name": "secretAccessKey",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "bucket": {
+ "name": "bucket",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "region": {
+ "name": "region",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "endpoint": {
+ "name": "endpoint",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "adminId": {
+ "name": "adminId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "destination_adminId_admin_adminId_fk": {
+ "name": "destination_adminId_admin_adminId_fk",
+ "tableFrom": "destination",
+ "tableTo": "admin",
+ "columnsFrom": [
+ "adminId"
+ ],
+ "columnsTo": [
+ "adminId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "public.deployment": {
+ "name": "deployment",
+ "schema": "",
+ "columns": {
+ "deploymentId": {
+ "name": "deploymentId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "deploymentStatus",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'running'"
+ },
+ "logPath": {
+ "name": "logPath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "applicationId": {
+ "name": "applicationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "composeId": {
+ "name": "composeId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "serverId": {
+ "name": "serverId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "deployment_applicationId_application_applicationId_fk": {
+ "name": "deployment_applicationId_application_applicationId_fk",
+ "tableFrom": "deployment",
+ "tableTo": "application",
+ "columnsFrom": [
+ "applicationId"
+ ],
+ "columnsTo": [
+ "applicationId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "deployment_composeId_compose_composeId_fk": {
+ "name": "deployment_composeId_compose_composeId_fk",
+ "tableFrom": "deployment",
+ "tableTo": "compose",
+ "columnsFrom": [
+ "composeId"
+ ],
+ "columnsTo": [
+ "composeId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "deployment_serverId_server_serverId_fk": {
+ "name": "deployment_serverId_server_serverId_fk",
+ "tableFrom": "deployment",
+ "tableTo": "server",
+ "columnsFrom": [
+ "serverId"
+ ],
+ "columnsTo": [
+ "serverId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "public.mount": {
+ "name": "mount",
+ "schema": "",
+ "columns": {
+ "mountId": {
+ "name": "mountId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "type": {
+ "name": "type",
+ "type": "mountType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "hostPath": {
+ "name": "hostPath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "volumeName": {
+ "name": "volumeName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "filePath": {
+ "name": "filePath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "content": {
+ "name": "content",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "serviceType": {
+ "name": "serviceType",
+ "type": "serviceType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'application'"
+ },
+ "mountPath": {
+ "name": "mountPath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "applicationId": {
+ "name": "applicationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "postgresId": {
+ "name": "postgresId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "mariadbId": {
+ "name": "mariadbId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "mongoId": {
+ "name": "mongoId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "mysqlId": {
+ "name": "mysqlId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "redisId": {
+ "name": "redisId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "composeId": {
+ "name": "composeId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "mount_applicationId_application_applicationId_fk": {
+ "name": "mount_applicationId_application_applicationId_fk",
+ "tableFrom": "mount",
+ "tableTo": "application",
+ "columnsFrom": [
+ "applicationId"
+ ],
+ "columnsTo": [
+ "applicationId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "mount_postgresId_postgres_postgresId_fk": {
+ "name": "mount_postgresId_postgres_postgresId_fk",
+ "tableFrom": "mount",
+ "tableTo": "postgres",
+ "columnsFrom": [
+ "postgresId"
+ ],
+ "columnsTo": [
+ "postgresId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "mount_mariadbId_mariadb_mariadbId_fk": {
+ "name": "mount_mariadbId_mariadb_mariadbId_fk",
+ "tableFrom": "mount",
+ "tableTo": "mariadb",
+ "columnsFrom": [
+ "mariadbId"
+ ],
+ "columnsTo": [
+ "mariadbId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "mount_mongoId_mongo_mongoId_fk": {
+ "name": "mount_mongoId_mongo_mongoId_fk",
+ "tableFrom": "mount",
+ "tableTo": "mongo",
+ "columnsFrom": [
+ "mongoId"
+ ],
+ "columnsTo": [
+ "mongoId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "mount_mysqlId_mysql_mysqlId_fk": {
+ "name": "mount_mysqlId_mysql_mysqlId_fk",
+ "tableFrom": "mount",
+ "tableTo": "mysql",
+ "columnsFrom": [
+ "mysqlId"
+ ],
+ "columnsTo": [
+ "mysqlId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "mount_redisId_redis_redisId_fk": {
+ "name": "mount_redisId_redis_redisId_fk",
+ "tableFrom": "mount",
+ "tableTo": "redis",
+ "columnsFrom": [
+ "redisId"
+ ],
+ "columnsTo": [
+ "redisId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "mount_composeId_compose_composeId_fk": {
+ "name": "mount_composeId_compose_composeId_fk",
+ "tableFrom": "mount",
+ "tableTo": "compose",
+ "columnsFrom": [
+ "composeId"
+ ],
+ "columnsTo": [
+ "composeId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "public.certificate": {
+ "name": "certificate",
+ "schema": "",
+ "columns": {
+ "certificateId": {
+ "name": "certificateId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "certificateData": {
+ "name": "certificateData",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "privateKey": {
+ "name": "privateKey",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "certificatePath": {
+ "name": "certificatePath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "autoRenew": {
+ "name": "autoRenew",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "certificate_certificatePath_unique": {
+ "name": "certificate_certificatePath_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "certificatePath"
+ ]
+ }
+ }
+ },
+ "public.session": {
+ "name": "session",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "session_user_id_auth_id_fk": {
+ "name": "session_user_id_auth_id_fk",
+ "tableFrom": "session",
+ "tableTo": "auth",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "public.redirect": {
+ "name": "redirect",
+ "schema": "",
+ "columns": {
+ "redirectId": {
+ "name": "redirectId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "regex": {
+ "name": "regex",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "replacement": {
+ "name": "replacement",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "permanent": {
+ "name": "permanent",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "uniqueConfigKey": {
+ "name": "uniqueConfigKey",
+ "type": "serial",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "applicationId": {
+ "name": "applicationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "redirect_applicationId_application_applicationId_fk": {
+ "name": "redirect_applicationId_application_applicationId_fk",
+ "tableFrom": "redirect",
+ "tableTo": "application",
+ "columnsFrom": [
+ "applicationId"
+ ],
+ "columnsTo": [
+ "applicationId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "public.security": {
+ "name": "security",
+ "schema": "",
+ "columns": {
+ "securityId": {
+ "name": "securityId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "applicationId": {
+ "name": "applicationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "security_applicationId_application_applicationId_fk": {
+ "name": "security_applicationId_application_applicationId_fk",
+ "tableFrom": "security",
+ "tableTo": "application",
+ "columnsFrom": [
+ "applicationId"
+ ],
+ "columnsTo": [
+ "applicationId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "security_username_applicationId_unique": {
+ "name": "security_username_applicationId_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "username",
+ "applicationId"
+ ]
+ }
+ }
+ },
+ "public.port": {
+ "name": "port",
+ "schema": "",
+ "columns": {
+ "portId": {
+ "name": "portId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "publishedPort": {
+ "name": "publishedPort",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "targetPort": {
+ "name": "targetPort",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "protocol": {
+ "name": "protocol",
+ "type": "protocolType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "applicationId": {
+ "name": "applicationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "port_applicationId_application_applicationId_fk": {
+ "name": "port_applicationId_application_applicationId_fk",
+ "tableFrom": "port",
+ "tableTo": "application",
+ "columnsFrom": [
+ "applicationId"
+ ],
+ "columnsTo": [
+ "applicationId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "public.redis": {
+ "name": "redis",
+ "schema": "",
+ "columns": {
+ "redisId": {
+ "name": "redisId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "appName": {
+ "name": "appName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "dockerImage": {
+ "name": "dockerImage",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "command": {
+ "name": "command",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "env": {
+ "name": "env",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "memoryReservation": {
+ "name": "memoryReservation",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "memoryLimit": {
+ "name": "memoryLimit",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cpuReservation": {
+ "name": "cpuReservation",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cpuLimit": {
+ "name": "cpuLimit",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "externalPort": {
+ "name": "externalPort",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "applicationStatus": {
+ "name": "applicationStatus",
+ "type": "applicationStatus",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'idle'"
+ },
+ "projectId": {
+ "name": "projectId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "serverId": {
+ "name": "serverId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "redis_projectId_project_projectId_fk": {
+ "name": "redis_projectId_project_projectId_fk",
+ "tableFrom": "redis",
+ "tableTo": "project",
+ "columnsFrom": [
+ "projectId"
+ ],
+ "columnsTo": [
+ "projectId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "redis_serverId_server_serverId_fk": {
+ "name": "redis_serverId_server_serverId_fk",
+ "tableFrom": "redis",
+ "tableTo": "server",
+ "columnsFrom": [
+ "serverId"
+ ],
+ "columnsTo": [
+ "serverId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "redis_appName_unique": {
+ "name": "redis_appName_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "appName"
+ ]
+ }
+ }
+ },
+ "public.compose": {
+ "name": "compose",
+ "schema": "",
+ "columns": {
+ "composeId": {
+ "name": "composeId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "appName": {
+ "name": "appName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "env": {
+ "name": "env",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "composeFile": {
+ "name": "composeFile",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "refreshToken": {
+ "name": "refreshToken",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sourceType": {
+ "name": "sourceType",
+ "type": "sourceTypeCompose",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'github'"
+ },
+ "composeType": {
+ "name": "composeType",
+ "type": "composeType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'docker-compose'"
+ },
+ "repository": {
+ "name": "repository",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "owner": {
+ "name": "owner",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "branch": {
+ "name": "branch",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "autoDeploy": {
+ "name": "autoDeploy",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitlabProjectId": {
+ "name": "gitlabProjectId",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitlabRepository": {
+ "name": "gitlabRepository",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitlabOwner": {
+ "name": "gitlabOwner",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitlabBranch": {
+ "name": "gitlabBranch",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitlabPathNamespace": {
+ "name": "gitlabPathNamespace",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bitbucketRepository": {
+ "name": "bitbucketRepository",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bitbucketOwner": {
+ "name": "bitbucketOwner",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bitbucketBranch": {
+ "name": "bitbucketBranch",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "customGitUrl": {
+ "name": "customGitUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "customGitBranch": {
+ "name": "customGitBranch",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "customGitSSHKeyId": {
+ "name": "customGitSSHKeyId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "command": {
+ "name": "command",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "composePath": {
+ "name": "composePath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'./docker-compose.yml'"
+ },
+ "suffix": {
+ "name": "suffix",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "randomize": {
+ "name": "randomize",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "composeStatus": {
+ "name": "composeStatus",
+ "type": "applicationStatus",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'idle'"
+ },
+ "projectId": {
+ "name": "projectId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "githubId": {
+ "name": "githubId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitlabId": {
+ "name": "gitlabId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bitbucketId": {
+ "name": "bitbucketId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "serverId": {
+ "name": "serverId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "compose_customGitSSHKeyId_ssh-key_sshKeyId_fk": {
+ "name": "compose_customGitSSHKeyId_ssh-key_sshKeyId_fk",
+ "tableFrom": "compose",
+ "tableTo": "ssh-key",
+ "columnsFrom": [
+ "customGitSSHKeyId"
+ ],
+ "columnsTo": [
+ "sshKeyId"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "compose_projectId_project_projectId_fk": {
+ "name": "compose_projectId_project_projectId_fk",
+ "tableFrom": "compose",
+ "tableTo": "project",
+ "columnsFrom": [
+ "projectId"
+ ],
+ "columnsTo": [
+ "projectId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "compose_githubId_github_githubId_fk": {
+ "name": "compose_githubId_github_githubId_fk",
+ "tableFrom": "compose",
+ "tableTo": "github",
+ "columnsFrom": [
+ "githubId"
+ ],
+ "columnsTo": [
+ "githubId"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "compose_gitlabId_gitlab_gitlabId_fk": {
+ "name": "compose_gitlabId_gitlab_gitlabId_fk",
+ "tableFrom": "compose",
+ "tableTo": "gitlab",
+ "columnsFrom": [
+ "gitlabId"
+ ],
+ "columnsTo": [
+ "gitlabId"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "compose_bitbucketId_bitbucket_bitbucketId_fk": {
+ "name": "compose_bitbucketId_bitbucket_bitbucketId_fk",
+ "tableFrom": "compose",
+ "tableTo": "bitbucket",
+ "columnsFrom": [
+ "bitbucketId"
+ ],
+ "columnsTo": [
+ "bitbucketId"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "compose_serverId_server_serverId_fk": {
+ "name": "compose_serverId_server_serverId_fk",
+ "tableFrom": "compose",
+ "tableTo": "server",
+ "columnsFrom": [
+ "serverId"
+ ],
+ "columnsTo": [
+ "serverId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "public.registry": {
+ "name": "registry",
+ "schema": "",
+ "columns": {
+ "registryId": {
+ "name": "registryId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "registryName": {
+ "name": "registryName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "imagePrefix": {
+ "name": "imagePrefix",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "registryUrl": {
+ "name": "registryUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "selfHosted": {
+ "name": "selfHosted",
+ "type": "RegistryType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'cloud'"
+ },
+ "adminId": {
+ "name": "adminId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "registry_adminId_admin_adminId_fk": {
+ "name": "registry_adminId_admin_adminId_fk",
+ "tableFrom": "registry",
+ "tableTo": "admin",
+ "columnsFrom": [
+ "adminId"
+ ],
+ "columnsTo": [
+ "adminId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "public.discord": {
+ "name": "discord",
+ "schema": "",
+ "columns": {
+ "discordId": {
+ "name": "discordId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "webhookUrl": {
+ "name": "webhookUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "public.email": {
+ "name": "email",
+ "schema": "",
+ "columns": {
+ "emailId": {
+ "name": "emailId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "smtpServer": {
+ "name": "smtpServer",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "smtpPort": {
+ "name": "smtpPort",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "fromAddress": {
+ "name": "fromAddress",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "toAddress": {
+ "name": "toAddress",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "public.notification": {
+ "name": "notification",
+ "schema": "",
+ "columns": {
+ "notificationId": {
+ "name": "notificationId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "appDeploy": {
+ "name": "appDeploy",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "appBuildError": {
+ "name": "appBuildError",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "databaseBackup": {
+ "name": "databaseBackup",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "dokployRestart": {
+ "name": "dokployRestart",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "dockerCleanup": {
+ "name": "dockerCleanup",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "notificationType": {
+ "name": "notificationType",
+ "type": "notificationType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "slackId": {
+ "name": "slackId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "telegramId": {
+ "name": "telegramId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "discordId": {
+ "name": "discordId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "emailId": {
+ "name": "emailId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "notification_slackId_slack_slackId_fk": {
+ "name": "notification_slackId_slack_slackId_fk",
+ "tableFrom": "notification",
+ "tableTo": "slack",
+ "columnsFrom": [
+ "slackId"
+ ],
+ "columnsTo": [
+ "slackId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "notification_telegramId_telegram_telegramId_fk": {
+ "name": "notification_telegramId_telegram_telegramId_fk",
+ "tableFrom": "notification",
+ "tableTo": "telegram",
+ "columnsFrom": [
+ "telegramId"
+ ],
+ "columnsTo": [
+ "telegramId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "notification_discordId_discord_discordId_fk": {
+ "name": "notification_discordId_discord_discordId_fk",
+ "tableFrom": "notification",
+ "tableTo": "discord",
+ "columnsFrom": [
+ "discordId"
+ ],
+ "columnsTo": [
+ "discordId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "notification_emailId_email_emailId_fk": {
+ "name": "notification_emailId_email_emailId_fk",
+ "tableFrom": "notification",
+ "tableTo": "email",
+ "columnsFrom": [
+ "emailId"
+ ],
+ "columnsTo": [
+ "emailId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "public.slack": {
+ "name": "slack",
+ "schema": "",
+ "columns": {
+ "slackId": {
+ "name": "slackId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "webhookUrl": {
+ "name": "webhookUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "channel": {
+ "name": "channel",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "public.telegram": {
+ "name": "telegram",
+ "schema": "",
+ "columns": {
+ "telegramId": {
+ "name": "telegramId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "botToken": {
+ "name": "botToken",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "chatId": {
+ "name": "chatId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "public.ssh-key": {
+ "name": "ssh-key",
+ "schema": "",
+ "columns": {
+ "sshKeyId": {
+ "name": "sshKeyId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "publicKey": {
+ "name": "publicKey",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "lastUsedAt": {
+ "name": "lastUsedAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "public.git_provider": {
+ "name": "git_provider",
+ "schema": "",
+ "columns": {
+ "gitProviderId": {
+ "name": "gitProviderId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "providerType": {
+ "name": "providerType",
+ "type": "gitProviderType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'github'"
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "authId": {
+ "name": "authId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "git_provider_authId_auth_id_fk": {
+ "name": "git_provider_authId_auth_id_fk",
+ "tableFrom": "git_provider",
+ "tableTo": "auth",
+ "columnsFrom": [
+ "authId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "public.bitbucket": {
+ "name": "bitbucket",
+ "schema": "",
+ "columns": {
+ "bitbucketId": {
+ "name": "bitbucketId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "bitbucketUsername": {
+ "name": "bitbucketUsername",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "appPassword": {
+ "name": "appPassword",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bitbucketWorkspaceName": {
+ "name": "bitbucketWorkspaceName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitProviderId": {
+ "name": "gitProviderId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "bitbucket_gitProviderId_git_provider_gitProviderId_fk": {
+ "name": "bitbucket_gitProviderId_git_provider_gitProviderId_fk",
+ "tableFrom": "bitbucket",
+ "tableTo": "git_provider",
+ "columnsFrom": [
+ "gitProviderId"
+ ],
+ "columnsTo": [
+ "gitProviderId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "public.github": {
+ "name": "github",
+ "schema": "",
+ "columns": {
+ "githubId": {
+ "name": "githubId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "githubAppName": {
+ "name": "githubAppName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "githubAppId": {
+ "name": "githubAppId",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "githubClientId": {
+ "name": "githubClientId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "githubClientSecret": {
+ "name": "githubClientSecret",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "githubInstallationId": {
+ "name": "githubInstallationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "githubPrivateKey": {
+ "name": "githubPrivateKey",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "githubWebhookSecret": {
+ "name": "githubWebhookSecret",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitProviderId": {
+ "name": "gitProviderId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "github_gitProviderId_git_provider_gitProviderId_fk": {
+ "name": "github_gitProviderId_git_provider_gitProviderId_fk",
+ "tableFrom": "github",
+ "tableTo": "git_provider",
+ "columnsFrom": [
+ "gitProviderId"
+ ],
+ "columnsTo": [
+ "gitProviderId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "public.gitlab": {
+ "name": "gitlab",
+ "schema": "",
+ "columns": {
+ "gitlabId": {
+ "name": "gitlabId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "application_id": {
+ "name": "application_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "redirect_uri": {
+ "name": "redirect_uri",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "secret": {
+ "name": "secret",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "access_token": {
+ "name": "access_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refresh_token": {
+ "name": "refresh_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "group_name": {
+ "name": "group_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitProviderId": {
+ "name": "gitProviderId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "gitlab_gitProviderId_git_provider_gitProviderId_fk": {
+ "name": "gitlab_gitProviderId_git_provider_gitProviderId_fk",
+ "tableFrom": "gitlab",
+ "tableTo": "git_provider",
+ "columnsFrom": [
+ "gitProviderId"
+ ],
+ "columnsTo": [
+ "gitProviderId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "public.server": {
+ "name": "server",
+ "schema": "",
+ "columns": {
+ "serverId": {
+ "name": "serverId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ipAddress": {
+ "name": "ipAddress",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "port": {
+ "name": "port",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'root'"
+ },
+ "appName": {
+ "name": "appName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "enableDockerCleanup": {
+ "name": "enableDockerCleanup",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "adminId": {
+ "name": "adminId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "sshKeyId": {
+ "name": "sshKeyId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "server_adminId_admin_adminId_fk": {
+ "name": "server_adminId_admin_adminId_fk",
+ "tableFrom": "server",
+ "tableTo": "admin",
+ "columnsFrom": [
+ "adminId"
+ ],
+ "columnsTo": [
+ "adminId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "server_sshKeyId_ssh-key_sshKeyId_fk": {
+ "name": "server_sshKeyId_ssh-key_sshKeyId_fk",
+ "tableFrom": "server",
+ "tableTo": "ssh-key",
+ "columnsFrom": [
+ "sshKeyId"
+ ],
+ "columnsTo": [
+ "sshKeyId"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ }
+ },
+ "enums": {
+ "public.buildType": {
+ "name": "buildType",
+ "schema": "public",
+ "values": [
+ "dockerfile",
+ "heroku_buildpacks",
+ "paketo_buildpacks",
+ "nixpacks",
+ "static"
+ ]
+ },
+ "public.sourceType": {
+ "name": "sourceType",
+ "schema": "public",
+ "values": [
+ "docker",
+ "git",
+ "github",
+ "gitlab",
+ "bitbucket",
+ "drop"
+ ]
+ },
+ "public.Roles": {
+ "name": "Roles",
+ "schema": "public",
+ "values": [
+ "admin",
+ "user"
+ ]
+ },
+ "public.domainType": {
+ "name": "domainType",
+ "schema": "public",
+ "values": [
+ "compose",
+ "application"
+ ]
+ },
+ "public.databaseType": {
+ "name": "databaseType",
+ "schema": "public",
+ "values": [
+ "postgres",
+ "mariadb",
+ "mysql",
+ "mongo"
+ ]
+ },
+ "public.deploymentStatus": {
+ "name": "deploymentStatus",
+ "schema": "public",
+ "values": [
+ "running",
+ "done",
+ "error"
+ ]
+ },
+ "public.mountType": {
+ "name": "mountType",
+ "schema": "public",
+ "values": [
+ "bind",
+ "volume",
+ "file"
+ ]
+ },
+ "public.serviceType": {
+ "name": "serviceType",
+ "schema": "public",
+ "values": [
+ "application",
+ "postgres",
+ "mysql",
+ "mariadb",
+ "mongo",
+ "redis",
+ "compose"
+ ]
+ },
+ "public.protocolType": {
+ "name": "protocolType",
+ "schema": "public",
+ "values": [
+ "tcp",
+ "udp"
+ ]
+ },
+ "public.applicationStatus": {
+ "name": "applicationStatus",
+ "schema": "public",
+ "values": [
+ "idle",
+ "running",
+ "done",
+ "error"
+ ]
+ },
+ "public.certificateType": {
+ "name": "certificateType",
+ "schema": "public",
+ "values": [
+ "letsencrypt",
+ "none"
+ ]
+ },
+ "public.composeType": {
+ "name": "composeType",
+ "schema": "public",
+ "values": [
+ "docker-compose",
+ "stack"
+ ]
+ },
+ "public.sourceTypeCompose": {
+ "name": "sourceTypeCompose",
+ "schema": "public",
+ "values": [
+ "git",
+ "github",
+ "gitlab",
+ "bitbucket",
+ "raw"
+ ]
+ },
+ "public.RegistryType": {
+ "name": "RegistryType",
+ "schema": "public",
+ "values": [
+ "selfHosted",
+ "cloud"
+ ]
+ },
+ "public.notificationType": {
+ "name": "notificationType",
+ "schema": "public",
+ "values": [
+ "slack",
+ "telegram",
+ "discord",
+ "email"
+ ]
+ },
+ "public.gitProviderType": {
+ "name": "gitProviderType",
+ "schema": "public",
+ "values": [
+ "github",
+ "gitlab",
+ "bitbucket"
+ ]
+ }
+ },
+ "schemas": {},
+ "_meta": {
+ "columns": {},
+ "schemas": {},
+ "tables": {}
+ }
+}
\ No newline at end of file
diff --git a/apps/dokploy/drizzle/meta/_journal.json b/apps/dokploy/drizzle/meta/_journal.json
index 2163e5312..5a84e0b1b 100644
--- a/apps/dokploy/drizzle/meta/_journal.json
+++ b/apps/dokploy/drizzle/meta/_journal.json
@@ -260,6 +260,20 @@
"when": 1725519351871,
"tag": "0036_tired_ronan",
"breakpoints": true
+ },
+ {
+ "idx": 37,
+ "version": "6",
+ "when": 1726988289562,
+ "tag": "0037_legal_namor",
+ "breakpoints": true
+ },
+ {
+ "idx": 38,
+ "version": "6",
+ "when": 1727942090102,
+ "tag": "0038_rapid_landau",
+ "breakpoints": true
}
]
}
\ No newline at end of file
diff --git a/apps/dokploy/package.json b/apps/dokploy/package.json
index 3c17a7e94..b68317489 100644
--- a/apps/dokploy/package.json
+++ b/apps/dokploy/package.json
@@ -1,6 +1,6 @@
{
"name": "dokploy",
- "version": "v0.8.3",
+ "version": "v0.9.4",
"private": true,
"license": "Apache-2.0",
"type": "module",
@@ -11,7 +11,7 @@
"build-next": "next build",
"setup": "tsx -r dotenv/config setup.ts && sleep 5 && pnpm run migration:run",
"reset-password": "node dist/reset-password.mjs",
- "dev": "tsx watch -r dotenv/config ./server/server.ts --project tsconfig.server.json ",
+ "dev": "tsx -r dotenv/config ./server/server.ts --project tsconfig.server.json ",
"studio": "drizzle-kit studio --config ./server/db/drizzle.config.ts",
"migration:generate": "drizzle-kit generate --config ./server/db/drizzle.config.ts",
"migration:run": "tsx -r dotenv/config migration.ts",
@@ -35,7 +35,6 @@
},
"dependencies": {
"rotating-file-stream": "3.2.3",
- "@aws-sdk/client-s3": "3.515.0",
"@codemirror/lang-json": "^6.0.1",
"@codemirror/lang-yaml": "^6.1.1",
"@codemirror/language": "^6.10.1",
@@ -130,7 +129,8 @@
"zod": "^3.23.4",
"zod-form-data": "^2.0.2",
"@radix-ui/react-primitive": "2.0.0",
- "@radix-ui/react-use-controllable-state": "1.1.0"
+ "@radix-ui/react-use-controllable-state": "1.1.0",
+ "ssh2": "1.15.0"
},
"devDependencies": {
"@biomejs/biome": "1.8.3",
@@ -167,7 +167,8 @@
"typescript": "^5.4.2",
"vite-tsconfig-paths": "4.3.2",
"vitest": "^1.6.0",
- "xterm-readline": "1.1.1"
+ "xterm-readline": "1.1.1",
+ "@types/ssh2": "1.15.1"
},
"ct3aMetadata": {
"initVersion": "7.25.2"
diff --git a/apps/dokploy/pages/api/deploy/[refreshToken].ts b/apps/dokploy/pages/api/deploy/[refreshToken].ts
index 7b4e0a757..829d4d342 100644
--- a/apps/dokploy/pages/api/deploy/[refreshToken].ts
+++ b/apps/dokploy/pages/api/deploy/[refreshToken].ts
@@ -87,6 +87,7 @@ export default async function handler(
descriptionLog: `Hash: ${deploymentHash}`,
type: "deploy",
applicationType: "application",
+ server: !!application.serverId,
};
await myQueue.add(
"deployments",
diff --git a/apps/dokploy/pages/api/deploy/compose/[refreshToken].ts b/apps/dokploy/pages/api/deploy/compose/[refreshToken].ts
index 65ab80dff..8f24d2c09 100644
--- a/apps/dokploy/pages/api/deploy/compose/[refreshToken].ts
+++ b/apps/dokploy/pages/api/deploy/compose/[refreshToken].ts
@@ -63,6 +63,7 @@ export default async function handler(
type: "deploy",
applicationType: "compose",
descriptionLog: `Hash: ${deploymentHash}`,
+ server: !!composeResult.serverId,
};
await myQueue.add(
"deployments",
diff --git a/apps/dokploy/pages/api/deploy/github.ts b/apps/dokploy/pages/api/deploy/github.ts
index f8f9d59c4..032d6b7e3 100644
--- a/apps/dokploy/pages/api/deploy/github.ts
+++ b/apps/dokploy/pages/api/deploy/github.ts
@@ -86,6 +86,7 @@ export default async function handler(
descriptionLog: `Hash: ${deploymentHash}`,
type: "deploy",
applicationType: "application",
+ server: !!app.serverId,
};
await myQueue.add(
"deployments",
diff --git a/apps/dokploy/pages/api/teapot.ts b/apps/dokploy/pages/api/teapot.ts
new file mode 100644
index 000000000..7485fa79f
--- /dev/null
+++ b/apps/dokploy/pages/api/teapot.ts
@@ -0,0 +1,18 @@
+import type { NextRequest } from "next/server";
+import { renderToString } from "react-dom/server";
+import Page418 from "../hola"; // Importa la página 418
+
+export const GET = async (req: NextRequest) => {
+ // Renderiza el componente de la página 418 como HTML
+ const htmlContent = renderToString(Page418());
+
+ // Devuelve la respuesta con el código de estado HTTP 418
+ return new Response(htmlContent, {
+ headers: {
+ "Content-Type": "text/html",
+ },
+ status: 418,
+ });
+};
+
+export default GET;
diff --git a/apps/dokploy/pages/dashboard/project/[projectId]/services/application/[applicationId].tsx b/apps/dokploy/pages/dashboard/project/[projectId]/services/application/[applicationId].tsx
index 942332cd7..5c54b7c43 100644
--- a/apps/dokploy/pages/dashboard/project/[projectId]/services/application/[applicationId].tsx
+++ b/apps/dokploy/pages/dashboard/project/[projectId]/services/application/[applicationId].tsx
@@ -16,12 +16,14 @@ import { UpdateApplication } from "@/components/dashboard/application/update-app
import { DockerMonitoring } from "@/components/dashboard/monitoring/docker/show";
import { ProjectLayout } from "@/components/layouts/project-layout";
import { StatusTooltip } from "@/components/shared/status-tooltip";
+import { Badge } from "@/components/ui/badge";
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
} from "@/components/ui/breadcrumb";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
+import { cn } from "@/lib/utils";
import { appRouter } from "@/server/api/root";
import { validateRequest } from "@/server/auth/auth";
import { api } from "@/utils/api";
@@ -98,6 +100,9 @@ const Service = (
{data?.appName}
+
+ {data?.server?.name || "Dokploy Server"}
+
{data?.description && (
@@ -125,10 +130,17 @@ const Service = (
}}
>
-
+
General
Environment
- Monitoring
+ {!data?.serverId && (
+ Monitoring
+ )}
Logs
Deployments
Domains
@@ -152,14 +164,20 @@ const Service = (
-
-
-
-
-
+ {!data?.serverId && (
+
+
+
+
+
+ )}
+
-
+
diff --git a/apps/dokploy/pages/dashboard/project/[projectId]/services/compose/[composeId].tsx b/apps/dokploy/pages/dashboard/project/[projectId]/services/compose/[composeId].tsx
index 225517e56..e393343f4 100644
--- a/apps/dokploy/pages/dashboard/project/[projectId]/services/compose/[composeId].tsx
+++ b/apps/dokploy/pages/dashboard/project/[projectId]/services/compose/[composeId].tsx
@@ -10,12 +10,14 @@ import { ShowMonitoringCompose } from "@/components/dashboard/compose/monitoring
import { UpdateCompose } from "@/components/dashboard/compose/update-compose";
import { ProjectLayout } from "@/components/layouts/project-layout";
import { StatusTooltip } from "@/components/shared/status-tooltip";
+import { Badge } from "@/components/ui/badge";
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
} from "@/components/ui/breadcrumb";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
+import { cn } from "@/lib/utils";
import { appRouter } from "@/server/api/root";
import { validateRequest } from "@/server/auth/auth";
import { api } from "@/utils/api";
@@ -92,13 +94,16 @@ const Service = (
{data?.appName}
-
+
+ {data?.server?.name || "Dokploy Server"}
+
{data?.description && (
{data?.description}
)}
+
@@ -119,10 +124,23 @@ const Service = (
}}
>
-
+
General
- Environment
- Monitoring
+ {data?.composeType === "docker-compose" && (
+ Environment
+ )}
+ {!data?.serverId && (
+ Monitoring
+ )}
Logs
Deployments
Domains
@@ -147,19 +165,22 @@ const Service = (
-
-
-
-
-
-
+ {!data?.serverId && (
+
+
+
+
+
+ )}
diff --git a/apps/dokploy/pages/dashboard/project/[projectId]/services/mariadb/[mariadbId].tsx b/apps/dokploy/pages/dashboard/project/[projectId]/services/mariadb/[mariadbId].tsx
index 8d43e4cf3..837153944 100644
--- a/apps/dokploy/pages/dashboard/project/[projectId]/services/mariadb/[mariadbId].tsx
+++ b/apps/dokploy/pages/dashboard/project/[projectId]/services/mariadb/[mariadbId].tsx
@@ -9,15 +9,16 @@ import { ShowInternalMariadbCredentials } from "@/components/dashboard/mariadb/g
import { UpdateMariadb } from "@/components/dashboard/mariadb/update-mariadb";
import { DockerMonitoring } from "@/components/dashboard/monitoring/docker/show";
import { MariadbIcon } from "@/components/icons/data-tools-icons";
-import { DashboardLayout } from "@/components/layouts/dashboard-layout";
import { ProjectLayout } from "@/components/layouts/project-layout";
import { StatusTooltip } from "@/components/shared/status-tooltip";
+import { Badge } from "@/components/ui/badge";
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
} from "@/components/ui/breadcrumb";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
+import { cn } from "@/lib/utils";
import { appRouter } from "@/server/api/root";
import { validateRequest } from "@/server/auth/auth";
import { api } from "@/utils/api";
@@ -81,7 +82,9 @@ const Mariadb = (
{data?.appName}
-
+
+ {data?.server?.name || "Dokploy Server"}
+
{data?.description && (
{data?.description}
@@ -108,10 +111,17 @@ const Mariadb = (
}}
>
-
+
General
Environment
- Monitoring
+ {!data?.serverId && (
+ Monitoring
+ )}
Backups
Logs
Advanced
@@ -136,14 +146,19 @@ const Mariadb = (
-
-
-
-
-
+ {!data?.serverId && (
+
+
+
+
+
+ )}
-
+
diff --git a/apps/dokploy/pages/dashboard/project/[projectId]/services/mongo/[mongoId].tsx b/apps/dokploy/pages/dashboard/project/[projectId]/services/mongo/[mongoId].tsx
index 409b9bd6d..cc6753fbe 100644
--- a/apps/dokploy/pages/dashboard/project/[projectId]/services/mongo/[mongoId].tsx
+++ b/apps/dokploy/pages/dashboard/project/[projectId]/services/mongo/[mongoId].tsx
@@ -9,15 +9,16 @@ import { ShowInternalMongoCredentials } from "@/components/dashboard/mongo/gener
import { UpdateMongo } from "@/components/dashboard/mongo/update-mongo";
import { DockerMonitoring } from "@/components/dashboard/monitoring/docker/show";
import { MongodbIcon } from "@/components/icons/data-tools-icons";
-import { DashboardLayout } from "@/components/layouts/dashboard-layout";
import { ProjectLayout } from "@/components/layouts/project-layout";
import { StatusTooltip } from "@/components/shared/status-tooltip";
+import { Badge } from "@/components/ui/badge";
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
} from "@/components/ui/breadcrumb";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
+import { cn } from "@/lib/utils";
import { appRouter } from "@/server/api/root";
import { validateRequest } from "@/server/auth/auth";
import { api } from "@/utils/api";
@@ -82,7 +83,9 @@ const Mongo = (
{data?.appName}
-
+
+ {data?.server?.name || "Dokploy Server"}
+
{data?.description && (
{data?.description}
@@ -109,10 +112,17 @@ const Mongo = (
}}
>
-
+
General
Environment
- Monitoring
+ {!data?.serverId && (
+ Monitoring
+ )}
Backups
Logs
Advanced
@@ -138,14 +148,19 @@ const Mongo = (
-
-
-
-
-
+ {!data?.serverId && (
+
+
+
+
+
+ )}
-
+
diff --git a/apps/dokploy/pages/dashboard/project/[projectId]/services/mysql/[mysqlId].tsx b/apps/dokploy/pages/dashboard/project/[projectId]/services/mysql/[mysqlId].tsx
index 21f4036e8..5a119f816 100644
--- a/apps/dokploy/pages/dashboard/project/[projectId]/services/mysql/[mysqlId].tsx
+++ b/apps/dokploy/pages/dashboard/project/[projectId]/services/mysql/[mysqlId].tsx
@@ -9,15 +9,16 @@ import { ShowGeneralMysql } from "@/components/dashboard/mysql/general/show-gene
import { ShowInternalMysqlCredentials } from "@/components/dashboard/mysql/general/show-internal-mysql-credentials";
import { UpdateMysql } from "@/components/dashboard/mysql/update-mysql";
import { MysqlIcon } from "@/components/icons/data-tools-icons";
-import { DashboardLayout } from "@/components/layouts/dashboard-layout";
import { ProjectLayout } from "@/components/layouts/project-layout";
import { StatusTooltip } from "@/components/shared/status-tooltip";
+import { Badge } from "@/components/ui/badge";
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
} from "@/components/ui/breadcrumb";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
+import { cn } from "@/lib/utils";
import { appRouter } from "@/server/api/root";
import { validateRequest } from "@/server/auth/auth";
import { api } from "@/utils/api";
@@ -80,7 +81,9 @@ const MySql = (
{data?.appName}
-
+
+ {data?.server?.name || "Dokploy Server"}
+
{data?.description && (
{data?.description}
@@ -108,10 +111,17 @@ const MySql = (
}}
>
-
+
General
Environment
- Monitoring
+ {!data?.serverId && (
+ Monitoring
+ )}
Backups
Logs
Advanced
@@ -137,14 +147,19 @@ const MySql = (
-
-
-
-
-
+ {!data?.serverId && (
+
+
+
+
+
+ )}
-
+
diff --git a/apps/dokploy/pages/dashboard/project/[projectId]/services/postgres/[postgresId].tsx b/apps/dokploy/pages/dashboard/project/[projectId]/services/postgres/[postgresId].tsx
index e0342ee44..02c5ee094 100644
--- a/apps/dokploy/pages/dashboard/project/[projectId]/services/postgres/[postgresId].tsx
+++ b/apps/dokploy/pages/dashboard/project/[projectId]/services/postgres/[postgresId].tsx
@@ -9,15 +9,16 @@ import { ShowGeneralPostgres } from "@/components/dashboard/postgres/general/sho
import { ShowInternalPostgresCredentials } from "@/components/dashboard/postgres/general/show-internal-postgres-credentials";
import { UpdatePostgres } from "@/components/dashboard/postgres/update-postgres";
import { PostgresqlIcon } from "@/components/icons/data-tools-icons";
-import { DashboardLayout } from "@/components/layouts/dashboard-layout";
import { ProjectLayout } from "@/components/layouts/project-layout";
import { StatusTooltip } from "@/components/shared/status-tooltip";
+import { Badge } from "@/components/ui/badge";
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
} from "@/components/ui/breadcrumb";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
+import { cn } from "@/lib/utils";
import { appRouter } from "@/server/api/root";
import { validateRequest } from "@/server/auth/auth";
import { api } from "@/utils/api";
@@ -81,7 +82,9 @@ const Postgresql = (
{data?.appName}
-
+
+ {data?.server?.name || "Dokploy Server"}
+
{data?.description && (
{data?.description}
@@ -109,10 +112,17 @@ const Postgresql = (
}}
>
-
+
General
Environment
- Monitoring
+ {!data?.serverId && (
+ Monitoring
+ )}
Backups
Logs
Advanced
@@ -138,14 +148,19 @@ const Postgresql = (
-
-
-
-
-
+ {!data?.serverId && (
+
+
+
+
+
+ )}
-
+
diff --git a/apps/dokploy/pages/dashboard/project/[projectId]/services/redis/[redisId].tsx b/apps/dokploy/pages/dashboard/project/[projectId]/services/redis/[redisId].tsx
index f092ec065..768b637d6 100644
--- a/apps/dokploy/pages/dashboard/project/[projectId]/services/redis/[redisId].tsx
+++ b/apps/dokploy/pages/dashboard/project/[projectId]/services/redis/[redisId].tsx
@@ -8,15 +8,16 @@ import { ShowGeneralRedis } from "@/components/dashboard/redis/general/show-gene
import { ShowInternalRedisCredentials } from "@/components/dashboard/redis/general/show-internal-redis-credentials";
import { UpdateRedis } from "@/components/dashboard/redis/update-redis";
import { RedisIcon } from "@/components/icons/data-tools-icons";
-import { DashboardLayout } from "@/components/layouts/dashboard-layout";
import { ProjectLayout } from "@/components/layouts/project-layout";
import { StatusTooltip } from "@/components/shared/status-tooltip";
+import { Badge } from "@/components/ui/badge";
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
} from "@/components/ui/breadcrumb";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
+import { cn } from "@/lib/utils";
import { appRouter } from "@/server/api/root";
import { validateRequest } from "@/server/auth/auth";
import { api } from "@/utils/api";
@@ -80,7 +81,9 @@ const Redis = (
{data?.appName}
-
+
+ {data?.server?.name || "Dokploy Server"}
+
{data?.description && (
{data?.description}
@@ -108,10 +111,17 @@ const Redis = (
}}
>
-
+
General
Environment
- Monitoring
+ {!data?.serverId && (
+ Monitoring
+ )}
Logs
Advanced
@@ -136,14 +146,19 @@ const Redis = (
-
-
-
-
-
+ {!data?.serverId && (
+
+
+
+
+
+ )}
-
+
diff --git a/apps/dokploy/pages/dashboard/settings/servers.tsx b/apps/dokploy/pages/dashboard/settings/servers.tsx
new file mode 100644
index 000000000..266f264b7
--- /dev/null
+++ b/apps/dokploy/pages/dashboard/settings/servers.tsx
@@ -0,0 +1,49 @@
+import { ShowServers } from "@/components/dashboard/settings/servers/show-servers";
+import { DashboardLayout } from "@/components/layouts/dashboard-layout";
+import { SettingsLayout } from "@/components/layouts/settings-layout";
+import { validateRequest } from "@/server/auth/auth";
+import type { GetServerSidePropsContext } from "next";
+import React, { type ReactElement } from "react";
+
+const Page = () => {
+ return (
+
+
+
+ );
+};
+
+export default Page;
+
+Page.getLayout = (page: ReactElement) => {
+ return (
+
+ {page}
+
+ );
+};
+export async function getServerSideProps(
+ ctx: GetServerSidePropsContext<{ serviceId: string }>,
+) {
+ const { user } = await validateRequest(ctx.req, ctx.res);
+ if (!user) {
+ return {
+ redirect: {
+ permanent: true,
+ destination: "/",
+ },
+ };
+ }
+ if (user.rol === "user") {
+ return {
+ redirect: {
+ permanent: true,
+ destination: "/dashboard/settings/profile",
+ },
+ };
+ }
+
+ return {
+ props: {},
+ };
+}
diff --git a/apps/dokploy/pages/hola.tsx b/apps/dokploy/pages/hola.tsx
new file mode 100644
index 000000000..65b3d1aa7
--- /dev/null
+++ b/apps/dokploy/pages/hola.tsx
@@ -0,0 +1,3 @@
+export default function hola() {
+ return hola
;
+}
diff --git a/apps/dokploy/pages/register.tsx b/apps/dokploy/pages/register.tsx
index 31cf82cc2..57502ce14 100644
--- a/apps/dokploy/pages/register.tsx
+++ b/apps/dokploy/pages/register.tsx
@@ -16,6 +16,7 @@ import {
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { isAdminPresent } from "@/server/api/services/admin";
+// import { IS_CLOUD } from "@/server/constants";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { AlertTriangle } from "lucide-react";
@@ -220,6 +221,11 @@ const Register = ({ hasAdmin }: Props) => {
export default Register;
export async function getServerSideProps() {
+ // if (IS_CLOUD) {
+ // return {
+ // props: {},
+ // };
+ // }
const hasAdmin = await isAdminPresent();
if (hasAdmin) {
diff --git a/apps/dokploy/public/templates/roundcube.svg b/apps/dokploy/public/templates/roundcube.svg
new file mode 100644
index 000000000..04238a06a
--- /dev/null
+++ b/apps/dokploy/public/templates/roundcube.svg
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/apps/dokploy/server/api/root.ts b/apps/dokploy/server/api/root.ts
index 0e0579d03..ad08911d8 100644
--- a/apps/dokploy/server/api/root.ts
+++ b/apps/dokploy/server/api/root.ts
@@ -26,6 +26,7 @@ import { redirectsRouter } from "./routers/redirects";
import { redisRouter } from "./routers/redis";
import { registryRouter } from "./routers/registry";
import { securityRouter } from "./routers/security";
+import { serverRouter } from "./routers/server";
import { settingsRouter } from "./routers/settings";
import { sshRouter } from "./routers/ssh-key";
import { userRouter } from "./routers/user";
@@ -35,6 +36,7 @@ import { userRouter } from "./routers/user";
*
* All routers added in /api/routers should be manually added here.
*/
+
export const appRouter = createTRPCRouter({
admin: adminRouter,
docker: dockerRouter,
@@ -66,6 +68,7 @@ export const appRouter = createTRPCRouter({
bitbucket: bitbucketRouter,
gitlab: gitlabRouter,
github: githubRouter,
+ server: serverRouter,
});
// export type definition of API
diff --git a/apps/dokploy/server/api/routers/admin.ts b/apps/dokploy/server/api/routers/admin.ts
index c8a543dab..2157073e6 100644
--- a/apps/dokploy/server/api/routers/admin.ts
+++ b/apps/dokploy/server/api/routers/admin.ts
@@ -17,7 +17,7 @@ import {
import { adminProcedure, createTRPCRouter, publicProcedure } from "../trpc";
export const adminRouter = createTRPCRouter({
- one: adminProcedure.query(async () => {
+ one: adminProcedure.query(async ({ ctx }) => {
const { sshPrivateKey, ...rest } = await findAdmin();
return {
haveSSH: !!sshPrivateKey,
diff --git a/apps/dokploy/server/api/routers/application.ts b/apps/dokploy/server/api/routers/application.ts
index cb0022e92..4c43fe7ce 100644
--- a/apps/dokploy/server/api/routers/application.ts
+++ b/apps/dokploy/server/api/routers/application.ts
@@ -24,10 +24,13 @@ import {
cleanQueuesByApplication,
} from "@/server/queues/deployments-queue";
import { myQueue } from "@/server/queues/queueSetup";
+import { unzipDrop } from "@/server/utils/builders/drop";
import {
removeService,
startService,
+ startServiceRemote,
stopService,
+ stopServiceRemote,
} from "@/server/utils/docker/utils";
import {
removeDirectoryCode,
@@ -35,10 +38,13 @@ import {
} from "@/server/utils/filesystem/directory";
import {
readConfig,
+ readRemoteConfig,
removeTraefikConfig,
writeConfig,
+ writeConfigRemote,
} from "@/server/utils/traefik/application";
import { deleteAllMiddlewares } from "@/server/utils/traefik/middleware";
+import { uploadFileSchema } from "@/utils/schema";
import { TRPCError } from "@trpc/server";
import { eq } from "drizzle-orm";
import { nanoid } from "nanoid";
@@ -53,9 +59,6 @@ import {
import { removeDeployments } from "../services/deployment";
import { addNewService, checkServiceAccess } from "../services/user";
-import { unzipDrop } from "@/server/utils/builders/drop";
-import { uploadFileSchema } from "@/utils/schema";
-
export const applicationRouter = createTRPCRouter({
create: protectedProcedure
.input(apiCreateApplication)
@@ -96,9 +99,19 @@ export const applicationRouter = createTRPCRouter({
reload: protectedProcedure
.input(apiReloadApplication)
.mutation(async ({ input }) => {
- await stopService(input.appName);
+ const application = await findApplicationById(input.applicationId);
+ if (application.serverId) {
+ await stopServiceRemote(application.serverId, input.appName);
+ } else {
+ await stopService(input.appName);
+ }
await updateApplicationStatus(input.applicationId, "idle");
- await startService(input.appName);
+
+ if (application.serverId) {
+ await startServiceRemote(application.serverId, input.appName);
+ } else {
+ await startService(input.appName);
+ }
await updateApplicationStatus(input.applicationId, "done");
return true;
}),
@@ -121,12 +134,19 @@ export const applicationRouter = createTRPCRouter({
.returning();
const cleanupOperations = [
- async () => deleteAllMiddlewares(application),
+ async () => await deleteAllMiddlewares(application),
async () => await removeDeployments(application),
- async () => await removeDirectoryCode(application?.appName),
- async () => await removeMonitoringDirectory(application?.appName),
- async () => await removeTraefikConfig(application?.appName),
- async () => await removeService(application?.appName),
+ async () =>
+ await removeDirectoryCode(application.appName, application.serverId),
+ async () =>
+ await removeMonitoringDirectory(
+ application.appName,
+ application.serverId,
+ ),
+ async () =>
+ await removeTraefikConfig(application.appName, application.serverId),
+ async () =>
+ await removeService(application?.appName, application.serverId),
];
for (const operation of cleanupOperations) {
@@ -142,7 +162,11 @@ export const applicationRouter = createTRPCRouter({
.input(apiFindOneApplication)
.mutation(async ({ input }) => {
const service = await findApplicationById(input.applicationId);
- await stopService(service.appName);
+ if (service.serverId) {
+ await stopServiceRemote(service.serverId, service.appName);
+ } else {
+ await stopService(service.appName);
+ }
await updateApplicationStatus(input.applicationId, "idle");
return service;
@@ -152,8 +176,11 @@ export const applicationRouter = createTRPCRouter({
.input(apiFindOneApplication)
.mutation(async ({ input }) => {
const service = await findApplicationById(input.applicationId);
-
- await startService(service.appName);
+ if (service.serverId) {
+ await startServiceRemote(service.serverId, service.appName);
+ } else {
+ await startService(service.appName);
+ }
await updateApplicationStatus(input.applicationId, "done");
return service;
@@ -162,12 +189,14 @@ export const applicationRouter = createTRPCRouter({
redeploy: protectedProcedure
.input(apiFindOneApplication)
.mutation(async ({ input }) => {
+ const application = await findApplicationById(input.applicationId);
const jobData: DeploymentJob = {
applicationId: input.applicationId,
titleLog: "Rebuild deployment",
descriptionLog: "",
type: "redeploy",
applicationType: "application",
+ server: !!application.serverId,
};
await myQueue.add(
"deployments",
@@ -306,13 +335,15 @@ export const applicationRouter = createTRPCRouter({
}),
deploy: protectedProcedure
.input(apiFindOneApplication)
- .mutation(async ({ input }) => {
+ .mutation(async ({ input, ctx }) => {
+ const application = await findApplicationById(input.applicationId);
const jobData: DeploymentJob = {
applicationId: input.applicationId,
titleLog: "Manual deployment",
descriptionLog: "",
type: "deploy",
applicationType: "application",
+ server: !!application.serverId,
};
await myQueue.add(
"deployments",
@@ -334,8 +365,15 @@ export const applicationRouter = createTRPCRouter({
.input(apiFindOneApplication)
.query(async ({ input }) => {
const application = await findApplicationById(input.applicationId);
-
- const traefikConfig = readConfig(application.appName);
+ let traefikConfig = null;
+ if (application.serverId) {
+ traefikConfig = await readRemoteConfig(
+ application.serverId,
+ application.appName,
+ );
+ } else {
+ traefikConfig = readConfig(application.appName);
+ }
return traefikConfig;
}),
@@ -359,7 +397,7 @@ export const applicationRouter = createTRPCRouter({
});
const app = await findApplicationById(input.applicationId as string);
- await unzipDrop(zipFile, app.appName);
+ await unzipDrop(zipFile, app);
const jobData: DeploymentJob = {
applicationId: app.applicationId,
@@ -367,6 +405,7 @@ export const applicationRouter = createTRPCRouter({
descriptionLog: "",
type: "deploy",
applicationType: "application",
+ server: !!app.serverId,
};
await myQueue.add(
"deployments",
@@ -382,7 +421,16 @@ export const applicationRouter = createTRPCRouter({
.input(z.object({ applicationId: z.string(), traefikConfig: z.string() }))
.mutation(async ({ input }) => {
const application = await findApplicationById(input.applicationId);
- writeConfig(application.appName, input.traefikConfig);
+
+ if (application.serverId) {
+ await writeConfigRemote(
+ application.serverId,
+ application.appName,
+ input.traefikConfig,
+ );
+ } else {
+ writeConfig(application.appName, input.traefikConfig);
+ }
return true;
}),
readAppMonitoring: protectedProcedure
diff --git a/apps/dokploy/server/api/routers/auth.ts b/apps/dokploy/server/api/routers/auth.ts
index 94645c67f..2d564f72d 100644
--- a/apps/dokploy/server/api/routers/auth.ts
+++ b/apps/dokploy/server/api/routers/auth.ts
@@ -1,5 +1,6 @@
import { lucia, validateRequest } from "@/server/auth/auth";
import { luciaToken } from "@/server/auth/token";
+// import { IS_CLOUD } from "@/server/constants";
import {
apiCreateAdmin,
apiCreateUser,
@@ -35,14 +36,16 @@ export const authRouter = createTRPCRouter({
.input(apiCreateAdmin)
.mutation(async ({ ctx, input }) => {
try {
+ // if (!IS_CLOUD) {
const admin = await db.query.admins.findFirst({});
-
if (admin) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Admin already exists",
});
}
+ // }
+
const newAdmin = await createAdmin(input);
const session = await lucia.createSession(newAdmin.id || "", {});
ctx.res.appendHeader(
@@ -51,6 +54,7 @@ export const authRouter = createTRPCRouter({
);
return true;
} catch (error) {
+ console.log(error);
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error to create the main admin",
diff --git a/apps/dokploy/server/api/routers/backup.ts b/apps/dokploy/server/api/routers/backup.ts
index 7e84a8a84..a8740104d 100644
--- a/apps/dokploy/server/api/routers/backup.ts
+++ b/apps/dokploy/server/api/routers/backup.ts
@@ -57,6 +57,7 @@ export const backupRouter = createTRPCRouter({
const backup = await findBackupById(input.backupId);
if (backup.enabled) {
+ removeScheduleBackup(input.backupId);
scheduleBackup(backup);
} else {
removeScheduleBackup(input.backupId);
@@ -90,7 +91,6 @@ export const backupRouter = createTRPCRouter({
const backup = await findBackupById(input.backupId);
const postgres = await findPostgresByBackupId(backup.backupId);
await runPostgresBackup(postgres, backup);
-
return true;
} catch (error) {
console.log(error);
diff --git a/apps/dokploy/server/api/routers/compose.ts b/apps/dokploy/server/api/routers/compose.ts
index 26be7fca9..8829ee556 100644
--- a/apps/dokploy/server/api/routers/compose.ts
+++ b/apps/dokploy/server/api/routers/compose.ts
@@ -15,11 +15,12 @@ import {
} from "@/server/queues/deployments-queue";
import { myQueue } from "@/server/queues/queueSetup";
import { createCommand } from "@/server/utils/builders/compose";
+import { randomizeComposeFile } from "@/server/utils/docker/compose";
import {
- randomizeComposeFile,
- randomizeSpecificationFile,
-} from "@/server/utils/docker/compose";
-import { addDomainToCompose, cloneCompose } from "@/server/utils/docker/domain";
+ addDomainToCompose,
+ cloneCompose,
+ cloneComposeRemote,
+} from "@/server/utils/docker/domain";
import { removeComposeDirectory } from "@/server/utils/filesystem/directory";
import { templates } from "@/templates/templates";
import type { TemplatesKeys } from "@/templates/types/templates-data.type";
@@ -33,7 +34,7 @@ import { eq } from "drizzle-orm";
import { dump } from "js-yaml";
import _ from "lodash";
import { nanoid } from "nanoid";
-import { findAdmin } from "../services/admin";
+import { findAdmin, findAdminById } from "../services/admin";
import {
createCompose,
createComposeByTemplate,
@@ -47,6 +48,7 @@ import { removeDeploymentsByComposeId } from "../services/deployment";
import { createDomain, findDomainsByComposeId } from "../services/domain";
import { createMount } from "../services/mount";
import { findProjectById } from "../services/project";
+import { findServerById } from "../services/server";
import { addNewService, checkServiceAccess } from "../services/user";
import { createTRPCRouter, protectedProcedure } from "../trpc";
@@ -130,7 +132,11 @@ export const composeRouter = createTRPCRouter({
.mutation(async ({ input }) => {
try {
const compose = await findComposeById(input.composeId);
- await cloneCompose(compose);
+ if (compose.serverId) {
+ await cloneComposeRemote(compose);
+ } else {
+ await cloneCompose(compose);
+ }
return compose.sourceType;
} catch (err) {
throw new TRPCError({
@@ -151,9 +157,7 @@ export const composeRouter = createTRPCRouter({
.query(async ({ input }) => {
const compose = await findComposeById(input.composeId);
const domains = await findDomainsByComposeId(input.composeId);
-
const composeFile = await addDomainToCompose(compose, domains);
-
return dump(composeFile, {
lineWidth: 1000,
});
@@ -162,12 +166,14 @@ export const composeRouter = createTRPCRouter({
deploy: protectedProcedure
.input(apiFindCompose)
.mutation(async ({ input }) => {
+ const compose = await findComposeById(input.composeId);
const jobData: DeploymentJob = {
composeId: input.composeId,
titleLog: "Manual deployment",
type: "deploy",
applicationType: "compose",
descriptionLog: "",
+ server: !!compose.serverId,
};
await myQueue.add(
"deployments",
@@ -181,12 +187,14 @@ export const composeRouter = createTRPCRouter({
redeploy: protectedProcedure
.input(apiFindCompose)
.mutation(async ({ input }) => {
+ const compose = await findComposeById(input.composeId);
const jobData: DeploymentJob = {
composeId: input.composeId,
titleLog: "Rebuild deployment",
type: "redeploy",
applicationType: "compose",
descriptionLog: "",
+ server: !!compose.serverId,
};
await myQueue.add(
"deployments",
@@ -227,7 +235,8 @@ export const composeRouter = createTRPCRouter({
const generate = await loadTemplateModule(input.id as TemplatesKeys);
- const admin = await findAdmin();
+ const admin = await findAdminById(ctx.user.adminId);
+ let serverIp = admin.serverIp;
if (!admin.serverIp) {
throw new TRPCError({
@@ -239,9 +248,15 @@ export const composeRouter = createTRPCRouter({
const project = await findProjectById(input.projectId);
+ if (input.serverId) {
+ const server = await findServerById(input.serverId);
+ serverIp = server.ipAddress;
+ } else if (process.env.NODE_ENV === "development") {
+ serverIp = "127.0.0.1";
+ }
const projectName = slugify(`${project.name} ${input.id}`);
const { envs, mounts, domains } = generate({
- serverIp: admin.serverIp,
+ serverIp: serverIp || "",
projectName: projectName,
});
@@ -249,6 +264,7 @@ export const composeRouter = createTRPCRouter({
...input,
composeFile: composeFile,
env: envs?.join("\n"),
+ serverId: input.serverId,
name: input.id,
sourceType: "raw",
appName: `${projectName}-${generatePassword(6)}`,
diff --git a/apps/dokploy/server/api/routers/deployment.ts b/apps/dokploy/server/api/routers/deployment.ts
index 9585645ab..b9afdc067 100644
--- a/apps/dokploy/server/api/routers/deployment.ts
+++ b/apps/dokploy/server/api/routers/deployment.ts
@@ -1,10 +1,12 @@
import {
apiFindAllByApplication,
apiFindAllByCompose,
+ apiFindAllByServer,
} from "@/server/db/schema";
import {
findAllDeploymentsByApplicationId,
findAllDeploymentsByComposeId,
+ findAllDeploymentsByServerId,
} from "../services/deployment";
import { createTRPCRouter, protectedProcedure } from "../trpc";
@@ -20,4 +22,9 @@ export const deploymentRouter = createTRPCRouter({
.query(async ({ input }) => {
return await findAllDeploymentsByComposeId(input.composeId);
}),
+ allByServer: protectedProcedure
+ .input(apiFindAllByServer)
+ .query(async ({ input }) => {
+ return await findAllDeploymentsByServerId(input.serverId);
+ }),
});
diff --git a/apps/dokploy/server/api/routers/destination.ts b/apps/dokploy/server/api/routers/destination.ts
index 19e773e5e..b7bc9906c 100644
--- a/apps/dokploy/server/api/routers/destination.ts
+++ b/apps/dokploy/server/api/routers/destination.ts
@@ -10,7 +10,7 @@ import {
apiRemoveDestination,
apiUpdateDestination,
} from "@/server/db/schema";
-import { HeadBucketCommand, S3Client } from "@aws-sdk/client-s3";
+import { execAsync } from "@/server/utils/process/execAsync";
import { TRPCError } from "@trpc/server";
import { findAdmin } from "../services/admin";
import {
@@ -39,22 +39,22 @@ export const destinationRouter = createTRPCRouter({
.input(apiCreateDestination)
.mutation(async ({ input }) => {
const { secretAccessKey, bucket, region, endpoint, accessKey } = input;
- const s3Client = new S3Client({
- region: region,
- ...(endpoint && {
- endpoint: endpoint,
- }),
- credentials: {
- accessKeyId: accessKey,
- secretAccessKey: secretAccessKey,
- },
- forcePathStyle: true,
- });
- const headBucketCommand = new HeadBucketCommand({ Bucket: bucket });
try {
- await s3Client.send(headBucketCommand);
+ const rcloneFlags = [
+ // `--s3-provider=Cloudflare`,
+ `--s3-access-key-id=${accessKey}`,
+ `--s3-secret-access-key=${secretAccessKey}`,
+ `--s3-region=${region}`,
+ `--s3-endpoint=${endpoint}`,
+ "--s3-no-check-bucket",
+ "--s3-force-path-style",
+ ];
+ const rcloneDestination = `:s3:${bucket}`;
+ const rcloneCommand = `rclone ls ${rcloneFlags.join(" ")} "${rcloneDestination}"`;
+ await execAsync(rcloneCommand);
} catch (error) {
+ console.log(error);
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error to connect to bucket",
@@ -68,7 +68,7 @@ export const destinationRouter = createTRPCRouter({
const destination = await findDestinationById(input.destinationId);
return destination;
}),
- all: adminProcedure.query(async () => {
+ all: protectedProcedure.query(async () => {
return await db.query.destinations.findMany({});
}),
remove: adminProcedure
diff --git a/apps/dokploy/server/api/routers/docker.ts b/apps/dokploy/server/api/routers/docker.ts
index 227200951..4281cb3b4 100644
--- a/apps/dokploy/server/api/routers/docker.ts
+++ b/apps/dokploy/server/api/routers/docker.ts
@@ -9,9 +9,15 @@ import {
import { createTRPCRouter, protectedProcedure } from "../trpc";
export const dockerRouter = createTRPCRouter({
- getContainers: protectedProcedure.query(async () => {
- return await getContainers();
- }),
+ getContainers: protectedProcedure
+ .input(
+ z.object({
+ serverId: z.string().optional(),
+ }),
+ )
+ .query(async ({ input }) => {
+ return await getContainers(input.serverId);
+ }),
restartContainer: protectedProcedure
.input(
@@ -27,10 +33,11 @@ export const dockerRouter = createTRPCRouter({
.input(
z.object({
containerId: z.string().min(1),
+ serverId: z.string().optional(),
}),
)
.query(async ({ input }) => {
- return await getConfig(input.containerId);
+ return await getConfig(input.containerId, input.serverId);
}),
getContainersByAppNameMatch: protectedProcedure
@@ -40,19 +47,25 @@ export const dockerRouter = createTRPCRouter({
.union([z.literal("stack"), z.literal("docker-compose")])
.optional(),
appName: z.string().min(1),
+ serverId: z.string().optional(),
}),
)
.query(async ({ input }) => {
- return await getContainersByAppNameMatch(input.appName, input.appType);
+ return await getContainersByAppNameMatch(
+ input.appName,
+ input.appType,
+ input.serverId,
+ );
}),
getContainersByAppLabel: protectedProcedure
.input(
z.object({
appName: z.string().min(1),
+ serverId: z.string().optional(),
}),
)
.query(async ({ input }) => {
- return await getContainersByAppLabel(input.appName);
+ return await getContainersByAppLabel(input.appName, input.serverId);
}),
});
diff --git a/apps/dokploy/server/api/routers/domain.ts b/apps/dokploy/server/api/routers/domain.ts
index 3db43e563..9e8a371a0 100644
--- a/apps/dokploy/server/api/routers/domain.ts
+++ b/apps/dokploy/server/api/routers/domain.ts
@@ -1,16 +1,14 @@
import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc";
import {
apiCreateDomain,
- apiCreateTraefikMeDomain,
apiFindCompose,
apiFindDomain,
- apiFindDomainByApplication,
- apiFindDomainByCompose,
apiFindOneApplication,
apiUpdateDomain,
} from "@/server/db/schema";
import { manageDomain, removeDomain } from "@/server/utils/traefik/domain";
import { TRPCError } from "@trpc/server";
+import { z } from "zod";
import { findApplicationById } from "../services/application";
import {
createDomain,
@@ -47,9 +45,13 @@ export const domainRouter = createTRPCRouter({
return await findDomainsByComposeId(input.composeId);
}),
generateDomain: protectedProcedure
- .input(apiCreateTraefikMeDomain)
- .mutation(async ({ input }) => {
- return generateTraefikMeDomain(input.appName);
+ .input(z.object({ appName: z.string(), serverId: z.string().optional() }))
+ .mutation(async ({ input, ctx }) => {
+ return generateTraefikMeDomain(
+ input.appName,
+ ctx.user.adminId,
+ input.serverId,
+ );
}),
update: protectedProcedure
@@ -71,8 +73,10 @@ export const domainRouter = createTRPCRouter({
.mutation(async ({ input }) => {
const domain = await findDomainById(input.domainId);
const result = await removeDomainById(input.domainId);
- if (domain.application) {
- await removeDomain(domain.application.appName, domain.uniqueConfigKey);
+
+ if (domain.applicationId) {
+ const application = await findApplicationById(domain.applicationId);
+ await removeDomain(application, domain.uniqueConfigKey);
}
return result;
diff --git a/apps/dokploy/server/api/routers/mariadb.ts b/apps/dokploy/server/api/routers/mariadb.ts
index dc13461a8..e8893782b 100644
--- a/apps/dokploy/server/api/routers/mariadb.ts
+++ b/apps/dokploy/server/api/routers/mariadb.ts
@@ -12,7 +12,9 @@ import {
import {
removeService,
startService,
+ startServiceRemote,
stopService,
+ stopServiceRemote,
} from "@/server/utils/docker/utils";
import { TRPCError } from "@trpc/server";
import {
@@ -72,8 +74,11 @@ export const mariadbRouter = createTRPCRouter({
.input(apiFindOneMariaDB)
.mutation(async ({ input }) => {
const service = await findMariadbById(input.mariadbId);
-
- await startService(service.appName);
+ if (service.serverId) {
+ await startServiceRemote(service.serverId, service.appName);
+ } else {
+ await startService(service.appName);
+ }
await updateMariadbById(input.mariadbId, {
applicationStatus: "done",
});
@@ -83,13 +88,18 @@ export const mariadbRouter = createTRPCRouter({
stop: protectedProcedure
.input(apiFindOneMariaDB)
.mutation(async ({ input }) => {
- const mongo = await findMariadbById(input.mariadbId);
- await stopService(mongo.appName);
+ const mariadb = await findMariadbById(input.mariadbId);
+
+ if (mariadb.serverId) {
+ await stopServiceRemote(mariadb.serverId, mariadb.appName);
+ } else {
+ await stopService(mariadb.appName);
+ }
await updateMariadbById(input.mariadbId, {
applicationStatus: "idle",
});
- return mongo;
+ return mariadb;
}),
saveExternalPort: protectedProcedure
.input(apiSaveExternalPortMariaDB)
@@ -125,7 +135,7 @@ export const mariadbRouter = createTRPCRouter({
const mongo = await findMariadbById(input.mariadbId);
const cleanupOperations = [
- async () => await removeService(mongo?.appName),
+ async () => await removeService(mongo?.appName, mongo.serverId),
async () => await removeMariadbById(input.mariadbId),
];
@@ -156,11 +166,21 @@ export const mariadbRouter = createTRPCRouter({
reload: protectedProcedure
.input(apiResetMariadb)
.mutation(async ({ input }) => {
- await stopService(input.appName);
+ const mariadb = await findMariadbById(input.mariadbId);
+ if (mariadb.serverId) {
+ await stopServiceRemote(mariadb.serverId, mariadb.appName);
+ } else {
+ await stopService(mariadb.appName);
+ }
await updateMariadbById(input.mariadbId, {
applicationStatus: "idle",
});
- await startService(input.appName);
+
+ if (mariadb.serverId) {
+ await startServiceRemote(mariadb.serverId, mariadb.appName);
+ } else {
+ await startService(mariadb.appName);
+ }
await updateMariadbById(input.mariadbId, {
applicationStatus: "done",
});
diff --git a/apps/dokploy/server/api/routers/mongo.ts b/apps/dokploy/server/api/routers/mongo.ts
index e38ea7494..f51d0eb9e 100644
--- a/apps/dokploy/server/api/routers/mongo.ts
+++ b/apps/dokploy/server/api/routers/mongo.ts
@@ -12,7 +12,9 @@ import {
import {
removeService,
startService,
+ startServiceRemote,
stopService,
+ stopServiceRemote,
} from "@/server/utils/docker/utils";
import { TRPCError } from "@trpc/server";
import {
@@ -74,7 +76,11 @@ export const mongoRouter = createTRPCRouter({
.mutation(async ({ input }) => {
const service = await findMongoById(input.mongoId);
- await startService(service.appName);
+ if (service.serverId) {
+ await startServiceRemote(service.serverId, service.appName);
+ } else {
+ await startService(service.appName);
+ }
await updateMongoById(input.mongoId, {
applicationStatus: "done",
});
@@ -85,7 +91,12 @@ export const mongoRouter = createTRPCRouter({
.input(apiFindOneMongo)
.mutation(async ({ input }) => {
const mongo = await findMongoById(input.mongoId);
- await stopService(mongo.appName);
+
+ if (mongo.serverId) {
+ await stopServiceRemote(mongo.serverId, mongo.appName);
+ } else {
+ await stopService(mongo.appName);
+ }
await updateMongoById(input.mongoId, {
applicationStatus: "idle",
});
@@ -119,11 +130,21 @@ export const mongoRouter = createTRPCRouter({
reload: protectedProcedure
.input(apiResetMongo)
.mutation(async ({ input }) => {
- await stopService(input.appName);
+ const mongo = await findMongoById(input.mongoId);
+ if (mongo.serverId) {
+ await stopServiceRemote(mongo.serverId, mongo.appName);
+ } else {
+ await stopService(mongo.appName);
+ }
await updateMongoById(input.mongoId, {
applicationStatus: "idle",
});
- await startService(input.appName);
+
+ if (mongo.serverId) {
+ await startServiceRemote(mongo.serverId, mongo.appName);
+ } else {
+ await startService(mongo.appName);
+ }
await updateMongoById(input.mongoId, {
applicationStatus: "done",
});
@@ -139,7 +160,7 @@ export const mongoRouter = createTRPCRouter({
const mongo = await findMongoById(input.mongoId);
const cleanupOperations = [
- async () => await removeService(mongo?.appName),
+ async () => await removeService(mongo?.appName, mongo.serverId),
async () => await removeMongoById(input.mongoId),
];
diff --git a/apps/dokploy/server/api/routers/mysql.ts b/apps/dokploy/server/api/routers/mysql.ts
index d0c0f149b..71f6d514b 100644
--- a/apps/dokploy/server/api/routers/mysql.ts
+++ b/apps/dokploy/server/api/routers/mysql.ts
@@ -12,10 +12,11 @@ import {
import {
removeService,
startService,
+ startServiceRemote,
stopService,
+ stopServiceRemote,
} from "@/server/utils/docker/utils";
import { TRPCError } from "@trpc/server";
-import { z } from "zod";
import { createMount } from "../services/mount";
import {
createMysql,
@@ -74,7 +75,11 @@ export const mysqlRouter = createTRPCRouter({
.mutation(async ({ input }) => {
const service = await findMySqlById(input.mysqlId);
- await startService(service.appName);
+ if (service.serverId) {
+ await startServiceRemote(service.serverId, service.appName);
+ } else {
+ await startService(service.appName);
+ }
await updateMySqlById(input.mysqlId, {
applicationStatus: "done",
});
@@ -85,7 +90,11 @@ export const mysqlRouter = createTRPCRouter({
.input(apiFindOneMySql)
.mutation(async ({ input }) => {
const mongo = await findMySqlById(input.mysqlId);
- await stopService(mongo.appName);
+ if (mongo.serverId) {
+ await stopServiceRemote(mongo.serverId, mongo.appName);
+ } else {
+ await stopService(mongo.appName);
+ }
await updateMySqlById(input.mysqlId, {
applicationStatus: "idle",
});
@@ -119,11 +128,20 @@ export const mysqlRouter = createTRPCRouter({
reload: protectedProcedure
.input(apiResetMysql)
.mutation(async ({ input }) => {
- await stopService(input.appName);
+ const mysql = await findMySqlById(input.mysqlId);
+ if (mysql.serverId) {
+ await stopServiceRemote(mysql.serverId, mysql.appName);
+ } else {
+ await stopService(mysql.appName);
+ }
await updateMySqlById(input.mysqlId, {
applicationStatus: "idle",
});
- await startService(input.appName);
+ if (mysql.serverId) {
+ await startServiceRemote(mysql.serverId, mysql.appName);
+ } else {
+ await startService(mysql.appName);
+ }
await updateMySqlById(input.mysqlId, {
applicationStatus: "done",
});
@@ -138,7 +156,7 @@ export const mysqlRouter = createTRPCRouter({
const mongo = await findMySqlById(input.mysqlId);
const cleanupOperations = [
- async () => await removeService(mongo?.appName),
+ async () => await removeService(mongo?.appName, mongo.serverId),
async () => await removeMySqlById(input.mysqlId),
];
diff --git a/apps/dokploy/server/api/routers/postgres.ts b/apps/dokploy/server/api/routers/postgres.ts
index a52074e0e..b902ee517 100644
--- a/apps/dokploy/server/api/routers/postgres.ts
+++ b/apps/dokploy/server/api/routers/postgres.ts
@@ -12,7 +12,9 @@ import {
import {
removeService,
startService,
+ startServiceRemote,
stopService,
+ stopServiceRemote,
} from "@/server/utils/docker/utils";
import { TRPCError } from "@trpc/server";
import { createMount } from "../services/mount";
@@ -74,7 +76,11 @@ export const postgresRouter = createTRPCRouter({
.mutation(async ({ input }) => {
const service = await findPostgresById(input.postgresId);
- await startService(service.appName);
+ if (service.serverId) {
+ await startServiceRemote(service.serverId, service.appName);
+ } else {
+ await startService(service.appName);
+ }
await updatePostgresById(input.postgresId, {
applicationStatus: "done",
});
@@ -85,7 +91,11 @@ export const postgresRouter = createTRPCRouter({
.input(apiFindOnePostgres)
.mutation(async ({ input }) => {
const postgres = await findPostgresById(input.postgresId);
- await stopService(postgres.appName);
+ if (postgres.serverId) {
+ await stopServiceRemote(postgres.serverId, postgres.appName);
+ } else {
+ await stopService(postgres.appName);
+ }
await updatePostgresById(input.postgresId, {
applicationStatus: "idle",
});
@@ -125,7 +135,7 @@ export const postgresRouter = createTRPCRouter({
const postgres = await findPostgresById(input.postgresId);
const cleanupOperations = [
- removeService(postgres.appName),
+ removeService(postgres.appName, postgres.serverId),
removePostgresById(input.postgresId),
];
@@ -152,11 +162,21 @@ export const postgresRouter = createTRPCRouter({
reload: protectedProcedure
.input(apiResetPostgres)
.mutation(async ({ input }) => {
- await stopService(input.appName);
+ const postgres = await findPostgresById(input.postgresId);
+ if (postgres.serverId) {
+ await stopServiceRemote(postgres.serverId, postgres.appName);
+ } else {
+ await stopService(postgres.appName);
+ }
await updatePostgresById(input.postgresId, {
applicationStatus: "idle",
});
- await startService(input.appName);
+
+ if (postgres.serverId) {
+ await startServiceRemote(postgres.serverId, postgres.appName);
+ } else {
+ await startService(postgres.appName);
+ }
await updatePostgresById(input.postgresId, {
applicationStatus: "done",
});
diff --git a/apps/dokploy/server/api/routers/project.ts b/apps/dokploy/server/api/routers/project.ts
index 32246fd68..4ad9be75f 100644
--- a/apps/dokploy/server/api/routers/project.ts
+++ b/apps/dokploy/server/api/routers/project.ts
@@ -1,8 +1,4 @@
-import {
- cliProcedure,
- createTRPCRouter,
- protectedProcedure,
-} from "@/server/api/trpc";
+import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc";
import { db } from "@/server/db";
import {
applications,
@@ -43,7 +39,8 @@ export const projectRouter = createTRPCRouter({
if (ctx.user.rol === "user") {
await checkProjectAccess(ctx.user.authId, "create");
}
- const project = await createProject(input);
+
+ const project = await createProject(input, ctx.user.adminId);
if (ctx.user.rol === "user") {
await addNewProject(ctx.user.authId, project.projectId);
}
@@ -153,6 +150,7 @@ export const projectRouter = createTRPCRouter({
return query;
}
+
return await db.query.projects.findMany({
with: {
applications: true,
@@ -163,6 +161,7 @@ export const projectRouter = createTRPCRouter({
redis: true,
compose: true,
},
+ where: eq(projects.adminId, ctx.user.adminId),
orderBy: desc(projects.createdAt),
});
}),
diff --git a/apps/dokploy/server/api/routers/redis.ts b/apps/dokploy/server/api/routers/redis.ts
index 27bf391ab..19bfa7c20 100644
--- a/apps/dokploy/server/api/routers/redis.ts
+++ b/apps/dokploy/server/api/routers/redis.ts
@@ -12,7 +12,9 @@ import {
import {
removeService,
startService,
+ startServiceRemote,
stopService,
+ stopServiceRemote,
} from "@/server/utils/docker/utils";
import { TRPCError } from "@trpc/server";
import { createMount } from "../services/mount";
@@ -69,7 +71,12 @@ export const redisRouter = createTRPCRouter({
.input(apiFindOneRedis)
.mutation(async ({ input }) => {
const redis = await findRedisById(input.redisId);
- await startService(redis.appName);
+
+ if (redis.serverId) {
+ await startServiceRemote(redis.serverId, redis.appName);
+ } else {
+ await startService(redis.appName);
+ }
await updateRedisById(input.redisId, {
applicationStatus: "done",
});
@@ -79,11 +86,21 @@ export const redisRouter = createTRPCRouter({
reload: protectedProcedure
.input(apiResetRedis)
.mutation(async ({ input }) => {
- await stopService(input.appName);
+ const redis = await findRedisById(input.redisId);
+ if (redis.serverId) {
+ await stopServiceRemote(redis.serverId, redis.appName);
+ } else {
+ await stopService(redis.appName);
+ }
await updateRedisById(input.redisId, {
applicationStatus: "idle",
});
- await startService(input.appName);
+
+ if (redis.serverId) {
+ await startServiceRemote(redis.serverId, redis.appName);
+ } else {
+ await startService(redis.appName);
+ }
await updateRedisById(input.redisId, {
applicationStatus: "done",
});
@@ -93,13 +110,17 @@ export const redisRouter = createTRPCRouter({
stop: protectedProcedure
.input(apiFindOneRedis)
.mutation(async ({ input }) => {
- const mongo = await findRedisById(input.redisId);
- await stopService(mongo.appName);
+ const redis = await findRedisById(input.redisId);
+ if (redis.serverId) {
+ await stopServiceRemote(redis.serverId, redis.appName);
+ } else {
+ await stopService(redis.appName);
+ }
await updateRedisById(input.redisId, {
applicationStatus: "idle",
});
- return mongo;
+ return redis;
}),
saveExternalPort: protectedProcedure
.input(apiSaveExternalPortRedis)
@@ -135,7 +156,7 @@ export const redisRouter = createTRPCRouter({
const redis = await findRedisById(input.redisId);
const cleanupOperations = [
- async () => await removeService(redis?.appName),
+ async () => await removeService(redis?.appName, redis.serverId),
async () => await removeRedisById(input.redisId),
];
diff --git a/apps/dokploy/server/api/routers/registry.ts b/apps/dokploy/server/api/routers/registry.ts
index 8fd8d2ce2..ca132966e 100644
--- a/apps/dokploy/server/api/routers/registry.ts
+++ b/apps/dokploy/server/api/routers/registry.ts
@@ -7,7 +7,7 @@ import {
apiUpdateRegistry,
} from "@/server/db/schema";
import { initializeRegistry } from "@/server/setup/registry-setup";
-import { execAsync } from "@/server/utils/process/execAsync";
+import { execAsync, execAsyncRemote } from "@/server/utils/process/execAsync";
import { manageRegistry } from "@/server/utils/traefik/registry";
import { TRPCError } from "@trpc/server";
import {
@@ -58,7 +58,13 @@ export const registryRouter = createTRPCRouter({
.mutation(async ({ input }) => {
try {
const loginCommand = `echo ${input.password} | docker login ${input.registryUrl} --username ${input.username} --password-stdin`;
- await execAsync(loginCommand);
+
+ if (input.serverId && input.serverId !== "none") {
+ await execAsyncRemote(input.serverId, loginCommand);
+ } else {
+ await execAsync(loginCommand);
+ }
+
return true;
} catch (error) {
console.log("Error Registry:", error);
@@ -78,6 +84,7 @@ export const registryRouter = createTRPCRouter({
? input.registryUrl
: "dokploy-registry.docker.localhost",
imagePrefix: null,
+ serverId: undefined,
});
await manageRegistry(selfHostedRegistry);
@@ -86,3 +93,17 @@ export const registryRouter = createTRPCRouter({
return selfHostedRegistry;
}),
});
+
+const shellEscape = (str: string) => {
+ const ret = [];
+ let s = str;
+ if (/[^A-Za-z0-9_\/:=-]/.test(s)) {
+ s = `'${s.replace(/'/g, "'\\''")}'`;
+ s = s
+ .replace(/^(?:'')+/g, "") // unduplicate single-quote at the beginning
+ .replace(/\\'''/g, "\\'"); // remove non-escaped single-quote if there are enclosed between 2 escaped
+ }
+ ret.push(s);
+
+ return ret.join(" ");
+};
diff --git a/apps/dokploy/server/api/routers/server.ts b/apps/dokploy/server/api/routers/server.ts
new file mode 100644
index 000000000..9ec0bfa4c
--- /dev/null
+++ b/apps/dokploy/server/api/routers/server.ts
@@ -0,0 +1,136 @@
+import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc";
+import { db } from "@/server/db";
+import {
+ apiCreateServer,
+ apiFindOneServer,
+ apiRemoveServer,
+ apiUpdateServer,
+ applications,
+ compose,
+ mariadb,
+ mongo,
+ mysql,
+ postgres,
+ redis,
+ server,
+} from "@/server/db/schema";
+import { serverSetup } from "@/server/setup/server-setup";
+import { TRPCError } from "@trpc/server";
+import { and, desc, eq, getTableColumns, isNotNull, sql } from "drizzle-orm";
+import { removeDeploymentsByServerId } from "../services/deployment";
+import {
+ createServer,
+ deleteServer,
+ findServerById,
+ haveActiveServices,
+ updateServerById,
+} from "../services/server";
+
+export const serverRouter = createTRPCRouter({
+ create: protectedProcedure
+ .input(apiCreateServer)
+ .mutation(async ({ ctx, input }) => {
+ try {
+ const project = await createServer(input, ctx.user.adminId);
+ return project;
+ } catch (error) {
+ console.log(error);
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Error to create the server",
+ cause: error,
+ });
+ }
+ }),
+
+ one: protectedProcedure
+ .input(apiFindOneServer)
+ .query(async ({ input, ctx }) => {
+ return await findServerById(input.serverId);
+ }),
+ all: protectedProcedure.query(async ({ ctx }) => {
+ const result = await db
+ .select({
+ ...getTableColumns(server),
+ totalSum: sql`cast(count(${applications.applicationId}) + count(${compose.composeId}) + count(${redis.redisId}) + count(${mariadb.mariadbId}) + count(${mongo.mongoId}) + count(${mysql.mysqlId}) + count(${postgres.postgresId}) as integer)`,
+ })
+ .from(server)
+ .leftJoin(applications, eq(applications.serverId, server.serverId))
+ .leftJoin(compose, eq(compose.serverId, server.serverId))
+ .leftJoin(redis, eq(redis.serverId, server.serverId))
+ .leftJoin(mariadb, eq(mariadb.serverId, server.serverId))
+ .leftJoin(mongo, eq(mongo.serverId, server.serverId))
+ .leftJoin(mysql, eq(mysql.serverId, server.serverId))
+ .leftJoin(postgres, eq(postgres.serverId, server.serverId))
+ .where(eq(server.adminId, ctx.user.adminId))
+ .orderBy(desc(server.createdAt))
+ .groupBy(server.serverId);
+
+ return result;
+ }),
+ withSSHKey: protectedProcedure.query(async ({ input, ctx }) => {
+ return await db.query.server.findMany({
+ orderBy: desc(server.createdAt),
+ where: and(
+ isNotNull(server.sshKeyId),
+ eq(server.adminId, ctx.user.adminId),
+ ),
+ });
+ }),
+ setup: protectedProcedure
+ .input(apiFindOneServer)
+ .mutation(async ({ input, ctx }) => {
+ try {
+ const currentServer = await serverSetup(input.serverId);
+ return currentServer;
+ } catch (error) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Error to setup this server",
+ cause: error,
+ });
+ }
+ }),
+ remove: protectedProcedure
+ .input(apiRemoveServer)
+ .mutation(async ({ input, ctx }) => {
+ try {
+ const activeServers = await haveActiveServices(input.serverId);
+
+ if (activeServers) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Server has active services, please delete them first",
+ });
+ }
+ const currentServer = await findServerById(input.serverId);
+ await removeDeploymentsByServerId(currentServer);
+ await deleteServer(input.serverId);
+
+ return currentServer;
+ } catch (error) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Error to delete this server",
+ cause: error,
+ });
+ }
+ }),
+ update: protectedProcedure
+ .input(apiUpdateServer)
+ .mutation(async ({ input }) => {
+ try {
+ const currentServer = await updateServerById(input.serverId, {
+ ...input,
+ });
+
+ return currentServer;
+ } catch (error) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Error to update this server",
+ cause: error,
+ });
+ }
+ }),
+});
diff --git a/apps/dokploy/server/api/routers/settings.ts b/apps/dokploy/server/api/routers/settings.ts
index 00df9b82c..ff6ed68ff 100644
--- a/apps/dokploy/server/api/routers/settings.ts
+++ b/apps/dokploy/server/api/routers/settings.ts
@@ -1,4 +1,4 @@
-import { MAIN_TRAEFIK_PATH, MONITORING_PATH } from "@/server/constants";
+import { paths } from "@/server/constants";
import {
apiAssignDomain,
apiEnableDashboard,
@@ -6,6 +6,7 @@ import {
apiReadStatsLogs,
apiReadTraefikConfig,
apiSaveSSHKey,
+ apiServerSchema,
apiTraefikConfig,
apiUpdateDockerCleanup,
} from "@/server/db/schema";
@@ -20,11 +21,13 @@ import {
cleanUpUnusedVolumes,
prepareEnvironmentVariables,
startService,
+ startServiceRemote,
stopService,
+ stopServiceRemote,
} from "@/server/utils/docker/utils";
import { recreateDirectory } from "@/server/utils/filesystem/directory";
import { sendDockerCleanupNotifications } from "@/server/utils/notifications/docker-cleanup";
-import { execAsync } from "@/server/utils/process/execAsync";
+import { execAsync, execAsyncRemote } from "@/server/utils/process/execAsync";
import { spawnAsync } from "@/server/utils/process/spawnAsync";
import {
readConfig,
@@ -46,6 +49,7 @@ import { scheduleJob, scheduledJobs } from "node-schedule";
import { z } from "zod";
import { appRouter } from "../root";
import { findAdmin, updateAdmin } from "../services/admin";
+import { findServerById, updateServerById } from "../services/server";
import {
getDokployImage,
getDokployVersion,
@@ -63,54 +67,76 @@ export const settingsRouter = createTRPCRouter({
await execAsync(`docker service update --force ${stdout.trim()}`);
return true;
}),
- reloadTraefik: adminProcedure.mutation(async () => {
- try {
- await stopService("dokploy-traefik");
- await startService("dokploy-traefik");
- } catch (err) {
- console.error(err);
- }
+ reloadTraefik: adminProcedure
+ .input(apiServerSchema)
+ .mutation(async ({ input }) => {
+ try {
+ if (input?.serverId) {
+ await stopServiceRemote(input.serverId, "dokploy-traefik");
+ await startServiceRemote(input.serverId, "dokploy-traefik");
+ } else {
+ await stopService("dokploy-traefik");
+ await startService("dokploy-traefik");
+ }
+ } catch (err) {
+ console.error(err);
+ }
- return true;
- }),
+ return true;
+ }),
toggleDashboard: adminProcedure
.input(apiEnableDashboard)
.mutation(async ({ input }) => {
await initializeTraefik({
enableDashboard: input.enableDashboard,
+ serverId: input.serverId,
});
return true;
}),
- cleanUnusedImages: adminProcedure.mutation(async () => {
- await cleanUpUnusedImages();
- return true;
- }),
- cleanUnusedVolumes: adminProcedure.mutation(async () => {
- await cleanUpUnusedVolumes();
- return true;
- }),
- cleanStoppedContainers: adminProcedure.mutation(async () => {
- await cleanStoppedContainers();
- return true;
- }),
- cleanDockerBuilder: adminProcedure.mutation(async () => {
- await cleanUpDockerBuilder();
- }),
- cleanDockerPrune: adminProcedure.mutation(async () => {
- await cleanUpSystemPrune();
- await cleanUpDockerBuilder();
+ cleanUnusedImages: adminProcedure
+ .input(apiServerSchema)
+ .mutation(async ({ input }) => {
+ await cleanUpUnusedImages(input?.serverId);
+ return true;
+ }),
+ cleanUnusedVolumes: adminProcedure
+ .input(apiServerSchema)
+ .mutation(async ({ input }) => {
+ await cleanUpUnusedVolumes(input?.serverId);
+ return true;
+ }),
+ cleanStoppedContainers: adminProcedure
+ .input(apiServerSchema)
+ .mutation(async ({ input }) => {
+ await cleanStoppedContainers(input?.serverId);
+ return true;
+ }),
+ cleanDockerBuilder: adminProcedure
+ .input(apiServerSchema)
+ .mutation(async ({ input }) => {
+ await cleanUpDockerBuilder(input?.serverId);
+ }),
+ cleanDockerPrune: adminProcedure
+ .input(apiServerSchema)
+ .mutation(async ({ input }) => {
+ await cleanUpSystemPrune(input?.serverId);
+ await cleanUpDockerBuilder(input?.serverId);
- return true;
- }),
- cleanAll: adminProcedure.mutation(async () => {
- await cleanUpUnusedImages();
- await cleanUpDockerBuilder();
- await cleanUpSystemPrune();
+ return true;
+ }),
+ cleanAll: adminProcedure
+ .input(apiServerSchema)
+ .mutation(async ({ input }) => {
+ await cleanUpUnusedImages(input?.serverId);
+ await cleanStoppedContainers(input?.serverId);
+ await cleanUpDockerBuilder(input?.serverId);
+ await cleanUpSystemPrune(input?.serverId);
- return true;
- }),
+ return true;
+ }),
cleanMonitoring: adminProcedure.mutation(async () => {
+ const { MONITORING_PATH } = paths();
await recreateDirectory(MONITORING_PATH);
return true;
}),
@@ -152,25 +178,43 @@ export const settingsRouter = createTRPCRouter({
updateDockerCleanup: adminProcedure
.input(apiUpdateDockerCleanup)
.mutation(async ({ input, ctx }) => {
- await updateAdmin(ctx.user.authId, {
- enableDockerCleanup: input.enableDockerCleanup,
- });
-
- const admin = await findAdmin();
-
- if (admin.enableDockerCleanup) {
- scheduleJob("docker-cleanup", "0 0 * * *", async () => {
- console.log(
- `Docker Cleanup ${new Date().toLocaleString()}] Running...`,
- );
- await cleanUpUnusedImages();
- await cleanUpDockerBuilder();
- await cleanUpSystemPrune();
- await sendDockerCleanupNotifications();
+ if (input.serverId) {
+ await updateServerById(input.serverId, {
+ enableDockerCleanup: input.enableDockerCleanup,
});
+
+ const server = await findServerById(input.serverId);
+ if (server.enableDockerCleanup) {
+ scheduleJob("docker-cleanup", "0 0 * * *", async () => {
+ console.log(
+ `Docker Cleanup ${new Date().toLocaleString()}] Running...`,
+ );
+ await cleanUpUnusedImages(server.serverId);
+ await cleanUpDockerBuilder(server.serverId);
+ await cleanUpSystemPrune(server.serverId);
+ await sendDockerCleanupNotifications();
+ });
+ }
} else {
- const currentJob = scheduledJobs["docker-cleanup"];
- currentJob?.cancel();
+ await updateAdmin(ctx.user.authId, {
+ enableDockerCleanup: input.enableDockerCleanup,
+ });
+ const admin = await findAdmin();
+
+ if (admin.enableDockerCleanup) {
+ scheduleJob("docker-cleanup", "0 0 * * *", async () => {
+ console.log(
+ `Docker Cleanup ${new Date().toLocaleString()}] Running...`,
+ );
+ await cleanUpUnusedImages();
+ await cleanUpDockerBuilder();
+ await cleanUpSystemPrune();
+ await sendDockerCleanupNotifications();
+ });
+ } else {
+ const currentJob = scheduledJobs["docker-cleanup"];
+ currentJob?.cancel();
+ }
}
return true;
@@ -229,17 +273,24 @@ export const settingsRouter = createTRPCRouter({
getDokployVersion: adminProcedure.query(() => {
return getDokployVersion();
}),
- readDirectories: protectedProcedure.query(async ({ ctx }) => {
- if (ctx.user.rol === "user") {
- const canAccess = await canAccessToTraefikFiles(ctx.user.authId);
+ readDirectories: protectedProcedure
+ .input(apiServerSchema)
+ .query(async ({ ctx, input }) => {
+ try {
+ if (ctx.user.rol === "user") {
+ const canAccess = await canAccessToTraefikFiles(ctx.user.authId);
- if (!canAccess) {
- throw new TRPCError({ code: "UNAUTHORIZED" });
+ if (!canAccess) {
+ throw new TRPCError({ code: "UNAUTHORIZED" });
+ }
+ }
+ const { MAIN_TRAEFIK_PATH } = paths(!!input?.serverId);
+ const result = await readDirectory(MAIN_TRAEFIK_PATH, input?.serverId);
+ return result || [];
+ } catch (error) {
+ throw error;
}
- }
- const result = readDirectory(MAIN_TRAEFIK_PATH);
- return result || [];
- }),
+ }),
updateTraefikFile: protectedProcedure
.input(apiModifyTraefikConfig)
@@ -251,7 +302,11 @@ export const settingsRouter = createTRPCRouter({
throw new TRPCError({ code: "UNAUTHORIZED" });
}
}
- writeTraefikConfigInPath(input.path, input.traefikConfig);
+ await writeTraefikConfigInPath(
+ input.path,
+ input.traefikConfig,
+ input?.serverId,
+ );
return true;
}),
@@ -265,7 +320,7 @@ export const settingsRouter = createTRPCRouter({
throw new TRPCError({ code: "UNAUTHORIZED" });
}
}
- return readConfigInPath(input.path);
+ return readConfigInPath(input.path, input.serverId);
}),
getIp: protectedProcedure.query(async () => {
const admin = await findAdmin();
@@ -322,39 +377,56 @@ export const settingsRouter = createTRPCRouter({
return openApiDocument;
},
),
- readTraefikEnv: adminProcedure.query(async () => {
- const { stdout } = await execAsync(
- "docker service inspect --format='{{range .Spec.TaskTemplate.ContainerSpec.Env}}{{println .}}{{end}}' dokploy-traefik",
- );
+ readTraefikEnv: adminProcedure
+ .input(apiServerSchema)
+ .query(async ({ input }) => {
+ const command =
+ "docker service inspect --format='{{range .Spec.TaskTemplate.ContainerSpec.Env}}{{println .}}{{end}}' dokploy-traefik";
- return stdout.trim();
- }),
+ if (input?.serverId) {
+ const result = await execAsyncRemote(input.serverId, command);
+ return result.stdout.trim();
+ }
+ const result = await execAsync(command);
+ return result.stdout.trim();
+ }),
writeTraefikEnv: adminProcedure
- .input(z.object({ env: z.string() }))
+ .input(z.object({ env: z.string(), serverId: z.string().optional() }))
.mutation(async ({ input }) => {
const envs = prepareEnvironmentVariables(input.env);
await initializeTraefik({
env: envs,
+ serverId: input.serverId,
});
return true;
}),
- haveTraefikDashboardPortEnabled: adminProcedure.query(async () => {
- const { stdout } = await execAsync(
- "docker service inspect --format='{{json .Endpoint.Ports}}' dokploy-traefik",
- );
+ haveTraefikDashboardPortEnabled: adminProcedure
+ .input(apiServerSchema)
+ .query(async ({ input }) => {
+ const command = `docker service inspect --format='{{json .Endpoint.Ports}}' dokploy-traefik`;
- const parsed: any[] = JSON.parse(stdout.trim());
-
- for (const port of parsed) {
- if (port.PublishedPort === 8080) {
- return true;
+ let stdout = "";
+ if (input?.serverId) {
+ const result = await execAsyncRemote(input.serverId, command);
+ stdout = result.stdout;
+ } else {
+ const result = await execAsync(
+ "docker service inspect --format='{{json .Endpoint.Ports}}' dokploy-traefik",
+ );
+ stdout = result.stdout;
}
- }
- return false;
- }),
+ const parsed: any[] = JSON.parse(stdout.trim());
+ for (const port of parsed) {
+ if (port.PublishedPort === 8080) {
+ return true;
+ }
+ }
+
+ return false;
+ }),
readStatsLogs: adminProcedure
.meta({
diff --git a/apps/dokploy/server/api/services/admin.ts b/apps/dokploy/server/api/services/admin.ts
index 13f66f1c8..b9394bc53 100644
--- a/apps/dokploy/server/api/services/admin.ts
+++ b/apps/dokploy/server/api/services/admin.ts
@@ -8,11 +8,9 @@ import {
} from "@/server/db/schema";
import { TRPCError } from "@trpc/server";
import * as bcrypt from "bcrypt";
-import { isAfter } from "date-fns";
import { eq } from "drizzle-orm";
export type Admin = typeof admins.$inferSelect;
-
export const createInvitation = async (
input: typeof apiCreateUserInvitation._type,
) => {
diff --git a/apps/dokploy/server/api/services/application.ts b/apps/dokploy/server/api/services/application.ts
index 1e7570274..7f0403586 100644
--- a/apps/dokploy/server/api/services/application.ts
+++ b/apps/dokploy/server/api/services/application.ts
@@ -1,27 +1,42 @@
import { docker } from "@/server/constants";
import { db } from "@/server/db";
-import {
- type apiCreateApplication,
- applications,
- domains,
-} from "@/server/db/schema";
+import { type apiCreateApplication, applications } from "@/server/db/schema";
import { generateAppName } from "@/server/db/schema/utils";
import { getAdvancedStats } from "@/server/monitoring/utilts";
-import { buildApplication } from "@/server/utils/builders";
-import { buildDocker } from "@/server/utils/providers/docker";
-import { cloneGitRepository } from "@/server/utils/providers/git";
-import { cloneGithubRepository } from "@/server/utils/providers/github";
+import {
+ buildApplication,
+ getBuildCommand,
+ mechanizeDockerContainer,
+} from "@/server/utils/builders";
+import { sendBuildErrorNotifications } from "@/server/utils/notifications/build-error";
+import { sendBuildSuccessNotifications } from "@/server/utils/notifications/build-success";
+import { execAsyncRemote } from "@/server/utils/process/execAsync";
+import {
+ cloneBitbucketRepository,
+ getBitbucketCloneCommand,
+} from "@/server/utils/providers/bitbucket";
+import {
+ buildDocker,
+ buildRemoteDocker,
+} from "@/server/utils/providers/docker";
+import {
+ cloneGitRepository,
+ getCustomGitCloneCommand,
+} from "@/server/utils/providers/git";
+import {
+ cloneGithubRepository,
+ getGithubCloneCommand,
+} from "@/server/utils/providers/github";
+import {
+ cloneGitlabRepository,
+ getGitlabCloneCommand,
+} from "@/server/utils/providers/gitlab";
import { createTraefikConfig } from "@/server/utils/traefik/application";
import { generatePassword } from "@/templates/utils";
import { TRPCError } from "@trpc/server";
import { eq } from "drizzle-orm";
-import { findAdmin, getDokployUrl } from "./admin";
+import { getDokployUrl } from "./admin";
import { createDeployment, updateDeploymentStatus } from "./deployment";
-
-import { sendBuildErrorNotifications } from "@/server/utils/notifications/build-error";
-import { sendBuildSuccessNotifications } from "@/server/utils/notifications/build-success";
-import { cloneBitbucketRepository } from "@/server/utils/providers/bitbucket";
-import { cloneGitlabRepository } from "@/server/utils/providers/gitlab";
import { validUniqueServerAppName } from "./project";
export type Application = typeof applications.$inferSelect;
@@ -80,6 +95,7 @@ export const findApplicationById = async (applicationId: string) => {
gitlab: true,
github: true,
bitbucket: true,
+ server: true,
},
});
if (!application) {
@@ -164,6 +180,7 @@ export const deployApplication = async ({
} else if (application.sourceType === "drop") {
await buildApplication(application, deployment.logPath);
}
+
await updateDeploymentStatus(deployment.deploymentId, "done");
await updateApplicationStatus(applicationId, "done");
@@ -240,6 +257,122 @@ export const rebuildApplication = async ({
return true;
};
+export const deployRemoteApplication = async ({
+ applicationId,
+ titleLog = "Manual deployment",
+ descriptionLog = "",
+}: {
+ applicationId: string;
+ titleLog: string;
+ descriptionLog: string;
+}) => {
+ const application = await findApplicationById(applicationId);
+ const buildLink = `${await getDokployUrl()}/dashboard/project/${application.projectId}/services/application/${application.applicationId}?tab=deployments`;
+ const deployment = await createDeployment({
+ applicationId: applicationId,
+ title: titleLog,
+ description: descriptionLog,
+ });
+
+ try {
+ if (application.serverId) {
+ let command = "set -e;";
+ if (application.sourceType === "github") {
+ command += await getGithubCloneCommand(application, deployment.logPath);
+ } else if (application.sourceType === "gitlab") {
+ command += await getGitlabCloneCommand(application, deployment.logPath);
+ } else if (application.sourceType === "bitbucket") {
+ command += await getBitbucketCloneCommand(
+ application,
+ deployment.logPath,
+ );
+ } else if (application.sourceType === "git") {
+ command += await getCustomGitCloneCommand(
+ application,
+ deployment.logPath,
+ );
+ } else if (application.sourceType === "docker") {
+ command += await buildRemoteDocker(application, deployment.logPath);
+ }
+
+ if (application.sourceType !== "docker") {
+ command += getBuildCommand(application, deployment.logPath);
+ }
+ await execAsyncRemote(application.serverId, command);
+ await mechanizeDockerContainer(application);
+ }
+
+ await updateDeploymentStatus(deployment.deploymentId, "done");
+ await updateApplicationStatus(applicationId, "done");
+
+ await sendBuildSuccessNotifications({
+ projectName: application.project.name,
+ applicationName: application.name,
+ applicationType: "application",
+ buildLink,
+ });
+ } catch (error) {
+ await updateDeploymentStatus(deployment.deploymentId, "error");
+ await updateApplicationStatus(applicationId, "error");
+ await sendBuildErrorNotifications({
+ projectName: application.project.name,
+ applicationName: application.name,
+ applicationType: "application",
+ // @ts-ignore
+ errorMessage: error?.message || "Error to build",
+ buildLink,
+ });
+
+ console.log(
+ "Error on ",
+ application.buildType,
+ "/",
+ application.sourceType,
+ error,
+ );
+
+ throw error;
+ }
+
+ return true;
+};
+
+export const rebuildRemoteApplication = async ({
+ applicationId,
+ titleLog = "Rebuild deployment",
+ descriptionLog = "",
+}: {
+ applicationId: string;
+ titleLog: string;
+ descriptionLog: string;
+}) => {
+ const application = await findApplicationById(applicationId);
+ const deployment = await createDeployment({
+ applicationId: applicationId,
+ title: titleLog,
+ description: descriptionLog,
+ });
+
+ try {
+ if (application.serverId) {
+ if (application.sourceType !== "docker") {
+ let command = "set -e;";
+ command += getBuildCommand(application, deployment.logPath);
+ await execAsyncRemote(application.serverId, command);
+ }
+ await mechanizeDockerContainer(application);
+ }
+ await updateDeploymentStatus(deployment.deploymentId, "done");
+ await updateApplicationStatus(applicationId, "done");
+ } catch (error) {
+ await updateDeploymentStatus(deployment.deploymentId, "error");
+ await updateApplicationStatus(applicationId, "error");
+ throw error;
+ }
+
+ return true;
+};
+
export const getApplicationStats = async (appName: string) => {
const filter = {
status: ["running"],
diff --git a/apps/dokploy/server/api/services/certificate.ts b/apps/dokploy/server/api/services/certificate.ts
index 6aca1b52c..3ed1dcd19 100644
--- a/apps/dokploy/server/api/services/certificate.ts
+++ b/apps/dokploy/server/api/services/certificate.ts
@@ -1,6 +1,6 @@
import fs from "node:fs";
import path from "node:path";
-import { CERTIFICATES_PATH } from "@/server/constants";
+import { paths } from "@/server/constants";
import { db } from "@/server/db";
import { type apiCreateCertificate, certificates } from "@/server/db/schema";
import { removeDirectoryIfExistsContent } from "@/server/utils/filesystem/directory";
@@ -50,6 +50,7 @@ export const createCertificate = async (
};
export const removeCertificateById = async (certificateId: string) => {
+ const { CERTIFICATES_PATH } = paths();
const certificate = await findCertificateById(certificateId);
const certDir = path.join(CERTIFICATES_PATH, certificate.certificatePath);
@@ -74,6 +75,7 @@ export const findCertificates = async () => {
};
const createCertificateFiles = (certificate: Certificate) => {
+ const { CERTIFICATES_PATH } = paths();
const dockerPath = "/etc/traefik";
const certDir = path.join(CERTIFICATES_PATH, certificate.certificatePath);
const crtPath = path.join(certDir, "chain.crt");
diff --git a/apps/dokploy/server/api/services/compose.ts b/apps/dokploy/server/api/services/compose.ts
index 2db9a343d..13c728f5f 100644
--- a/apps/dokploy/server/api/services/compose.ts
+++ b/apps/dokploy/server/api/services/compose.ts
@@ -1,19 +1,43 @@
import { join } from "node:path";
-import { COMPOSE_PATH } from "@/server/constants";
+import { paths } from "@/server/constants";
import { db } from "@/server/db";
import { type apiCreateCompose, compose } from "@/server/db/schema";
import { generateAppName } from "@/server/db/schema/utils";
-import { buildCompose } from "@/server/utils/builders/compose";
+import {
+ buildCompose,
+ getBuildComposeCommand,
+} from "@/server/utils/builders/compose";
import { randomizeSpecificationFile } from "@/server/utils/docker/compose";
-import { cloneCompose, loadDockerCompose } from "@/server/utils/docker/domain";
+import {
+ cloneCompose,
+ cloneComposeRemote,
+ loadDockerCompose,
+ loadDockerComposeRemote,
+} from "@/server/utils/docker/domain";
+import type { ComposeSpecification } from "@/server/utils/docker/types";
import { sendBuildErrorNotifications } from "@/server/utils/notifications/build-error";
import { sendBuildSuccessNotifications } from "@/server/utils/notifications/build-success";
-import { execAsync } from "@/server/utils/process/execAsync";
-import { cloneBitbucketRepository } from "@/server/utils/providers/bitbucket";
-import { cloneGitRepository } from "@/server/utils/providers/git";
-import { cloneGithubRepository } from "@/server/utils/providers/github";
-import { cloneGitlabRepository } from "@/server/utils/providers/gitlab";
-import { createComposeFile } from "@/server/utils/providers/raw";
+import { execAsync, execAsyncRemote } from "@/server/utils/process/execAsync";
+import {
+ cloneBitbucketRepository,
+ getBitbucketCloneCommand,
+} from "@/server/utils/providers/bitbucket";
+import {
+ cloneGitRepository,
+ getCustomGitCloneCommand,
+} from "@/server/utils/providers/git";
+import {
+ cloneGithubRepository,
+ getGithubCloneCommand,
+} from "@/server/utils/providers/github";
+import {
+ cloneGitlabRepository,
+ getGitlabCloneCommand,
+} from "@/server/utils/providers/gitlab";
+import {
+ createComposeFile,
+ getCreateComposeFileCommand,
+} from "@/server/utils/providers/raw";
import { generatePassword } from "@/templates/utils";
import { TRPCError } from "@trpc/server";
import { eq } from "drizzle-orm";
@@ -36,6 +60,7 @@ export const createCompose = async (input: typeof apiCreateCompose._type) => {
});
}
}
+
const newDestination = await db
.insert(compose)
.values({
@@ -97,6 +122,7 @@ export const findComposeById = async (composeId: string) => {
github: true,
gitlab: true,
bitbucket: true,
+ server: true,
},
});
if (!result) {
@@ -115,10 +141,20 @@ export const loadServices = async (
const compose = await findComposeById(composeId);
if (type === "fetch") {
- await cloneCompose(compose);
+ if (compose.serverId) {
+ await cloneComposeRemote(compose);
+ } else {
+ await cloneCompose(compose);
+ }
}
- let composeData = await loadDockerCompose(compose);
+ let composeData: ComposeSpecification | null;
+
+ if (compose.serverId) {
+ composeData = await loadDockerComposeRemote(compose);
+ } else {
+ composeData = await loadDockerCompose(compose);
+ }
if (compose.randomize && composeData) {
const randomizedCompose = randomizeSpecificationFile(
@@ -230,7 +266,129 @@ export const rebuildCompose = async ({
});
try {
- await buildCompose(compose, deployment.logPath);
+ if (compose.serverId) {
+ await getBuildComposeCommand(compose, deployment.logPath);
+ } else {
+ await buildCompose(compose, deployment.logPath);
+ }
+
+ await updateDeploymentStatus(deployment.deploymentId, "done");
+ await updateCompose(composeId, {
+ composeStatus: "done",
+ });
+ } catch (error) {
+ await updateDeploymentStatus(deployment.deploymentId, "error");
+ await updateCompose(composeId, {
+ composeStatus: "error",
+ });
+ throw error;
+ }
+
+ return true;
+};
+
+export const deployRemoteCompose = async ({
+ composeId,
+ titleLog = "Manual deployment",
+ descriptionLog = "",
+}: {
+ composeId: string;
+ titleLog: string;
+ descriptionLog: string;
+}) => {
+ const compose = await findComposeById(composeId);
+ const buildLink = `${await getDokployUrl()}/dashboard/project/${compose.projectId}/services/compose/${compose.composeId}?tab=deployments`;
+ const deployment = await createDeploymentCompose({
+ composeId: composeId,
+ title: titleLog,
+ description: descriptionLog,
+ });
+ try {
+ if (compose.serverId) {
+ let command = "set -e;";
+
+ if (compose.sourceType === "github") {
+ command += await getGithubCloneCommand(
+ compose,
+ deployment.logPath,
+ true,
+ );
+ } else if (compose.sourceType === "gitlab") {
+ command += await getGitlabCloneCommand(
+ compose,
+ deployment.logPath,
+ true,
+ );
+ } else if (compose.sourceType === "bitbucket") {
+ command += await getBitbucketCloneCommand(
+ compose,
+ deployment.logPath,
+ true,
+ );
+ } else if (compose.sourceType === "git") {
+ command += await getCustomGitCloneCommand(
+ compose,
+ deployment.logPath,
+ true,
+ );
+ } else if (compose.sourceType === "raw") {
+ command += getCreateComposeFileCommand(compose, deployment.logPath);
+ }
+
+ await execAsyncRemote(compose.serverId, command);
+ await getBuildComposeCommand(compose, deployment.logPath);
+ }
+
+ await updateDeploymentStatus(deployment.deploymentId, "done");
+ await updateCompose(composeId, {
+ composeStatus: "done",
+ });
+
+ await sendBuildSuccessNotifications({
+ projectName: compose.project.name,
+ applicationName: compose.name,
+ applicationType: "compose",
+ buildLink,
+ });
+ } catch (error) {
+ console.log(error);
+ await updateDeploymentStatus(deployment.deploymentId, "error");
+ await updateCompose(composeId, {
+ composeStatus: "error",
+ });
+ await sendBuildErrorNotifications({
+ projectName: compose.project.name,
+ applicationName: compose.name,
+ applicationType: "compose",
+ // @ts-ignore
+ errorMessage: error?.message || "Error to build",
+ buildLink,
+ });
+ throw error;
+ }
+};
+
+export const rebuildRemoteCompose = async ({
+ composeId,
+ titleLog = "Rebuild deployment",
+ descriptionLog = "",
+}: {
+ composeId: string;
+ titleLog: string;
+ descriptionLog: string;
+}) => {
+ const compose = await findComposeById(composeId);
+ const deployment = await createDeploymentCompose({
+ composeId: composeId,
+ title: titleLog,
+ description: descriptionLog,
+ });
+
+ try {
+ if (compose.serverId) {
+ await getBuildComposeCommand(compose, deployment.logPath);
+ }
+
await updateDeploymentStatus(deployment.deploymentId, "done");
await updateCompose(composeId, {
composeStatus: "done",
@@ -248,16 +406,28 @@ export const rebuildCompose = async ({
export const removeCompose = async (compose: Compose) => {
try {
+ const { COMPOSE_PATH } = paths(!!compose.serverId);
const projectPath = join(COMPOSE_PATH, compose.appName);
if (compose.composeType === "stack") {
- await execAsync(`docker stack rm ${compose.appName}`, {
+ const command = `cd ${projectPath} && docker stack rm ${compose.appName} && rm -rf ${projectPath}`;
+ if (compose.serverId) {
+ await execAsyncRemote(compose.serverId, command);
+ } else {
+ await execAsync(command);
+ }
+ await execAsync(command, {
cwd: projectPath,
});
} else {
- await execAsync(`docker compose -p ${compose.appName} down`, {
- cwd: projectPath,
- });
+ const command = `cd ${projectPath} && docker compose -p ${compose.appName} down && rm -rf ${projectPath}`;
+ if (compose.serverId) {
+ await execAsyncRemote(compose.serverId, command);
+ } else {
+ await execAsync(command, {
+ cwd: projectPath,
+ });
+ }
}
} catch (error) {
throw error;
@@ -269,10 +439,18 @@ export const removeCompose = async (compose: Compose) => {
export const stopCompose = async (composeId: string) => {
const compose = await findComposeById(composeId);
try {
+ const { COMPOSE_PATH } = paths(!!compose.serverId);
if (compose.composeType === "docker-compose") {
- await execAsync(`docker compose -p ${compose.appName} stop`, {
- cwd: join(COMPOSE_PATH, compose.appName),
- });
+ if (compose.serverId) {
+ await execAsyncRemote(
+ compose.serverId,
+ `cd ${join(COMPOSE_PATH, compose.appName)} && docker compose -p ${compose.appName} stop`,
+ );
+ } else {
+ await execAsync(`docker compose -p ${compose.appName} stop`, {
+ cwd: join(COMPOSE_PATH, compose.appName),
+ });
+ }
}
await updateCompose(composeId, {
diff --git a/apps/dokploy/server/api/services/deployment.ts b/apps/dokploy/server/api/services/deployment.ts
index f72c70f65..5ea39c577 100644
--- a/apps/dokploy/server/api/services/deployment.ts
+++ b/apps/dokploy/server/api/services/deployment.ts
@@ -1,18 +1,26 @@
import { existsSync, promises as fsPromises } from "node:fs";
import path from "node:path";
-import { LOGS_PATH } from "@/server/constants";
+import { paths } from "@/server/constants";
import { db } from "@/server/db";
import {
type apiCreateDeployment,
type apiCreateDeploymentCompose,
+ type apiCreateDeploymentServer,
deployments,
} from "@/server/db/schema";
import { removeDirectoryIfExistsContent } from "@/server/utils/filesystem/directory";
import { TRPCError } from "@trpc/server";
import { format } from "date-fns";
import { desc, eq } from "drizzle-orm";
-import { type Application, findApplicationById } from "./application";
-import { type Compose, findComposeById } from "./compose";
+import {
+ type Application,
+ findApplicationById,
+ updateApplicationStatus,
+} from "./application";
+import { type Compose, findComposeById, updateCompose } from "./compose";
+import { type Server, findServerById } from "./server";
+
+import { execAsyncRemote } from "@/server/utils/process/execAsync";
export type Deployment = typeof deployments.$inferSelect;
@@ -38,17 +46,31 @@ export const createDeployment = async (
"deploymentId" | "createdAt" | "status" | "logPath"
>,
) => {
- try {
- const application = await findApplicationById(deployment.applicationId);
+ const application = await findApplicationById(deployment.applicationId);
- await removeLastTenDeployments(deployment.applicationId);
+ try {
+ // await removeLastTenDeployments(deployment.applicationId);
+ const { LOGS_PATH } = paths(!!application.serverId);
const formattedDateTime = format(new Date(), "yyyy-MM-dd:HH:mm:ss");
const fileName = `${application.appName}-${formattedDateTime}.log`;
const logFilePath = path.join(LOGS_PATH, application.appName, fileName);
- await fsPromises.mkdir(path.join(LOGS_PATH, application.appName), {
- recursive: true,
- });
- await fsPromises.writeFile(logFilePath, "Initializing deployment");
+
+ if (application.serverId) {
+ const server = await findServerById(application.serverId);
+
+ const command = `
+ mkdir -p ${LOGS_PATH}/${application.appName};
+ echo "Initializing deployment" >> ${logFilePath};
+ `;
+
+ await execAsyncRemote(server.serverId, command);
+ } else {
+ await fsPromises.mkdir(path.join(LOGS_PATH, application.appName), {
+ recursive: true,
+ });
+ await fsPromises.writeFile(logFilePath, "Initializing deployment");
+ }
+
const deploymentCreate = await db
.insert(deployments)
.values({
@@ -67,6 +89,7 @@ export const createDeployment = async (
}
return deploymentCreate[0];
} catch (error) {
+ await updateApplicationStatus(application.applicationId, "error");
console.log(error);
throw new TRPCError({
code: "BAD_REQUEST",
@@ -81,17 +104,30 @@ export const createDeploymentCompose = async (
"deploymentId" | "createdAt" | "status" | "logPath"
>,
) => {
+ const compose = await findComposeById(deployment.composeId);
try {
- const compose = await findComposeById(deployment.composeId);
-
- await removeLastTenComposeDeployments(deployment.composeId);
+ // await removeLastTenComposeDeployments(deployment.composeId);
+ const { LOGS_PATH } = paths(!!compose.serverId);
const formattedDateTime = format(new Date(), "yyyy-MM-dd:HH:mm:ss");
const fileName = `${compose.appName}-${formattedDateTime}.log`;
const logFilePath = path.join(LOGS_PATH, compose.appName, fileName);
- await fsPromises.mkdir(path.join(LOGS_PATH, compose.appName), {
- recursive: true,
- });
- await fsPromises.writeFile(logFilePath, "Initializing deployment");
+
+ if (compose.serverId) {
+ const server = await findServerById(compose.serverId);
+
+ const command = `
+mkdir -p ${LOGS_PATH}/${compose.appName};
+echo "Initializing deployment" >> ${logFilePath};
+`;
+
+ await execAsyncRemote(server.serverId, command);
+ } else {
+ await fsPromises.mkdir(path.join(LOGS_PATH, compose.appName), {
+ recursive: true,
+ });
+ await fsPromises.writeFile(logFilePath, "Initializing deployment");
+ }
+
const deploymentCreate = await db
.insert(deployments)
.values({
@@ -110,6 +146,9 @@ export const createDeploymentCompose = async (
}
return deploymentCreate[0];
} catch (error) {
+ await updateCompose(compose.composeId, {
+ composeStatus: "error",
+ });
console.log(error);
throw new TRPCError({
code: "BAD_REQUEST",
@@ -178,15 +217,26 @@ const removeLastTenComposeDeployments = async (composeId: string) => {
export const removeDeployments = async (application: Application) => {
const { appName, applicationId } = application;
+ const { LOGS_PATH } = paths(!!application.serverId);
const logsPath = path.join(LOGS_PATH, appName);
- await removeDirectoryIfExistsContent(logsPath);
+ if (application.serverId) {
+ await execAsyncRemote(application.serverId, `rm -rf ${logsPath}`);
+ } else {
+ await removeDirectoryIfExistsContent(logsPath);
+ }
await removeDeploymentsByApplicationId(applicationId);
};
export const removeDeploymentsByComposeId = async (compose: Compose) => {
const { appName } = compose;
+ const { LOGS_PATH } = paths(!!compose.serverId);
const logsPath = path.join(LOGS_PATH, appName);
- await removeDirectoryIfExistsContent(logsPath);
+ if (compose.serverId) {
+ await execAsyncRemote(compose.serverId, `rm -rf ${logsPath}`);
+ } else {
+ await removeDirectoryIfExistsContent(logsPath);
+ }
+
await db
.delete(deployments)
.where(eq(deployments.composeId, compose.composeId))
@@ -240,3 +290,83 @@ export const updateDeploymentStatus = async (
return application;
};
+
+export const createServerDeployment = async (
+ deployment: Omit<
+ typeof apiCreateDeploymentServer._type,
+ "deploymentId" | "createdAt" | "status" | "logPath"
+ >,
+) => {
+ try {
+ const { LOGS_PATH } = paths();
+
+ const server = await findServerById(deployment.serverId);
+ await removeLastFiveDeployments(deployment.serverId);
+ const formattedDateTime = format(new Date(), "yyyy-MM-dd:HH:mm:ss");
+ const fileName = `${server.appName}-${formattedDateTime}.log`;
+ const logFilePath = path.join(LOGS_PATH, server.appName, fileName);
+ await fsPromises.mkdir(path.join(LOGS_PATH, server.appName), {
+ recursive: true,
+ });
+ await fsPromises.writeFile(logFilePath, "Initializing Setup Server");
+ const deploymentCreate = await db
+ .insert(deployments)
+ .values({
+ serverId: server.serverId,
+ title: deployment.title || "Deployment",
+ description: deployment.description || "",
+ status: "running",
+ logPath: logFilePath,
+ })
+ .returning();
+ if (deploymentCreate.length === 0 || !deploymentCreate[0]) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Error to create the deployment",
+ });
+ }
+ return deploymentCreate[0];
+ } catch (error) {
+ console.log(error);
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Error to create the deployment",
+ });
+ }
+};
+
+export const removeLastFiveDeployments = async (serverId: string) => {
+ const deploymentList = await db.query.deployments.findMany({
+ where: eq(deployments.serverId, serverId),
+ orderBy: desc(deployments.createdAt),
+ });
+ if (deploymentList.length >= 5) {
+ const deploymentsToDelete = deploymentList.slice(4);
+ for (const oldDeployment of deploymentsToDelete) {
+ const logPath = path.join(oldDeployment.logPath);
+ if (existsSync(logPath)) {
+ await fsPromises.unlink(logPath);
+ }
+ await removeDeployment(oldDeployment.deploymentId);
+ }
+ }
+};
+
+export const removeDeploymentsByServerId = async (server: Server) => {
+ const { LOGS_PATH } = paths();
+ const { appName } = server;
+ const logsPath = path.join(LOGS_PATH, appName);
+ await removeDirectoryIfExistsContent(logsPath);
+ await db
+ .delete(deployments)
+ .where(eq(deployments.serverId, server.serverId))
+ .returning();
+};
+
+export const findAllDeploymentsByServerId = async (serverId: string) => {
+ const deploymentsList = await db.query.deployments.findMany({
+ where: eq(deployments.serverId, serverId),
+ orderBy: desc(deployments.createdAt),
+ });
+ return deploymentsList;
+};
diff --git a/apps/dokploy/server/api/services/docker.ts b/apps/dokploy/server/api/services/docker.ts
index 379bebe4c..d611a11d1 100644
--- a/apps/dokploy/server/api/services/docker.ts
+++ b/apps/dokploy/server/api/services/docker.ts
@@ -1,11 +1,22 @@
-import { execAsync } from "@/server/utils/process/execAsync";
+import { execAsync, execAsyncRemote } from "@/server/utils/process/execAsync";
-export const getContainers = async () => {
+export const getContainers = async (serverId?: string | null) => {
try {
- const { stdout, stderr } = await execAsync(
- "docker ps -a --format 'CONTAINER ID : {{.ID}} | Name: {{.Names}} | Image: {{.Image}} | Ports: {{.Ports}} | State: {{.State}} | Status: {{.Status}}'",
- );
+ const command =
+ "docker ps -a --format 'CONTAINER ID : {{.ID}} | Name: {{.Names}} | Image: {{.Image}} | Ports: {{.Ports}} | State: {{.State}} | Status: {{.Status}}'";
+ let stdout = "";
+ let stderr = "";
+ if (serverId) {
+ const result = await execAsyncRemote(serverId, command);
+
+ stdout = result.stdout;
+ stderr = result.stderr;
+ } else {
+ const result = await execAsync(command);
+ stdout = result.stdout;
+ stderr = result.stderr;
+ }
if (stderr) {
console.error(`Error: ${stderr}`);
return;
@@ -41,19 +52,36 @@ export const getContainers = async () => {
ports,
state,
status,
+ serverId,
};
})
.filter((container) => !container.name.includes("dokploy"));
return containers;
- } catch (error) {}
+ } catch (error) {
+ console.error(error);
+
+ return [];
+ }
};
-export const getConfig = async (containerId: string) => {
+export const getConfig = async (
+ containerId: string,
+ serverId?: string | null,
+) => {
try {
- const { stdout, stderr } = await execAsync(
- `docker inspect ${containerId} --format='{{json .}}'`,
- );
+ const command = `docker inspect ${containerId} --format='{{json .}}'`;
+ let stdout = "";
+ let stderr = "";
+ if (serverId) {
+ const result = await execAsyncRemote(serverId, command);
+ stdout = result.stdout;
+ stderr = result.stderr;
+ } else {
+ const result = await execAsync(command);
+ stdout = result.stdout;
+ stderr = result.stderr;
+ }
if (stderr) {
console.error(`Error: ${stderr}`);
@@ -69,25 +97,39 @@ export const getConfig = async (containerId: string) => {
export const getContainersByAppNameMatch = async (
appName: string,
appType?: "stack" | "docker-compose",
+ serverId?: string,
) => {
try {
+ let result: string[] = [];
const cmd =
"docker ps -a --format 'CONTAINER ID : {{.ID}} | Name: {{.Names}} | State: {{.State}}'";
- const { stdout, stderr } = await execAsync(
+ const command =
appType === "docker-compose"
? `${cmd} --filter='label=com.docker.compose.project=${appName}'`
- : `${cmd} | grep ${appName}`,
- );
+ : `${cmd} | grep ${appName}`;
+ if (serverId) {
+ const { stdout, stderr } = await execAsyncRemote(serverId, command);
- if (stderr) {
- return [];
+ if (stderr) {
+ return [];
+ }
+
+ if (!stdout) return [];
+ result = stdout.trim().split("\n");
+ } else {
+ const { stdout, stderr } = await execAsync(command);
+
+ if (stderr) {
+ return [];
+ }
+
+ if (!stdout) return [];
+
+ result = stdout.trim().split("\n");
}
- if (!stdout) return [];
-
- const lines = stdout.trim().split("\n");
- const containers = lines.map((line) => {
+ const containers = result.map((line) => {
const parts = line.split(" | ");
const containerId = parts[0]
? parts[0].replace("CONTAINER ID : ", "").trim()
@@ -112,12 +154,24 @@ export const getContainersByAppNameMatch = async (
return [];
};
-export const getContainersByAppLabel = async (appName: string) => {
+export const getContainersByAppLabel = async (
+ appName: string,
+ serverId?: string,
+) => {
try {
- const { stdout, stderr } = await execAsync(
- `docker ps --filter "label=com.docker.swarm.service.name=${appName}" --format 'CONTAINER ID : {{.ID}} | Name: {{.Names}} | State: {{.State}}'`,
- );
+ let stdout = "";
+ let stderr = "";
+ const command = `docker ps --filter "label=com.docker.swarm.service.name=${appName}" --format 'CONTAINER ID : {{.ID}} | Name: {{.Names}} | State: {{.State}}'`;
+ if (serverId) {
+ const result = await execAsyncRemote(serverId, command);
+ stdout = result.stdout;
+ stderr = result.stderr;
+ } else {
+ const result = await execAsync(command);
+ stdout = result.stdout;
+ stderr = result.stderr;
+ }
if (stderr) {
console.error(`Error: ${stderr}`);
return;
diff --git a/apps/dokploy/server/api/services/domain.ts b/apps/dokploy/server/api/services/domain.ts
index 927b144db..df180a450 100644
--- a/apps/dokploy/server/api/services/domain.ts
+++ b/apps/dokploy/server/api/services/domain.ts
@@ -1,15 +1,12 @@
import { db } from "@/server/db";
-import {
- type apiCreateDomain,
- type apiFindDomainByApplication,
- domains,
-} from "@/server/db/schema";
+import { type apiCreateDomain, domains } from "@/server/db/schema";
import { manageDomain } from "@/server/utils/traefik/domain";
import { generateRandomDomain } from "@/templates/utils";
import { TRPCError } from "@trpc/server";
import { eq } from "drizzle-orm";
-import { findAdmin } from "./admin";
+import { findAdmin, findAdminById } from "./admin";
import { findApplicationById } from "./application";
+import { findServerById } from "./server";
export type Domain = typeof domains.$inferSelect;
@@ -41,10 +38,28 @@ export const createDomain = async (input: typeof apiCreateDomain._type) => {
return result;
};
-export const generateTraefikMeDomain = async (appName: string) => {
- const admin = await findAdmin();
+export const generateTraefikMeDomain = async (
+ appName: string,
+ adminId: string,
+ serverId?: string,
+) => {
+ if (serverId) {
+ const server = await findServerById(serverId);
+ return generateRandomDomain({
+ serverIp: server.ipAddress,
+ projectName: appName,
+ });
+ }
+
+ if (process.env.NODE_ENV === "development") {
+ return generateRandomDomain({
+ serverIp: "",
+ projectName: appName,
+ });
+ }
+ const admin = await findAdminById(adminId);
return generateRandomDomain({
- serverIp: admin.serverIp || "",
+ serverIp: admin?.serverIp || "",
projectName: appName,
});
};
diff --git a/apps/dokploy/server/api/services/mariadb.ts b/apps/dokploy/server/api/services/mariadb.ts
index 08e69dc2c..2fdb3a7fe 100644
--- a/apps/dokploy/server/api/services/mariadb.ts
+++ b/apps/dokploy/server/api/services/mariadb.ts
@@ -9,6 +9,8 @@ import { TRPCError } from "@trpc/server";
import { eq, getTableColumns } from "drizzle-orm";
import { validUniqueServerAppName } from "./project";
+import { execAsyncRemote } from "@/server/utils/process/execAsync";
+
export type Mariadb = typeof mariadb.$inferSelect;
export const createMariadb = async (input: typeof apiCreateMariaDB._type) => {
@@ -56,6 +58,7 @@ export const findMariadbById = async (mariadbId: string) => {
with: {
project: true,
mounts: true,
+ server: true,
backups: {
with: {
destination: true,
@@ -118,7 +121,15 @@ export const findMariadbByBackupId = async (backupId: string) => {
export const deployMariadb = async (mariadbId: string) => {
const mariadb = await findMariadbById(mariadbId);
try {
- await pullImage(mariadb.dockerImage);
+ if (mariadb.serverId) {
+ await execAsyncRemote(
+ mariadb.serverId,
+ `docker pull ${mariadb.dockerImage}`,
+ );
+ } else {
+ await pullImage(mariadb.dockerImage);
+ }
+
await buildMariadb(mariadb);
await updateMariadbById(mariadbId, {
applicationStatus: "done",
diff --git a/apps/dokploy/server/api/services/mongo.ts b/apps/dokploy/server/api/services/mongo.ts
index 15ae858ea..9940768a7 100644
--- a/apps/dokploy/server/api/services/mongo.ts
+++ b/apps/dokploy/server/api/services/mongo.ts
@@ -9,6 +9,8 @@ import { TRPCError } from "@trpc/server";
import { eq, getTableColumns } from "drizzle-orm";
import { validUniqueServerAppName } from "./project";
+import { execAsyncRemote } from "@/server/utils/process/execAsync";
+
export type Mongo = typeof mongo.$inferSelect;
export const createMongo = async (input: typeof apiCreateMongo._type) => {
@@ -52,6 +54,7 @@ export const findMongoById = async (mongoId: string) => {
with: {
project: true,
mounts: true,
+ server: true,
backups: {
with: {
destination: true,
@@ -114,7 +117,12 @@ export const removeMongoById = async (mongoId: string) => {
export const deployMongo = async (mongoId: string) => {
const mongo = await findMongoById(mongoId);
try {
- await pullImage(mongo.dockerImage);
+ if (mongo.serverId) {
+ await execAsyncRemote(mongo.serverId, `docker pull ${mongo.dockerImage}`);
+ } else {
+ await pullImage(mongo.dockerImage);
+ }
+
await buildMongo(mongo);
await updateMongoById(mongoId, {
applicationStatus: "done",
diff --git a/apps/dokploy/server/api/services/mount.ts b/apps/dokploy/server/api/services/mount.ts
index d8270498d..97528aa5f 100644
--- a/apps/dokploy/server/api/services/mount.ts
+++ b/apps/dokploy/server/api/services/mount.ts
@@ -1,14 +1,14 @@
-import { rmdir, stat, unlink } from "node:fs/promises";
-import path, { join } from "node:path";
-import { APPLICATIONS_PATH, COMPOSE_PATH } from "@/server/constants";
+import path from "node:path";
+import { paths } from "@/server/constants";
import { db } from "@/server/db";
import {
type ServiceType,
type apiCreateMount,
mounts,
} from "@/server/db/schema";
-import { createFile } from "@/server/utils/docker/utils";
+import { createFile, getCreateFileCommand } from "@/server/utils/docker/utils";
import { removeFileOrDirectory } from "@/server/utils/filesystem/directory";
+import { execAsyncRemote } from "@/server/utils/process/execAsync";
import { TRPCError } from "@trpc/server";
import { type SQL, eq, sql } from "drizzle-orm";
@@ -71,7 +71,19 @@ export const createFileMount = async (mountId: string) => {
try {
const mount = await findMountById(mountId);
const baseFilePath = await getBaseFilesPath(mountId);
- await createFile(baseFilePath, mount.filePath || "", mount.content || "");
+
+ const serverId = await getServerId(mount);
+
+ if (serverId) {
+ const command = getCreateFileCommand(
+ baseFilePath,
+ mount.filePath || "",
+ mount.content || "",
+ );
+ await execAsyncRemote(serverId, command);
+ } else {
+ await createFile(baseFilePath, mount.filePath || "", mount.content || "");
+ }
} catch (error) {
console.log(`Error to create the file mount: ${error}`);
throw new TRPCError({
@@ -186,32 +198,52 @@ export const deleteFileMount = async (mountId: string) => {
const mount = await findMountById(mountId);
if (!mount.filePath) return;
const basePath = await getBaseFilesPath(mountId);
+
const fullPath = path.join(basePath, mount.filePath);
try {
- await removeFileOrDirectory(fullPath);
+ const serverId = await getServerId(mount);
+ if (serverId) {
+ const command = `rm -rf ${fullPath}`;
+ await execAsyncRemote(serverId, command);
+ } else {
+ await removeFileOrDirectory(fullPath);
+ }
} catch (error) {}
};
export const getBaseFilesPath = async (mountId: string) => {
const mount = await findMountById(mountId);
- let absoluteBasePath = path.resolve(APPLICATIONS_PATH);
+ let absoluteBasePath = "";
let appName = "";
let directoryPath = "";
if (mount.serviceType === "application" && mount.application) {
+ const { APPLICATIONS_PATH } = paths(!!mount.application.serverId);
+ absoluteBasePath = path.resolve(APPLICATIONS_PATH);
appName = mount.application.appName;
} else if (mount.serviceType === "postgres" && mount.postgres) {
+ const { APPLICATIONS_PATH } = paths(!!mount.postgres.serverId);
+ absoluteBasePath = path.resolve(APPLICATIONS_PATH);
appName = mount.postgres.appName;
} else if (mount.serviceType === "mariadb" && mount.mariadb) {
+ const { APPLICATIONS_PATH } = paths(!!mount.mariadb.serverId);
+ absoluteBasePath = path.resolve(APPLICATIONS_PATH);
appName = mount.mariadb.appName;
} else if (mount.serviceType === "mongo" && mount.mongo) {
+ const { APPLICATIONS_PATH } = paths(!!mount.mongo.serverId);
+ absoluteBasePath = path.resolve(APPLICATIONS_PATH);
appName = mount.mongo.appName;
} else if (mount.serviceType === "mysql" && mount.mysql) {
+ const { APPLICATIONS_PATH } = paths(!!mount.mysql.serverId);
+ absoluteBasePath = path.resolve(APPLICATIONS_PATH);
appName = mount.mysql.appName;
} else if (mount.serviceType === "redis" && mount.redis) {
+ const { APPLICATIONS_PATH } = paths(!!mount.redis.serverId);
+ absoluteBasePath = path.resolve(APPLICATIONS_PATH);
appName = mount.redis.appName;
} else if (mount.serviceType === "compose" && mount.compose) {
+ const { COMPOSE_PATH } = paths(!!mount.compose.serverId);
appName = mount.compose.appName;
absoluteBasePath = path.resolve(COMPOSE_PATH);
}
@@ -219,3 +251,30 @@ export const getBaseFilesPath = async (mountId: string) => {
return directoryPath;
};
+
+type MountNested = Awaited>;
+export const getServerId = async (mount: MountNested) => {
+ if (mount.serviceType === "application" && mount?.application?.serverId) {
+ return mount.application.serverId;
+ }
+ if (mount.serviceType === "postgres" && mount?.postgres?.serverId) {
+ return mount.postgres.serverId;
+ }
+ if (mount.serviceType === "mariadb" && mount?.mariadb?.serverId) {
+ return mount.mariadb.serverId;
+ }
+ if (mount.serviceType === "mongo" && mount?.mongo?.serverId) {
+ return mount.mongo.serverId;
+ }
+ if (mount.serviceType === "mysql" && mount?.mysql?.serverId) {
+ return mount.mysql.serverId;
+ }
+ if (mount.serviceType === "redis" && mount?.redis?.serverId) {
+ return mount.redis.serverId;
+ }
+ if (mount.serviceType === "compose" && mount?.compose?.serverId) {
+ return mount.compose.serverId;
+ }
+
+ return null;
+};
diff --git a/apps/dokploy/server/api/services/mysql.ts b/apps/dokploy/server/api/services/mysql.ts
index 4d4d0f759..b4dacf81f 100644
--- a/apps/dokploy/server/api/services/mysql.ts
+++ b/apps/dokploy/server/api/services/mysql.ts
@@ -9,6 +9,8 @@ import { TRPCError } from "@trpc/server";
import { eq, getTableColumns } from "drizzle-orm";
import { validUniqueServerAppName } from "./project";
+import { execAsyncRemote } from "@/server/utils/process/execAsync";
+
export type MySql = typeof mysql.$inferSelect;
export const createMysql = async (input: typeof apiCreateMySql._type) => {
@@ -57,6 +59,7 @@ export const findMySqlById = async (mysqlId: string) => {
with: {
project: true,
mounts: true,
+ server: true,
backups: {
with: {
destination: true,
@@ -119,7 +122,12 @@ export const removeMySqlById = async (mysqlId: string) => {
export const deployMySql = async (mysqlId: string) => {
const mysql = await findMySqlById(mysqlId);
try {
- await pullImage(mysql.dockerImage);
+ if (mysql.serverId) {
+ await execAsyncRemote(mysql.serverId, `docker pull ${mysql.dockerImage}`);
+ } else {
+ await pullImage(mysql.dockerImage);
+ }
+
await buildMysql(mysql);
await updateMySqlById(mysqlId, {
applicationStatus: "done",
diff --git a/apps/dokploy/server/api/services/postgres.ts b/apps/dokploy/server/api/services/postgres.ts
index a82a1eebb..56c91149c 100644
--- a/apps/dokploy/server/api/services/postgres.ts
+++ b/apps/dokploy/server/api/services/postgres.ts
@@ -9,6 +9,8 @@ import { TRPCError } from "@trpc/server";
import { eq, getTableColumns } from "drizzle-orm";
import { validUniqueServerAppName } from "./project";
+import { execAsyncRemote } from "@/server/utils/process/execAsync";
+
export type Postgres = typeof postgres.$inferSelect;
export const createPostgres = async (input: typeof apiCreatePostgres._type) => {
@@ -45,13 +47,13 @@ export const createPostgres = async (input: typeof apiCreatePostgres._type) => {
return newPostgres;
};
-
export const findPostgresById = async (postgresId: string) => {
const result = await db.query.postgres.findFirst({
where: eq(postgres.postgresId, postgresId),
with: {
project: true,
mounts: true,
+ server: true,
backups: {
with: {
destination: true,
@@ -114,7 +116,16 @@ export const removePostgresById = async (postgresId: string) => {
export const deployPostgres = async (postgresId: string) => {
const postgres = await findPostgresById(postgresId);
try {
- await pullImage(postgres.dockerImage);
+ const promises = [];
+ if (postgres.serverId) {
+ const result = await execAsyncRemote(
+ postgres.serverId,
+ `docker pull ${postgres.dockerImage}`,
+ );
+ } else {
+ await pullImage(postgres.dockerImage);
+ }
+
await buildPostgres(postgres);
await updatePostgresById(postgresId, {
applicationStatus: "done",
diff --git a/apps/dokploy/server/api/services/project.ts b/apps/dokploy/server/api/services/project.ts
index 56aecce5f..902dd06b3 100644
--- a/apps/dokploy/server/api/services/project.ts
+++ b/apps/dokploy/server/api/services/project.ts
@@ -11,17 +11,18 @@ import {
} from "@/server/db/schema";
import { TRPCError } from "@trpc/server";
import { eq } from "drizzle-orm";
-import { findAdmin } from "./admin";
export type Project = typeof projects.$inferSelect;
-export const createProject = async (input: typeof apiCreateProject._type) => {
- const admin = await findAdmin();
+export const createProject = async (
+ input: typeof apiCreateProject._type,
+ adminId: string,
+) => {
const newProject = await db
.insert(projects)
.values({
...input,
- adminId: admin.adminId,
+ adminId: adminId,
})
.returning()
.then((value) => value[0]);
diff --git a/apps/dokploy/server/api/services/redirect.ts b/apps/dokploy/server/api/services/redirect.ts
index 476023a7d..972603f2e 100644
--- a/apps/dokploy/server/api/services/redirect.ts
+++ b/apps/dokploy/server/api/services/redirect.ts
@@ -46,7 +46,7 @@ export const createRedirect = async (
const application = await findApplicationById(redirect.applicationId);
- createRedirectMiddleware(application.appName, redirect);
+ createRedirectMiddleware(application, redirect);
});
return true;
@@ -77,7 +77,7 @@ export const removeRedirectById = async (redirectId: string) => {
const application = await findApplicationById(response.applicationId);
- removeRedirectMiddleware(application.appName, response);
+ await removeRedirectMiddleware(application, response);
return response;
} catch (error) {
@@ -111,7 +111,7 @@ export const updateRedirectById = async (
}
const application = await findApplicationById(redirect.applicationId);
- updateRedirectMiddleware(application.appName, redirect);
+ await updateRedirectMiddleware(application, redirect);
return redirect;
} catch (error) {
diff --git a/apps/dokploy/server/api/services/redis.ts b/apps/dokploy/server/api/services/redis.ts
index c1f61c223..a4d85857c 100644
--- a/apps/dokploy/server/api/services/redis.ts
+++ b/apps/dokploy/server/api/services/redis.ts
@@ -9,6 +9,8 @@ import { TRPCError } from "@trpc/server";
import { eq } from "drizzle-orm";
import { validUniqueServerAppName } from "./project";
+import { execAsyncRemote } from "@/server/utils/process/execAsync";
+
export type Redis = typeof redis.$inferSelect;
// https://github.com/drizzle-team/drizzle-orm/discussions/1483#discussioncomment-7523881
@@ -53,6 +55,7 @@ export const findRedisById = async (redisId: string) => {
with: {
project: true,
mounts: true,
+ server: true,
},
});
if (!result) {
@@ -91,7 +94,12 @@ export const removeRedisById = async (redisId: string) => {
export const deployRedis = async (redisId: string) => {
const redis = await findRedisById(redisId);
try {
- await pullImage(redis.dockerImage);
+ if (redis.serverId) {
+ await execAsyncRemote(redis.serverId, `docker pull ${redis.dockerImage}`);
+ } else {
+ await pullImage(redis.dockerImage);
+ }
+
await buildRedis(redis);
await updateRedisById(redisId, {
applicationStatus: "done",
diff --git a/apps/dokploy/server/api/services/registry.ts b/apps/dokploy/server/api/services/registry.ts
index 83dcc2a21..f7c20af6d 100644
--- a/apps/dokploy/server/api/services/registry.ts
+++ b/apps/dokploy/server/api/services/registry.ts
@@ -2,7 +2,7 @@ import { db } from "@/server/db";
import { type apiCreateRegistry, registry } from "@/server/db/schema";
import { initializeRegistry } from "@/server/setup/registry-setup";
import { removeService } from "@/server/utils/docker/utils";
-import { execAsync } from "@/server/utils/process/execAsync";
+import { execAsync, execAsyncRemote } from "@/server/utils/process/execAsync";
import {
manageRegistry,
removeSelfHostedRegistry,
@@ -32,9 +32,10 @@ export const createRegistry = async (input: typeof apiCreateRegistry._type) => {
message: "Error input: Inserting registry",
});
}
-
- if (newRegistry.registryType === "cloud") {
- const loginCommand = `echo ${input.password} | docker login ${input.registryUrl} --username ${input.username} --password-stdin`;
+ const loginCommand = `echo ${input.password} | docker login ${input.registryUrl} --username ${input.username} --password-stdin`;
+ if (input.serverId && input.serverId !== "none") {
+ await execAsyncRemote(input.serverId, loginCommand);
+ } else if (newRegistry.registryType === "cloud") {
await execAsync(loginCommand);
}
@@ -76,7 +77,7 @@ export const removeRegistry = async (registryId: string) => {
export const updateRegistry = async (
registryId: string,
- registryData: Partial,
+ registryData: Partial & { serverId?: string | null },
) => {
try {
const response = await db
@@ -92,6 +93,13 @@ export const updateRegistry = async (
await manageRegistry(response);
await initializeRegistry(response.username, response.password);
}
+ const loginCommand = `echo ${response?.password} | docker login ${response?.registryUrl} --username ${response?.username} --password-stdin`;
+
+ if (registryData?.serverId && registryData?.serverId !== "none") {
+ await execAsyncRemote(registryData.serverId, loginCommand);
+ } else if (response?.registryType === "cloud") {
+ await execAsync(loginCommand);
+ }
return response;
} catch (error) {
diff --git a/apps/dokploy/server/api/services/security.ts b/apps/dokploy/server/api/services/security.ts
index ed7803a0d..2da78e266 100644
--- a/apps/dokploy/server/api/services/security.ts
+++ b/apps/dokploy/server/api/services/security.ts
@@ -44,7 +44,7 @@ export const createSecurity = async (
message: "Error to create the security",
});
}
- await createSecurityMiddleware(application.appName, securityResponse);
+ await createSecurityMiddleware(application, securityResponse);
return true;
});
} catch (error) {
@@ -74,7 +74,7 @@ export const deleteSecurityById = async (securityId: string) => {
const application = await findApplicationById(result.applicationId);
- removeSecurityMiddleware(application.appName, result);
+ await removeSecurityMiddleware(application, result);
return result;
} catch (error) {
throw new TRPCError({
diff --git a/apps/dokploy/server/api/services/server.ts b/apps/dokploy/server/api/services/server.ts
new file mode 100644
index 000000000..4cd79cdcc
--- /dev/null
+++ b/apps/dokploy/server/api/services/server.ts
@@ -0,0 +1,120 @@
+import { db } from "@/server/db";
+import { type apiCreateServer, server } from "@/server/db/schema";
+import { TRPCError } from "@trpc/server";
+import { desc, eq } from "drizzle-orm";
+
+export type Server = typeof server.$inferSelect;
+
+export const createServer = async (
+ input: typeof apiCreateServer._type,
+ adminId: string,
+) => {
+ const newServer = await db
+ .insert(server)
+ .values({
+ ...input,
+ adminId: adminId,
+ })
+ .returning()
+ .then((value) => value[0]);
+
+ if (!newServer) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Error to create the server",
+ });
+ }
+
+ return newServer;
+};
+
+export const findServerById = async (serverId: string) => {
+ const currentServer = await db.query.server.findFirst({
+ where: eq(server.serverId, serverId),
+ with: {
+ deployments: true,
+ sshKey: true,
+ },
+ });
+ if (!currentServer) {
+ throw new TRPCError({
+ code: "NOT_FOUND",
+ message: "Server not found",
+ });
+ }
+ return currentServer;
+};
+
+export const findServersByAdminId = async (adminId: string) => {
+ const servers = await db.query.server.findMany({
+ where: eq(server.adminId, adminId),
+ orderBy: desc(server.createdAt),
+ });
+
+ return servers;
+};
+
+export const deleteServer = async (serverId: string) => {
+ const currentServer = await db
+ .delete(server)
+ .where(eq(server.serverId, serverId))
+ .returning()
+ .then((value) => value[0]);
+
+ return currentServer;
+};
+
+export const haveActiveServices = async (serverId: string) => {
+ const currentServer = await db.query.server.findFirst({
+ where: eq(server.serverId, serverId),
+ with: {
+ applications: true,
+ compose: true,
+ redis: true,
+ mariadb: true,
+ mongo: true,
+ mysql: true,
+ postgres: true,
+ },
+ });
+
+ if (!currentServer) {
+ return false;
+ }
+
+ const total =
+ currentServer?.applications?.length +
+ currentServer?.compose?.length +
+ currentServer?.redis?.length +
+ currentServer?.mariadb?.length +
+ currentServer?.mongo?.length +
+ currentServer?.mysql?.length +
+ currentServer?.postgres?.length;
+
+ if (total === 0) {
+ return false;
+ }
+
+ return true;
+};
+
+export const updateServerById = async (
+ serverId: string,
+ serverData: Partial,
+) => {
+ const result = await db
+ .update(server)
+ .set({
+ ...serverData,
+ })
+ .where(eq(server.serverId, serverId))
+ .returning()
+ .then((res) => res[0]);
+
+ return result;
+};
+
+export const getAllServers = async () => {
+ const servers = await db.query.server.findMany();
+ return servers;
+};
diff --git a/apps/dokploy/server/api/services/settings.ts b/apps/dokploy/server/api/services/settings.ts
index a3f658d9e..ae1c275c5 100644
--- a/apps/dokploy/server/api/services/settings.ts
+++ b/apps/dokploy/server/api/services/settings.ts
@@ -2,6 +2,7 @@ import { readdirSync } from "node:fs";
import { join } from "node:path";
import { docker } from "@/server/constants";
import { getServiceContainer } from "@/server/utils/docker/utils";
+import { execAsyncRemote } from "@/server/utils/process/execAsync";
import packageInfo from "../../../package.json";
const updateIsAvailable = async () => {
@@ -44,22 +45,104 @@ interface TreeDataItem {
children?: TreeDataItem[];
}
-export const readDirectory = (dirPath: string): TreeDataItem[] => {
+export const readDirectory = async (
+ dirPath: string,
+ serverId?: string,
+): Promise => {
+ if (serverId) {
+ const { stdout } = await execAsyncRemote(
+ serverId,
+ `
+process_items() {
+ local parent_dir="$1"
+ local __resultvar=$2
+
+ local items_json=""
+ local first=true
+ for item in "$parent_dir"/*; do
+ [ -e "$item" ] || continue
+ process_item "$item" item_json
+ if [ "$first" = true ]; then
+ first=false
+ items_json="$item_json"
+ else
+ items_json="$items_json,$item_json"
+ fi
+ done
+
+ eval $__resultvar="'[$items_json]'"
+}
+
+process_item() {
+ local item_path="$1"
+ local __resultvar=$2
+
+ local item_name=$(basename "$item_path")
+ local escaped_name=$(echo "$item_name" | sed 's/"/\\"/g')
+ local escaped_path=$(echo "$item_path" | sed 's/"/\\"/g')
+
+ if [ -d "$item_path" ]; then
+ # Is directory
+ process_items "$item_path" children_json
+ local json='{"id":"'"$escaped_path"'","name":"'"$escaped_name"'","type":"directory","children":'"$children_json"'}'
+ else
+ # Is file
+ local json='{"id":"'"$escaped_path"'","name":"'"$escaped_name"'","type":"file"}'
+ fi
+
+ eval $__resultvar="'$json'"
+}
+
+root_dir=${dirPath}
+
+process_items "$root_dir" json_output
+
+echo "$json_output"
+ `,
+ );
+ const result = JSON.parse(stdout);
+ return result;
+ }
const items = readdirSync(dirPath, { withFileTypes: true });
- return items.map((item) => {
- const fullPath = join(dirPath, item.name);
- if (item.isDirectory()) {
- return {
- id: fullPath,
- name: item.name,
- type: "directory",
- children: readDirectory(fullPath),
- };
+
+ const stack = [dirPath];
+ const result: TreeDataItem[] = [];
+ const parentMap: Record = {};
+
+ while (stack.length > 0) {
+ const currentPath = stack.pop();
+ if (!currentPath) continue;
+
+ const items = readdirSync(currentPath, { withFileTypes: true });
+ const currentDirectoryResult: TreeDataItem[] = [];
+
+ for (const item of items) {
+ const fullPath = join(currentPath, item.name);
+ if (item.isDirectory()) {
+ stack.push(fullPath);
+ const directoryItem: TreeDataItem = {
+ id: fullPath,
+ name: item.name,
+ type: "directory",
+ children: [],
+ };
+ currentDirectoryResult.push(directoryItem);
+ parentMap[fullPath] = directoryItem.children as TreeDataItem[];
+ } else {
+ const fileItem: TreeDataItem = {
+ id: fullPath,
+ name: item.name,
+ type: "file",
+ };
+ currentDirectoryResult.push(fileItem);
+ }
}
- return {
- id: fullPath,
- name: item.name,
- type: "file",
- };
- });
+
+ if (parentMap[currentPath]) {
+ parentMap[currentPath].push(...currentDirectoryResult);
+ } else {
+ result.push(...currentDirectoryResult);
+ }
+ }
+ return result;
};
diff --git a/apps/dokploy/server/api/trpc.ts b/apps/dokploy/server/api/trpc.ts
index 2466af624..cfa85fc0e 100644
--- a/apps/dokploy/server/api/trpc.ts
+++ b/apps/dokploy/server/api/trpc.ts
@@ -22,6 +22,8 @@ import superjson from "superjson";
import { ZodError } from "zod";
import { validateRequest } from "../auth/auth";
import { validateBearerToken } from "../auth/token";
+import { findAdminByAuthId } from "./services/admin";
+import { findUserByAuthId } from "./services/user";
/**
* 1. CONTEXT
@@ -32,7 +34,7 @@ import { validateBearerToken } from "../auth/token";
*/
interface CreateContextOptions {
- user: (User & { authId: string }) | null;
+ user: (User & { authId: string; adminId: string }) | null;
session: Session | null;
req: CreateNextContextOptions["req"];
res: CreateNextContextOptions["res"];
@@ -86,6 +88,7 @@ export const createTRPCContext = async (opts: CreateNextContextOptions) => {
rol: user.rol,
id: user.id,
secret: user.secret,
+ adminId: user.adminId,
},
}) || {
user: null,
diff --git a/apps/dokploy/server/auth/auth.ts b/apps/dokploy/server/auth/auth.ts
index eb5facdfb..a09714374 100644
--- a/apps/dokploy/server/auth/auth.ts
+++ b/apps/dokploy/server/auth/auth.ts
@@ -4,6 +4,8 @@ import { DrizzlePostgreSQLAdapter } from "@lucia-auth/adapter-drizzle";
import { TimeSpan } from "lucia";
import { Lucia } from "lucia/dist/core.js";
import type { Session, User } from "lucia/dist/core.js";
+import { findAdminByAuthId } from "../api/services/admin";
+import { findUserByAuthId } from "../api/services/user";
import { db } from "../db";
import { type DatabaseUser, auth, sessionTable } from "../db/schema";
@@ -22,6 +24,7 @@ export const lucia = new Lucia(adapter, {
email: attributes.email,
rol: attributes.rol,
secret: attributes.secret !== null,
+ adminId: attributes.adminId,
};
},
});
@@ -29,12 +32,15 @@ export const lucia = new Lucia(adapter, {
declare module "lucia" {
interface Register {
Lucia: typeof lucia;
- DatabaseUserAttributes: Omit & { authId: string };
+ DatabaseUserAttributes: Omit & {
+ authId: string;
+ adminId: string;
+ };
}
}
export type ReturnValidateToken = Promise<{
- user: (User & { authId: string }) | null;
+ user: (User & { authId: string; adminId: string }) | null;
session: Session | null;
}>;
@@ -63,6 +69,17 @@ export async function validateRequest(
lucia.createBlankSessionCookie().serialize(),
);
}
+
+ if (result.user) {
+ if (result.user?.rol === "admin") {
+ const admin = await findAdminByAuthId(result.user.id);
+ result.user.adminId = admin.adminId;
+ } else if (result.user?.rol === "user") {
+ const userResult = await findUserByAuthId(result.user.id);
+ result.user.adminId = userResult.adminId;
+ }
+ }
+
return {
session: result.session,
...((result.user && {
@@ -72,6 +89,7 @@ export async function validateRequest(
rol: result.user.rol,
id: result.user.id,
secret: result.user.secret,
+ adminId: result.user.adminId,
},
}) || {
user: null,
diff --git a/apps/dokploy/server/auth/token.ts b/apps/dokploy/server/auth/token.ts
index 1195b6c24..5581777ae 100644
--- a/apps/dokploy/server/auth/token.ts
+++ b/apps/dokploy/server/auth/token.ts
@@ -1,6 +1,8 @@
import type { IncomingMessage } from "node:http";
import { TimeSpan } from "lucia";
import { Lucia } from "lucia/dist/core.js";
+import { findAdminByAuthId } from "../api/services/admin";
+import { findUserByAuthId } from "../api/services/user";
import { type ReturnValidateToken, adapter } from "./auth";
export const luciaToken = new Lucia(adapter, {
@@ -31,10 +33,21 @@ export const validateBearerToken = async (
};
}
const result = await luciaToken.validateSession(sessionId);
+
+ if (result.user) {
+ if (result.user?.rol === "admin") {
+ const admin = await findAdminByAuthId(result.user.id);
+ result.user.adminId = admin.adminId;
+ } else if (result.user?.rol === "user") {
+ const userResult = await findUserByAuthId(result.user.id);
+ result.user.adminId = userResult.adminId;
+ }
+ }
return {
session: result.session,
...((result.user && {
user: {
+ adminId: result.user.adminId,
authId: result.user.id,
email: result.user.email,
rol: result.user.rol,
diff --git a/apps/dokploy/server/constants/index.ts b/apps/dokploy/server/constants/index.ts
index be5880584..f2f1a4d88 100644
--- a/apps/dokploy/server/constants/index.ts
+++ b/apps/dokploy/server/constants/index.ts
@@ -1,17 +1,39 @@
import path from "node:path";
import Docker from "dockerode";
-export const BASE_PATH =
- process.env.NODE_ENV === "production"
- ? "/etc/dokploy"
- : path.join(process.cwd(), ".docker");
-export const MAIN_TRAEFIK_PATH = `${BASE_PATH}/traefik`;
-export const DYNAMIC_TRAEFIK_PATH = `${BASE_PATH}/traefik/dynamic`;
-export const LOGS_PATH = `${BASE_PATH}/logs`;
-export const APPLICATIONS_PATH = `${BASE_PATH}/applications`;
-export const COMPOSE_PATH = `${BASE_PATH}/compose`;
-export const SSH_PATH = `${BASE_PATH}/ssh`;
-export const CERTIFICATES_PATH = `${DYNAMIC_TRAEFIK_PATH}/certificates`;
-export const REGISTRY_PATH = `${DYNAMIC_TRAEFIK_PATH}/registry`;
-export const MONITORING_PATH = `${BASE_PATH}/monitoring`;
+export const IS_CLOUD = process.env.IS_CLOUD === "true";
export const docker = new Docker();
+
+export const paths = (isServer = false) => {
+ if (isServer) {
+ const BASE_PATH = "/etc/dokploy";
+ return {
+ BASE_PATH,
+ MAIN_TRAEFIK_PATH: `${BASE_PATH}/traefik`,
+ DYNAMIC_TRAEFIK_PATH: `${BASE_PATH}/traefik/dynamic`,
+ LOGS_PATH: `${BASE_PATH}/logs`,
+ APPLICATIONS_PATH: `${BASE_PATH}/applications`,
+ COMPOSE_PATH: `${BASE_PATH}/compose`,
+ SSH_PATH: `${BASE_PATH}/ssh`,
+ CERTIFICATES_PATH: `${BASE_PATH}/certificates`,
+ MONITORING_PATH: `${BASE_PATH}/monitoring`,
+ REGISTRY_PATH: `${BASE_PATH}/registry`,
+ };
+ }
+ const BASE_PATH =
+ process.env.NODE_ENV === "production"
+ ? "/etc/dokploy"
+ : path.join(process.cwd(), ".docker");
+ return {
+ BASE_PATH,
+ MAIN_TRAEFIK_PATH: `${BASE_PATH}/traefik`,
+ DYNAMIC_TRAEFIK_PATH: `${BASE_PATH}/traefik/dynamic`,
+ LOGS_PATH: `${BASE_PATH}/logs`,
+ APPLICATIONS_PATH: `${BASE_PATH}/applications`,
+ COMPOSE_PATH: `${BASE_PATH}/compose`,
+ SSH_PATH: `${BASE_PATH}/ssh`,
+ CERTIFICATES_PATH: `${BASE_PATH}/certificates`,
+ MONITORING_PATH: `${BASE_PATH}/monitoring`,
+ REGISTRY_PATH: `${BASE_PATH}/registry`,
+ };
+};
diff --git a/apps/dokploy/server/db/schema/admin.ts b/apps/dokploy/server/db/schema/admin.ts
index 957db8a70..861b1ce8a 100644
--- a/apps/dokploy/server/db/schema/admin.ts
+++ b/apps/dokploy/server/db/schema/admin.ts
@@ -63,7 +63,10 @@ export const apiUpdateDockerCleanup = createSchema
.pick({
enableDockerCleanup: true,
})
- .required();
+ .required()
+ .extend({
+ serverId: z.string().optional(),
+ });
export const apiTraefikConfig = z.object({
traefikConfig: z.string().min(1),
@@ -72,15 +75,24 @@ export const apiTraefikConfig = z.object({
export const apiModifyTraefikConfig = z.object({
path: z.string().min(1),
traefikConfig: z.string().min(1),
+ serverId: z.string().optional(),
});
export const apiReadTraefikConfig = z.object({
path: z.string().min(1),
+ serverId: z.string().optional(),
});
export const apiEnableDashboard = z.object({
enableDashboard: z.boolean().optional(),
+ serverId: z.string().optional(),
});
+export const apiServerSchema = z
+ .object({
+ serverId: z.string().optional(),
+ })
+ .optional();
+
export const apiReadStatsLogs = z.object({
page: z
.object({
diff --git a/apps/dokploy/server/db/schema/application.ts b/apps/dokploy/server/db/schema/application.ts
index 24cc88540..09175edc8 100644
--- a/apps/dokploy/server/db/schema/application.ts
+++ b/apps/dokploy/server/db/schema/application.ts
@@ -10,7 +10,7 @@ import {
import { createInsertSchema } from "drizzle-zod";
import { nanoid } from "nanoid";
import { z } from "zod";
-import { bitbucket, github, gitlab } from ".";
+import { bitbucket, github, gitlab, server } from ".";
import { deployments } from "./deployment";
import { domains } from "./domain";
import { mounts } from "./mount";
@@ -193,6 +193,9 @@ export const applications = pgTable("application", {
bitbucketId: text("bitbucketId").references(() => bitbucket.bitbucketId, {
onDelete: "set null",
}),
+ serverId: text("serverId").references(() => server.serverId, {
+ onDelete: "cascade",
+ }),
});
export const applicationsRelations = relations(
@@ -228,6 +231,10 @@ export const applicationsRelations = relations(
fields: [applications.bitbucketId],
references: [bitbucket.bitbucketId],
}),
+ server: one(server, {
+ fields: [applications.serverId],
+ references: [server.serverId],
+ }),
}),
);
@@ -373,6 +380,7 @@ export const apiCreateApplication = createSchema.pick({
appName: true,
description: true,
projectId: true,
+ serverId: true,
});
export const apiFindOneApplication = createSchema
@@ -471,6 +479,9 @@ export const apiFindMonitoringStats = createSchema
})
.required();
-export const apiUpdateApplication = createSchema.partial().extend({
- applicationId: z.string().min(1),
-});
+export const apiUpdateApplication = createSchema
+ .partial()
+ .extend({
+ applicationId: z.string().min(1),
+ })
+ .omit({ serverId: true });
diff --git a/apps/dokploy/server/db/schema/compose.ts b/apps/dokploy/server/db/schema/compose.ts
index f2ab084a1..b15171619 100644
--- a/apps/dokploy/server/db/schema/compose.ts
+++ b/apps/dokploy/server/db/schema/compose.ts
@@ -4,7 +4,7 @@ import { boolean, integer, pgEnum, pgTable, text } from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod";
import { nanoid } from "nanoid";
import { z } from "zod";
-import { bitbucket, github, gitlab } from ".";
+import { bitbucket, github, gitlab, server } from ".";
import { deployments } from "./deployment";
import { domains } from "./domain";
import { mounts } from "./mount";
@@ -83,6 +83,9 @@ export const compose = pgTable("compose", {
bitbucketId: text("bitbucketId").references(() => bitbucket.bitbucketId, {
onDelete: "set null",
}),
+ serverId: text("serverId").references(() => server.serverId, {
+ onDelete: "cascade",
+ }),
});
export const composeRelations = relations(compose, ({ one, many }) => ({
@@ -109,6 +112,10 @@ export const composeRelations = relations(compose, ({ one, many }) => ({
fields: [compose.bitbucketId],
references: [bitbucket.bitbucketId],
}),
+ server: one(server, {
+ fields: [compose.serverId],
+ references: [server.serverId],
+ }),
}));
const createSchema = createInsertSchema(compose, {
@@ -129,6 +136,7 @@ export const apiCreateCompose = createSchema.pick({
projectId: true,
composeType: true,
appName: true,
+ serverId: true,
});
export const apiCreateComposeByTemplate = createSchema
@@ -137,6 +145,7 @@ export const apiCreateComposeByTemplate = createSchema
})
.extend({
id: z.string().min(1),
+ serverId: z.string().optional(),
});
export const apiFindCompose = z.object({
@@ -148,11 +157,14 @@ export const apiFetchServices = z.object({
type: z.enum(["fetch", "cache"]).optional().default("cache"),
});
-export const apiUpdateCompose = createSchema.partial().extend({
- composeId: z.string(),
- composeFile: z.string().optional(),
- command: z.string().optional(),
-});
+export const apiUpdateCompose = createSchema
+ .partial()
+ .extend({
+ composeId: z.string(),
+ composeFile: z.string().optional(),
+ command: z.string().optional(),
+ })
+ .omit({ serverId: true });
export const apiRandomizeCompose = createSchema
.pick({
diff --git a/apps/dokploy/server/db/schema/deployment.ts b/apps/dokploy/server/db/schema/deployment.ts
index 7e4911876..db9838f05 100644
--- a/apps/dokploy/server/db/schema/deployment.ts
+++ b/apps/dokploy/server/db/schema/deployment.ts
@@ -5,6 +5,7 @@ import { nanoid } from "nanoid";
import { z } from "zod";
import { applications } from "./application";
import { compose } from "./compose";
+import { server } from "./server";
export const deploymentStatus = pgEnum("deploymentStatus", [
"running",
@@ -28,6 +29,9 @@ export const deployments = pgTable("deployment", {
composeId: text("composeId").references(() => compose.composeId, {
onDelete: "cascade",
}),
+ serverId: text("serverId").references(() => server.serverId, {
+ onDelete: "cascade",
+ }),
createdAt: text("createdAt")
.notNull()
.$defaultFn(() => new Date().toISOString()),
@@ -42,6 +46,10 @@ export const deploymentsRelations = relations(deployments, ({ one }) => ({
fields: [deployments.composeId],
references: [compose.composeId],
}),
+ server: one(server, {
+ fields: [deployments.serverId],
+ references: [server.serverId],
+ }),
}));
const schema = createInsertSchema(deployments, {
@@ -77,6 +85,18 @@ export const apiCreateDeploymentCompose = schema
composeId: z.string().min(1),
});
+export const apiCreateDeploymentServer = schema
+ .pick({
+ title: true,
+ status: true,
+ logPath: true,
+ serverId: true,
+ description: true,
+ })
+ .extend({
+ serverId: z.string().min(1),
+ });
+
export const apiFindAllByApplication = schema
.pick({
applicationId: true,
@@ -94,3 +114,12 @@ export const apiFindAllByCompose = schema
composeId: z.string().min(1),
})
.required();
+
+export const apiFindAllByServer = schema
+ .pick({
+ serverId: true,
+ })
+ .extend({
+ serverId: z.string().min(1),
+ })
+ .required();
diff --git a/apps/dokploy/server/db/schema/index.ts b/apps/dokploy/server/db/schema/index.ts
index 3b57b7a14..3b24faff1 100644
--- a/apps/dokploy/server/db/schema/index.ts
+++ b/apps/dokploy/server/db/schema/index.ts
@@ -27,3 +27,4 @@ export * from "./git-provider";
export * from "./bitbucket";
export * from "./github";
export * from "./gitlab";
+export * from "./server";
diff --git a/apps/dokploy/server/db/schema/mariadb.ts b/apps/dokploy/server/db/schema/mariadb.ts
index 82a89ebac..b5e13284f 100644
--- a/apps/dokploy/server/db/schema/mariadb.ts
+++ b/apps/dokploy/server/db/schema/mariadb.ts
@@ -7,6 +7,7 @@ import { z } from "zod";
import { backups } from "./backups";
import { mounts } from "./mount";
import { projects } from "./project";
+import { server } from "./server";
import { applicationStatus } from "./shared";
import { generateAppName } from "./utils";
@@ -44,6 +45,9 @@ export const mariadb = pgTable("mariadb", {
projectId: text("projectId")
.notNull()
.references(() => projects.projectId, { onDelete: "cascade" }),
+ serverId: text("serverId").references(() => server.serverId, {
+ onDelete: "cascade",
+ }),
});
export const mariadbRelations = relations(mariadb, ({ one, many }) => ({
@@ -53,6 +57,10 @@ export const mariadbRelations = relations(mariadb, ({ one, many }) => ({
}),
backups: many(backups),
mounts: many(mounts),
+ server: one(server, {
+ fields: [mariadb.serverId],
+ references: [server.serverId],
+ }),
}));
const createSchema = createInsertSchema(mariadb, {
@@ -75,6 +83,7 @@ const createSchema = createInsertSchema(mariadb, {
applicationStatus: z.enum(["idle", "running", "done", "error"]),
externalPort: z.number(),
description: z.string().optional(),
+ serverId: z.string().optional(),
});
export const apiCreateMariaDB = createSchema
@@ -88,6 +97,7 @@ export const apiCreateMariaDB = createSchema
databaseName: true,
databaseUser: true,
databasePassword: true,
+ serverId: true,
})
.required();
@@ -131,6 +141,9 @@ export const apiResetMariadb = createSchema
})
.required();
-export const apiUpdateMariaDB = createSchema.partial().extend({
- mariadbId: z.string().min(1),
-});
+export const apiUpdateMariaDB = createSchema
+ .partial()
+ .extend({
+ mariadbId: z.string().min(1),
+ })
+ .omit({ serverId: true });
diff --git a/apps/dokploy/server/db/schema/mongo.ts b/apps/dokploy/server/db/schema/mongo.ts
index a9a76a478..757ba9c90 100644
--- a/apps/dokploy/server/db/schema/mongo.ts
+++ b/apps/dokploy/server/db/schema/mongo.ts
@@ -1,4 +1,3 @@
-import { generatePassword } from "@/templates/utils";
import { relations } from "drizzle-orm";
import { integer, pgTable, text } from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod";
@@ -7,6 +6,7 @@ import { z } from "zod";
import { backups } from "./backups";
import { mounts } from "./mount";
import { projects } from "./project";
+import { server } from "./server";
import { applicationStatus } from "./shared";
import { generateAppName } from "./utils";
@@ -40,6 +40,9 @@ export const mongo = pgTable("mongo", {
projectId: text("projectId")
.notNull()
.references(() => projects.projectId, { onDelete: "cascade" }),
+ serverId: text("serverId").references(() => server.serverId, {
+ onDelete: "cascade",
+ }),
});
export const mongoRelations = relations(mongo, ({ one, many }) => ({
@@ -49,6 +52,10 @@ export const mongoRelations = relations(mongo, ({ one, many }) => ({
}),
backups: many(backups),
mounts: many(mounts),
+ server: one(server, {
+ fields: [mongo.serverId],
+ references: [server.serverId],
+ }),
}));
const createSchema = createInsertSchema(mongo, {
@@ -69,6 +76,7 @@ const createSchema = createInsertSchema(mongo, {
applicationStatus: z.enum(["idle", "running", "done", "error"]),
externalPort: z.number(),
description: z.string().optional(),
+ serverId: z.string().optional(),
});
export const apiCreateMongo = createSchema
@@ -80,6 +88,7 @@ export const apiCreateMongo = createSchema
description: true,
databaseUser: true,
databasePassword: true,
+ serverId: true,
})
.required();
@@ -116,9 +125,12 @@ export const apiDeployMongo = createSchema
})
.required();
-export const apiUpdateMongo = createSchema.partial().extend({
- mongoId: z.string().min(1),
-});
+export const apiUpdateMongo = createSchema
+ .partial()
+ .extend({
+ mongoId: z.string().min(1),
+ })
+ .omit({ serverId: true });
export const apiResetMongo = createSchema
.pick({
diff --git a/apps/dokploy/server/db/schema/mysql.ts b/apps/dokploy/server/db/schema/mysql.ts
index 38b2a5ef8..b53520696 100644
--- a/apps/dokploy/server/db/schema/mysql.ts
+++ b/apps/dokploy/server/db/schema/mysql.ts
@@ -1,4 +1,3 @@
-import { generatePassword } from "@/templates/utils";
import { relations } from "drizzle-orm";
import { integer, pgTable, text } from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod";
@@ -7,6 +6,7 @@ import { z } from "zod";
import { backups } from "./backups";
import { mounts } from "./mount";
import { projects } from "./project";
+import { server } from "./server";
import { applicationStatus } from "./shared";
import { generateAppName } from "./utils";
@@ -42,6 +42,9 @@ export const mysql = pgTable("mysql", {
projectId: text("projectId")
.notNull()
.references(() => projects.projectId, { onDelete: "cascade" }),
+ serverId: text("serverId").references(() => server.serverId, {
+ onDelete: "cascade",
+ }),
});
export const mysqlRelations = relations(mysql, ({ one, many }) => ({
@@ -51,6 +54,10 @@ export const mysqlRelations = relations(mysql, ({ one, many }) => ({
}),
backups: many(backups),
mounts: many(mounts),
+ server: one(server, {
+ fields: [mysql.serverId],
+ references: [server.serverId],
+ }),
}));
const createSchema = createInsertSchema(mysql, {
@@ -73,6 +80,7 @@ const createSchema = createInsertSchema(mysql, {
applicationStatus: z.enum(["idle", "running", "done", "error"]),
externalPort: z.number(),
description: z.string().optional(),
+ serverId: z.string().optional(),
});
export const apiCreateMySql = createSchema
@@ -86,6 +94,7 @@ export const apiCreateMySql = createSchema
databaseUser: true,
databasePassword: true,
databaseRootPassword: true,
+ serverId: true,
})
.required();
@@ -129,6 +138,9 @@ export const apiDeployMySql = createSchema
})
.required();
-export const apiUpdateMySql = createSchema.partial().extend({
- mysqlId: z.string().min(1),
-});
+export const apiUpdateMySql = createSchema
+ .partial()
+ .extend({
+ mysqlId: z.string().min(1),
+ })
+ .omit({ serverId: true });
diff --git a/apps/dokploy/server/db/schema/postgres.ts b/apps/dokploy/server/db/schema/postgres.ts
index e8982ac87..c751a6811 100644
--- a/apps/dokploy/server/db/schema/postgres.ts
+++ b/apps/dokploy/server/db/schema/postgres.ts
@@ -1,4 +1,3 @@
-import { generatePassword } from "@/templates/utils";
import { relations } from "drizzle-orm";
import { integer, pgTable, text } from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod";
@@ -7,6 +6,7 @@ import { z } from "zod";
import { backups } from "./backups";
import { mounts } from "./mount";
import { projects } from "./project";
+import { server } from "./server";
import { applicationStatus } from "./shared";
import { generateAppName } from "./utils";
@@ -41,6 +41,9 @@ export const postgres = pgTable("postgres", {
projectId: text("projectId")
.notNull()
.references(() => projects.projectId, { onDelete: "cascade" }),
+ serverId: text("serverId").references(() => server.serverId, {
+ onDelete: "cascade",
+ }),
});
export const postgresRelations = relations(postgres, ({ one, many }) => ({
@@ -50,6 +53,10 @@ export const postgresRelations = relations(postgres, ({ one, many }) => ({
}),
backups: many(backups),
mounts: many(mounts),
+ server: one(server, {
+ fields: [postgres.serverId],
+ references: [server.serverId],
+ }),
}));
const createSchema = createInsertSchema(postgres, {
@@ -70,6 +77,7 @@ const createSchema = createInsertSchema(postgres, {
externalPort: z.number(),
createdAt: z.string(),
description: z.string().optional(),
+ serverId: z.string().optional(),
});
export const apiCreatePostgres = createSchema
@@ -82,6 +90,7 @@ export const apiCreatePostgres = createSchema
dockerImage: true,
projectId: true,
description: true,
+ serverId: true,
})
.required();
@@ -125,6 +134,9 @@ export const apiResetPostgres = createSchema
})
.required();
-export const apiUpdatePostgres = createSchema.partial().extend({
- postgresId: z.string().min(1),
-});
+export const apiUpdatePostgres = createSchema
+ .partial()
+ .extend({
+ postgresId: z.string().min(1),
+ })
+ .omit({ serverId: true });
diff --git a/apps/dokploy/server/db/schema/redis.ts b/apps/dokploy/server/db/schema/redis.ts
index 65706f698..b27bbc49d 100644
--- a/apps/dokploy/server/db/schema/redis.ts
+++ b/apps/dokploy/server/db/schema/redis.ts
@@ -1,4 +1,3 @@
-import { generatePassword } from "@/templates/utils";
import { relations } from "drizzle-orm";
import { integer, pgTable, text } from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod";
@@ -6,6 +5,7 @@ import { nanoid } from "nanoid";
import { z } from "zod";
import { mounts } from "./mount";
import { projects } from "./project";
+import { server } from "./server";
import { applicationStatus } from "./shared";
import { generateAppName } from "./utils";
@@ -38,6 +38,9 @@ export const redis = pgTable("redis", {
projectId: text("projectId")
.notNull()
.references(() => projects.projectId, { onDelete: "cascade" }),
+ serverId: text("serverId").references(() => server.serverId, {
+ onDelete: "cascade",
+ }),
});
export const redisRelations = relations(redis, ({ one, many }) => ({
@@ -46,6 +49,10 @@ export const redisRelations = relations(redis, ({ one, many }) => ({
references: [projects.projectId],
}),
mounts: many(mounts),
+ server: one(server, {
+ fields: [redis.serverId],
+ references: [server.serverId],
+ }),
}));
const createSchema = createInsertSchema(redis, {
@@ -65,6 +72,7 @@ const createSchema = createInsertSchema(redis, {
applicationStatus: z.enum(["idle", "running", "done", "error"]),
externalPort: z.number(),
description: z.string().optional(),
+ serverId: z.string().optional(),
});
export const apiCreateRedis = createSchema
@@ -75,6 +83,7 @@ export const apiCreateRedis = createSchema
dockerImage: true,
projectId: true,
description: true,
+ serverId: true,
})
.required();
@@ -118,6 +127,9 @@ export const apiResetRedis = createSchema
})
.required();
-export const apiUpdateRedis = createSchema.partial().extend({
- redisId: z.string().min(1),
-});
+export const apiUpdateRedis = createSchema
+ .partial()
+ .extend({
+ redisId: z.string().min(1),
+ })
+ .omit({ serverId: true });
diff --git a/apps/dokploy/server/db/schema/registry.ts b/apps/dokploy/server/db/schema/registry.ts
index 832de9b1c..ee1bab946 100644
--- a/apps/dokploy/server/db/schema/registry.ts
+++ b/apps/dokploy/server/db/schema/registry.ts
@@ -23,7 +23,7 @@ export const registry = pgTable("registry", {
imagePrefix: text("imagePrefix"),
username: text("username").notNull(),
password: text("password").notNull(),
- registryUrl: text("registryUrl").notNull(),
+ registryUrl: text("registryUrl").notNull().default(""),
createdAt: text("createdAt")
.notNull()
.$defaultFn(() => new Date().toISOString()),
@@ -45,7 +45,7 @@ const createSchema = createInsertSchema(registry, {
registryName: z.string().min(1),
username: z.string().min(1),
password: z.string().min(1),
- registryUrl: z.string().min(1),
+ registryUrl: z.string(),
adminId: z.string().min(1),
registryId: z.string().min(1),
registryType: z.enum(["selfHosted", "cloud"]),
@@ -62,7 +62,10 @@ export const apiCreateRegistry = createSchema
registryType: z.enum(["selfHosted", "cloud"]),
imagePrefix: z.string().nullable().optional(),
})
- .required();
+ .required()
+ .extend({
+ serverId: z.string().optional(),
+ });
export const apiTestRegistry = createSchema.pick({}).extend({
registryName: z.string().min(1),
@@ -71,6 +74,7 @@ export const apiTestRegistry = createSchema.pick({}).extend({
registryUrl: z.string(),
registryType: z.enum(["selfHosted", "cloud"]),
imagePrefix: z.string().nullable().optional(),
+ serverId: z.string().optional(),
});
export const apiRemoveRegistry = createSchema
@@ -87,6 +91,7 @@ export const apiFindOneRegistry = createSchema
export const apiUpdateRegistry = createSchema.partial().extend({
registryId: z.string().min(1),
+ serverId: z.string().optional(),
});
export const apiEnableSelfHostedRegistry = createSchema
diff --git a/apps/dokploy/server/db/schema/server.ts b/apps/dokploy/server/db/schema/server.ts
new file mode 100644
index 000000000..02fa87cb7
--- /dev/null
+++ b/apps/dokploy/server/db/schema/server.ts
@@ -0,0 +1,103 @@
+import { relations } from "drizzle-orm";
+import { boolean, integer, pgTable, text } from "drizzle-orm/pg-core";
+import { createInsertSchema } from "drizzle-zod";
+import { nanoid } from "nanoid";
+import { z } from "zod";
+import {
+ applications,
+ compose,
+ mariadb,
+ mongo,
+ mysql,
+ postgres,
+ redis,
+} from ".";
+import { admins } from "./admin";
+import { deployments } from "./deployment";
+import { sshKeys } from "./ssh-key";
+import { generateAppName } from "./utils";
+
+export const server = pgTable("server", {
+ serverId: text("serverId")
+ .notNull()
+ .primaryKey()
+ .$defaultFn(() => nanoid()),
+ name: text("name").notNull(),
+ description: text("description"),
+ ipAddress: text("ipAddress").notNull(),
+ port: integer("port").notNull(),
+ username: text("username").notNull().default("root"),
+ appName: text("appName")
+ .notNull()
+ .$defaultFn(() => generateAppName("server")),
+ enableDockerCleanup: boolean("enableDockerCleanup").notNull().default(false),
+ createdAt: text("createdAt")
+ .notNull()
+ .$defaultFn(() => new Date().toISOString()),
+ adminId: text("adminId")
+ .notNull()
+ .references(() => admins.adminId, { onDelete: "cascade" }),
+ sshKeyId: text("sshKeyId").references(() => sshKeys.sshKeyId, {
+ onDelete: "set null",
+ }),
+});
+
+export const serverRelations = relations(server, ({ one, many }) => ({
+ admin: one(admins, {
+ fields: [server.adminId],
+ references: [admins.adminId],
+ }),
+ deployments: many(deployments),
+ sshKey: one(sshKeys, {
+ fields: [server.sshKeyId],
+ references: [sshKeys.sshKeyId],
+ }),
+ applications: many(applications),
+ compose: many(compose),
+ redis: many(redis),
+ mariadb: many(mariadb),
+ mongo: many(mongo),
+ mysql: many(mysql),
+ postgres: many(postgres),
+}));
+
+const createSchema = createInsertSchema(server, {
+ serverId: z.string().min(1),
+ name: z.string().min(1),
+ description: z.string().optional(),
+});
+
+export const apiCreateServer = createSchema
+ .pick({
+ name: true,
+ description: true,
+ ipAddress: true,
+ port: true,
+ username: true,
+ sshKeyId: true,
+ })
+ .required();
+
+export const apiFindOneServer = createSchema
+ .pick({
+ serverId: true,
+ })
+ .required();
+
+export const apiRemoveServer = createSchema
+ .pick({
+ serverId: true,
+ })
+ .required();
+
+export const apiUpdateServer = createSchema
+ .pick({
+ name: true,
+ description: true,
+ serverId: true,
+ ipAddress: true,
+ port: true,
+ username: true,
+ sshKeyId: true,
+ })
+ .required();
diff --git a/apps/dokploy/server/db/schema/ssh-key.ts b/apps/dokploy/server/db/schema/ssh-key.ts
index 2251c0aeb..8ff6b0e21 100644
--- a/apps/dokploy/server/db/schema/ssh-key.ts
+++ b/apps/dokploy/server/db/schema/ssh-key.ts
@@ -2,9 +2,10 @@ import { applications } from "@/server/db/schema/application";
import { compose } from "@/server/db/schema/compose";
import { sshKeyCreate, sshKeyType } from "@/server/db/validations";
import { relations } from "drizzle-orm";
-import { pgTable, text, time } from "drizzle-orm/pg-core";
+import { pgTable, text } from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod";
import { nanoid } from "nanoid";
+import { server } from "./server";
export const sshKeys = pgTable("ssh-key", {
sshKeyId: text("sshKeyId")
@@ -23,6 +24,7 @@ export const sshKeys = pgTable("ssh-key", {
export const sshKeysRelations = relations(sshKeys, ({ many }) => ({
applications: many(applications),
compose: many(compose),
+ servers: many(server),
}));
const createSchema = createInsertSchema(
diff --git a/apps/dokploy/server/monitoring/utilts.ts b/apps/dokploy/server/monitoring/utilts.ts
index 0d60b668f..f67d57050 100644
--- a/apps/dokploy/server/monitoring/utilts.ts
+++ b/apps/dokploy/server/monitoring/utilts.ts
@@ -1,74 +1,50 @@
import { promises } from "node:fs";
-import dockerstats from "dockerstats";
+import type Dockerode from "dockerode";
import osUtils from "node-os-utils";
-import { MONITORING_PATH } from "../constants";
+import { paths } from "../constants";
export const recordAdvancedStats = async (
+ stats: Dockerode.ContainerStats,
appName: string,
- containerId: string,
) => {
- await promises.mkdir(`${MONITORING_PATH}/${appName}`, { recursive: true });
+ const { MONITORING_PATH } = paths();
+ const path = `${MONITORING_PATH}/${appName}`;
- const result = await dockerstats.dockerContainerStats(containerId);
+ await promises.mkdir(path, { recursive: true });
- if (!result || result.length === 0 || !result[0]) return;
-
- const { memoryStats, cpuStats, precpuStats, netIO, blockIO } = result[0];
-
- const memoryUsage = memoryStats.usage / 1024 / 1024;
- const memoryTotal = memoryStats.limit / 1024 / 1024;
- const memoryFree = memoryTotal - memoryUsage;
- const memoryUsedPercentage = (memoryUsage / memoryTotal) * 100;
-
- const cpuDelta =
- cpuStats.cpu_usage.total_usage - precpuStats.cpu_usage.total_usage;
- const systemDelta = cpuStats.system_cpu_usage - precpuStats.system_cpu_usage;
- const onlineCpus = cpuStats.online_cpus;
-
- // Calcular el porcentaje de uso del CPU
- const cpuPercent = (cpuDelta / systemDelta) * onlineCpus * 100;
-
- // Extraer los valores de entrada y salida del objeto netIO
- const networkInBytes = netIO.rx;
- const networkOutBytes = netIO.wx;
-
- // Convertir bytes a Megabytes
- const networkInMB = networkInBytes / 1024 / 1024;
- const networkOutMB = networkOutBytes / 1024 / 1024;
-
- // BlockIO
-
- const blockRead = blockIO.r;
- const blockWrite = blockIO.w;
-
- const blockInMBBlocks = blockRead / 1024 / 1024;
- const blockOutMBBlocks = blockWrite / 1024 / 1024;
-
- // Disk
- const disk = await osUtils.drive.info("/");
-
- const diskUsage = disk.usedGb;
- const diskTotal = disk.totalGb;
- const diskUsedPercentage = disk.usedPercentage;
- const diskFree = disk.freeGb;
+ const cpuPercent = calculateCpuUsagePercent(
+ stats.cpu_stats,
+ stats.precpu_stats,
+ );
+ const memoryStats = calculateMemoryStats(stats.memory_stats);
+ const blockIO = calculateBlockIO(stats.blkio_stats);
+ const networkUsage = calculateNetworkUsage(stats.networks);
await updateStatsFile(appName, "cpu", cpuPercent);
await updateStatsFile(appName, "memory", {
- used: memoryUsage,
- free: memoryFree,
- usedPercentage: memoryUsedPercentage,
- total: memoryTotal,
+ used: memoryStats.used,
+ free: memoryStats.free,
+ usedPercentage: memoryStats.usedPercentage,
+ total: memoryStats.total,
});
await updateStatsFile(appName, "block", {
- readMb: blockInMBBlocks,
- writeMb: blockOutMBBlocks,
+ readMb: blockIO.readMb,
+ writeMb: blockIO.writeMb,
});
+
await updateStatsFile(appName, "network", {
- inputMb: networkInMB,
- outputMb: networkOutMB,
+ inputMb: networkUsage.inputMb,
+ outputMb: networkUsage.outputMb,
});
if (appName === "dokploy") {
+ const disk = await osUtils.drive.info("/");
+
+ const diskUsage = disk.usedGb;
+ const diskTotal = disk.totalGb;
+ const diskUsedPercentage = disk.usedPercentage;
+ const diskFree = disk.freeGb;
+
await updateStatsFile(appName, "disk", {
diskTotal: +diskTotal,
diskUsedPercentage: +diskUsedPercentage,
@@ -93,6 +69,7 @@ export const readStatsFile = async (
statType: "cpu" | "memory" | "disk" | "network" | "block",
) => {
try {
+ const { MONITORING_PATH } = paths();
const filePath = `${MONITORING_PATH}/${appName}/${statType}.json`;
const data = await promises.readFile(filePath, "utf-8");
return JSON.parse(data);
@@ -106,6 +83,7 @@ export const updateStatsFile = async (
statType: "cpu" | "memory" | "disk" | "network" | "block",
value: number | string | unknown,
) => {
+ const { MONITORING_PATH } = paths();
const stats = await readStatsFile(appName, statType);
stats.push({ value, time: new Date() });
@@ -125,6 +103,7 @@ export const readLastValueStatsFile = async (
statType: "cpu" | "memory" | "disk" | "network" | "block",
) => {
try {
+ const { MONITORING_PATH } = paths();
const filePath = `${MONITORING_PATH}/${appName}/${statType}.json`;
const data = await promises.readFile(filePath, "utf-8");
const stats = JSON.parse(data);
@@ -143,3 +122,77 @@ export const getLastAdvancedStatsFile = async (appName: string) => {
block: await readLastValueStatsFile(appName, "block"),
};
};
+
+const calculateCpuUsagePercent = (
+ cpu_stats: Dockerode.ContainerStats["cpu_stats"],
+ precpu_stats: Dockerode.ContainerStats["precpu_stats"],
+) => {
+ const cpuDelta =
+ cpu_stats.cpu_usage.total_usage - precpu_stats.cpu_usage.total_usage;
+ const systemDelta =
+ cpu_stats.system_cpu_usage - precpu_stats.system_cpu_usage;
+
+ const numberCpus =
+ cpu_stats.online_cpus ||
+ (cpu_stats.cpu_usage.percpu_usage
+ ? cpu_stats.cpu_usage.percpu_usage.length
+ : 1);
+
+ if (systemDelta > 0 && cpuDelta > 0) {
+ return (cpuDelta / systemDelta) * numberCpus * 100.0;
+ }
+ return 0;
+};
+
+const calculateMemoryStats = (
+ memory_stats: Dockerode.ContainerStats["memory_stats"],
+) => {
+ const usedMemory = memory_stats.usage - (memory_stats.stats.cache || 0);
+ const availableMemory = memory_stats.limit;
+ const memoryUsedPercentage = (usedMemory / availableMemory) * 100.0;
+
+ return {
+ used: usedMemory,
+ free: availableMemory - usedMemory,
+ usedPercentage: memoryUsedPercentage,
+ total: availableMemory,
+ };
+};
+const calculateBlockIO = (
+ blkio_stats: Dockerode.ContainerStats["blkio_stats"],
+) => {
+ let readIO = 0;
+ let writeIO = 0;
+ if (blkio_stats?.io_service_bytes_recursive) {
+ for (const io of blkio_stats.io_service_bytes_recursive) {
+ if (io.op === "read") {
+ readIO += io.value;
+ } else if (io.op === "write") {
+ writeIO += io.value;
+ }
+ }
+ }
+ return {
+ readMb: readIO / (1024 * 1024),
+ writeMb: writeIO / (1024 * 1024),
+ };
+};
+
+const calculateNetworkUsage = (
+ networks: Dockerode.ContainerStats["networks"],
+) => {
+ let totalRx = 0;
+ let totalTx = 0;
+
+ const stats = Object.keys(networks);
+
+ for (const interfaceName of stats) {
+ const net = networks[interfaceName];
+ totalRx += net?.rx_bytes || 0;
+ totalTx += net?.tx_bytes || 0;
+ }
+ return {
+ inputMb: totalRx / (1024 * 1024),
+ outputMb: totalTx / (1024 * 1024),
+ };
+};
diff --git a/apps/dokploy/server/queues/deployments-queue.ts b/apps/dokploy/server/queues/deployments-queue.ts
index bc27f9a2d..7fcae4f39 100644
--- a/apps/dokploy/server/queues/deployments-queue.ts
+++ b/apps/dokploy/server/queues/deployments-queue.ts
@@ -1,12 +1,16 @@
import { type Job, Worker } from "bullmq";
import {
deployApplication,
+ deployRemoteApplication,
rebuildApplication,
+ rebuildRemoteApplication,
updateApplicationStatus,
} from "../api/services/application";
import {
deployCompose,
+ deployRemoteCompose,
rebuildCompose,
+ rebuildRemoteCompose,
updateCompose,
} from "../api/services/compose";
import { myQueue, redisConfig } from "./queueSetup";
@@ -16,6 +20,7 @@ type DeployJob =
applicationId: string;
titleLog: string;
descriptionLog: string;
+ server?: boolean;
type: "deploy" | "redeploy";
applicationType: "application";
}
@@ -23,6 +28,7 @@ type DeployJob =
composeId: string;
titleLog: string;
descriptionLog: string;
+ server?: boolean;
type: "deploy" | "redeploy";
applicationType: "compose";
};
@@ -35,35 +41,68 @@ export const deploymentWorker = new Worker(
try {
if (job.data.applicationType === "application") {
await updateApplicationStatus(job.data.applicationId, "running");
- if (job.data.type === "redeploy") {
- await rebuildApplication({
- applicationId: job.data.applicationId,
- titleLog: job.data.titleLog,
- descriptionLog: job.data.descriptionLog,
- });
- } else if (job.data.type === "deploy") {
- await deployApplication({
- applicationId: job.data.applicationId,
- titleLog: job.data.titleLog,
- descriptionLog: job.data.descriptionLog,
- });
+ if (job.data.server) {
+ if (job.data.type === "redeploy") {
+ await rebuildRemoteApplication({
+ applicationId: job.data.applicationId,
+ titleLog: job.data.titleLog,
+ descriptionLog: job.data.descriptionLog,
+ });
+ } else if (job.data.type === "deploy") {
+ await deployRemoteApplication({
+ applicationId: job.data.applicationId,
+ titleLog: job.data.titleLog,
+ descriptionLog: job.data.descriptionLog,
+ });
+ }
+ } else {
+ if (job.data.type === "redeploy") {
+ await rebuildApplication({
+ applicationId: job.data.applicationId,
+ titleLog: job.data.titleLog,
+ descriptionLog: job.data.descriptionLog,
+ });
+ } else if (job.data.type === "deploy") {
+ await deployApplication({
+ applicationId: job.data.applicationId,
+ titleLog: job.data.titleLog,
+ descriptionLog: job.data.descriptionLog,
+ });
+ }
}
} else if (job.data.applicationType === "compose") {
await updateCompose(job.data.composeId, {
composeStatus: "running",
});
- if (job.data.type === "deploy") {
- await deployCompose({
- composeId: job.data.composeId,
- titleLog: job.data.titleLog,
- descriptionLog: job.data.descriptionLog,
- });
- } else if (job.data.type === "redeploy") {
- await rebuildCompose({
- composeId: job.data.composeId,
- titleLog: job.data.titleLog,
- descriptionLog: job.data.descriptionLog,
- });
+
+ if (job.data.server) {
+ if (job.data.type === "redeploy") {
+ await rebuildRemoteCompose({
+ composeId: job.data.composeId,
+ titleLog: job.data.titleLog,
+ descriptionLog: job.data.descriptionLog,
+ });
+ } else if (job.data.type === "deploy") {
+ await deployRemoteCompose({
+ composeId: job.data.composeId,
+ titleLog: job.data.titleLog,
+ descriptionLog: job.data.descriptionLog,
+ });
+ }
+ } else {
+ if (job.data.type === "deploy") {
+ await deployCompose({
+ composeId: job.data.composeId,
+ titleLog: job.data.titleLog,
+ descriptionLog: job.data.descriptionLog,
+ });
+ } else if (job.data.type === "redeploy") {
+ await rebuildCompose({
+ composeId: job.data.composeId,
+ titleLog: job.data.titleLog,
+ descriptionLog: job.data.descriptionLog,
+ });
+ }
}
}
} catch (error) {
diff --git a/apps/dokploy/server/queues/queueSetup.ts b/apps/dokploy/server/queues/queueSetup.ts
index 5df834fb5..650fbc03a 100644
--- a/apps/dokploy/server/queues/queueSetup.ts
+++ b/apps/dokploy/server/queues/queueSetup.ts
@@ -2,9 +2,7 @@ import { type ConnectionOptions, Queue } from "bullmq";
export const redisConfig: ConnectionOptions = {
host: process.env.NODE_ENV === "production" ? "dokploy-redis" : "127.0.0.1",
- port: 6379,
};
-// TODO: maybe add a options to clean the queue to the times
const myQueue = new Queue("deployments", {
connection: redisConfig,
});
diff --git a/apps/dokploy/server/server.ts b/apps/dokploy/server/server.ts
index 607a5fa07..d93d717e3 100644
--- a/apps/dokploy/server/server.ts
+++ b/apps/dokploy/server/server.ts
@@ -2,7 +2,9 @@ import http from "node:http";
import { migration } from "@/server/db/migration";
import { config } from "dotenv";
import next from "next";
+// import { IS_CLOUD } from "./constants";
import { deploymentWorker } from "./queues/deployments-queue";
+// import { deploymentWorker } from "./queues/deployments-queue";
import { setupDirectories } from "./setup/config-paths";
import { initializePostgres } from "./setup/postgres-setup";
import { initializeRedis } from "./setup/redis-setup";
@@ -43,6 +45,8 @@ void app.prepare().then(async () => {
setupDockerStatsMonitoringSocketServer(server);
if (process.env.NODE_ENV === "production") {
+ setupDirectories();
+ createDefaultMiddlewares();
setupDirectories();
createDefaultMiddlewares();
await initializeNetwork();
@@ -51,6 +55,7 @@ void app.prepare().then(async () => {
await initializePostgres();
await initializeTraefik();
await initializeRedis();
+
initCronJobs();
welcomeServer();
diff --git a/apps/dokploy/server/setup/config-paths.ts b/apps/dokploy/server/setup/config-paths.ts
index 3cf8f8415..190e438b0 100644
--- a/apps/dokploy/server/setup/config-paths.ts
+++ b/apps/dokploy/server/setup/config-paths.ts
@@ -1,15 +1,5 @@
-import { spawnSync } from "node:child_process";
import { chmodSync, existsSync, mkdirSync } from "node:fs";
-import {
- APPLICATIONS_PATH,
- BASE_PATH,
- CERTIFICATES_PATH,
- DYNAMIC_TRAEFIK_PATH,
- LOGS_PATH,
- MAIN_TRAEFIK_PATH,
- MONITORING_PATH,
- SSH_PATH,
-} from "../constants";
+import { paths } from "../constants";
const createDirectoryIfNotExist = (dirPath: string) => {
if (!existsSync(dirPath)) {
@@ -19,6 +9,16 @@ const createDirectoryIfNotExist = (dirPath: string) => {
};
export const setupDirectories = () => {
+ const {
+ APPLICATIONS_PATH,
+ BASE_PATH,
+ CERTIFICATES_PATH,
+ DYNAMIC_TRAEFIK_PATH,
+ LOGS_PATH,
+ MAIN_TRAEFIK_PATH,
+ MONITORING_PATH,
+ SSH_PATH,
+ } = paths();
const directories = [
BASE_PATH,
MAIN_TRAEFIK_PATH,
diff --git a/apps/dokploy/server/setup/registry-setup.ts b/apps/dokploy/server/setup/registry-setup.ts
index 688f2eadf..8b18b1088 100644
--- a/apps/dokploy/server/setup/registry-setup.ts
+++ b/apps/dokploy/server/setup/registry-setup.ts
@@ -1,6 +1,6 @@
import type { CreateServiceOptions } from "dockerode";
import { generateRandomPassword } from "../auth/random-password";
-import { REGISTRY_PATH, docker } from "../constants";
+import { docker, paths } from "../constants";
import { pullImage } from "../utils/docker/utils";
import { execAsync } from "../utils/process/execAsync";
@@ -8,6 +8,7 @@ export const initializeRegistry = async (
username: string,
password: string,
) => {
+ const { REGISTRY_PATH } = paths();
const imageName = "registry:2.8.3";
const containerName = "dokploy-registry";
await generateRegistryPassword(username, password);
@@ -78,6 +79,7 @@ export const initializeRegistry = async (
const generateRegistryPassword = async (username: string, password: string) => {
try {
+ const { REGISTRY_PATH } = paths();
const command = `htpasswd -nbB ${username} "${password}" > ${REGISTRY_PATH}/htpasswd`;
const result = await execAsync(command);
console.log("Password generated ✅");
diff --git a/apps/dokploy/server/setup/server-setup.ts b/apps/dokploy/server/setup/server-setup.ts
new file mode 100644
index 000000000..3b30b6c98
--- /dev/null
+++ b/apps/dokploy/server/setup/server-setup.ts
@@ -0,0 +1,300 @@
+import { createWriteStream } from "node:fs";
+import path from "node:path";
+import { slugify } from "@/lib/slug";
+import {
+ createServerDeployment,
+ updateDeploymentStatus,
+} from "@/server/api/services/deployment";
+import { findServerById } from "@/server/api/services/server";
+import { paths } from "@/server/constants";
+import {
+ getDefaultMiddlewares,
+ getDefaultServerTraefikConfig,
+} from "@/server/setup/traefik-setup";
+import { Client } from "ssh2";
+import { recreateDirectory } from "../utils/filesystem/directory";
+import { readSSHKey } from "../utils/filesystem/ssh";
+
+export const serverSetup = async (serverId: string) => {
+ const server = await findServerById(serverId);
+ const { LOGS_PATH } = paths();
+
+ const slugifyName = slugify(`server ${server.name}`);
+
+ const fullPath = path.join(LOGS_PATH, slugifyName);
+
+ await recreateDirectory(fullPath);
+
+ const deployment = await createServerDeployment({
+ serverId: server.serverId,
+ title: "Setup Server",
+ description: "Setup Server",
+ });
+
+ const writeStream = createWriteStream(deployment.logPath, { flags: "a" });
+ try {
+ writeStream.write("\nInstalling Server Dependencies: ✅\n");
+ await installRequirements(serverId, deployment.logPath);
+ writeStream.close();
+
+ await updateDeploymentStatus(deployment.deploymentId, "done");
+ } catch (err) {
+ console.log(err);
+ await updateDeploymentStatus(deployment.deploymentId, "error");
+ writeStream.write(err);
+ writeStream.close();
+ }
+};
+
+const installRequirements = async (serverId: string, logPath: string) => {
+ const writeStream = createWriteStream(logPath, { flags: "a" });
+ const client = new Client();
+ const server = await findServerById(serverId);
+ if (!server.sshKeyId) {
+ writeStream.write("❌ No SSH Key found");
+ writeStream.close();
+ throw new Error("No SSH Key found");
+ }
+ const keys = await readSSHKey(server.sshKeyId);
+
+ if (!keys.privateKey) {
+ writeStream.write("❌ No SSH Key found");
+ writeStream.close();
+ throw new Error("No SSH Key found");
+ }
+ return new Promise((resolve, reject) => {
+ client
+ .once("ready", () => {
+ const bashCommand = `
+
+ ${validatePorts()}
+
+ command_exists() {
+ command -v "$@" > /dev/null 2>&1
+ }
+ ${installRClone()}
+ ${installDocker()}
+ ${setupSwarm()}
+ ${setupNetwork()}
+ ${setupMainDirectory()}
+ ${setupDirectories()}
+ ${createTraefikConfig()}
+ ${createDefaultMiddlewares()}
+ ${createTraefikInstance()}
+ ${installNixpacks()}
+ ${installBuildpacks()}
+ `;
+
+ client.exec(bashCommand, (err, stream) => {
+ if (err) {
+ writeStream.write(err);
+ reject(err);
+ return;
+ }
+ stream
+ .on("close", () => {
+ writeStream.write("Connection closed ✅");
+ client.end();
+ resolve();
+ })
+ .on("data", (data: string) => {
+ writeStream.write(data.toString());
+ })
+ .stderr.on("data", (data) => {
+ writeStream.write(data.toString());
+ });
+ });
+ })
+ .on("error", (err) => {
+ client.end();
+ if (err.level === "client-authentication") {
+ writeStream.write(
+ `Authentication failed: Invalid SSH private key. ❌ Error: ${err.message} ${err.level}`,
+ );
+ reject(
+ new Error(
+ `Authentication failed: Invalid SSH private key. ❌ Error: ${err.message} ${err.level}`,
+ ),
+ );
+ } else {
+ writeStream.write(
+ `SSH connection error: ${err.message} ${err.level}`,
+ );
+ reject(new Error(`SSH connection error: ${err.message}`));
+ }
+ })
+ .connect({
+ host: server.ipAddress,
+ port: server.port,
+ username: server.username,
+ privateKey: keys.privateKey,
+ timeout: 99999,
+ });
+ });
+};
+
+const setupDirectories = () => {
+ const { SSH_PATH } = paths(true);
+ const directories = Object.values(paths(true));
+
+ const createDirsCommand = directories
+ .map((dir) => `mkdir -p "${dir}"`)
+ .join(" && ");
+ const chmodCommand = `chmod 700 "${SSH_PATH}"`;
+
+ const command = `
+ ${createDirsCommand}
+ ${chmodCommand}
+ `;
+
+ return command;
+};
+
+const setupMainDirectory = () => `
+ # Check if the /etc/dokploy directory exists
+ if [ -d /etc/dokploy ]; then
+ echo "/etc/dokploy already exists ✅"
+ else
+ # Create the /etc/dokploy directory
+ mkdir -p /etc/dokploy
+ chmod 777 /etc/dokploy
+
+ echo "Directory /etc/dokploy created ✅"
+ fi
+`;
+
+export const setupSwarm = () => `
+ # Check if the node is already part of a Docker Swarm
+ if docker info | grep -q 'Swarm: active'; then
+ echo "Already part of a Docker Swarm ✅"
+ else
+ # Get IP address
+ get_ip() {
+ # Try to get IPv4
+ local ipv4=\$(curl -4s https://ifconfig.io 2>/dev/null)
+
+ if [ -n "\$ipv4" ]; then
+ echo "\$ipv4"
+ else
+ # Try to get IPv6
+ local ipv6=\$(curl -6s https://ifconfig.io 2>/dev/null)
+ if [ -n "\$ipv6" ]; then
+ echo "\$ipv6"
+ fi
+ fi
+ }
+ advertise_addr=\$(get_ip)
+
+ # Initialize Docker Swarm
+ docker swarm init --advertise-addr \$advertise_addr
+ echo "Swarm initialized ✅"
+ fi
+ `;
+
+const setupNetwork = () => `
+ # Check if the dokploy-network already exists
+ if docker network ls | grep -q 'dokploy-network'; then
+ echo "Network dokploy-network already exists ✅"
+ else
+ # Create the dokploy-network if it doesn't exist
+ docker network create --driver overlay --attachable dokploy-network
+ echo "Network created ✅"
+ fi
+`;
+
+const installDocker = () => `
+ if command_exists docker; then
+ echo "Docker already installed ✅"
+ else
+ echo "Installing Docker ✅"
+ curl -sSL https://get.docker.com | sh -s -- --version 27.2.0
+ fi
+`;
+
+const validatePorts = () => `
+ # check if something is running on port 80
+ if ss -tulnp | grep ':80 ' >/dev/null; then
+ echo "Something is already running on port 80" >&2
+ fi
+
+ # check if something is running on port 443
+ if ss -tulnp | grep ':443 ' >/dev/null; then
+ echo "Something is already running on port 443" >&2
+ fi
+`;
+
+const createTraefikConfig = () => {
+ const config = getDefaultServerTraefikConfig();
+
+ const command = `
+ if [ -f "/etc/dokploy/traefik/dynamic/acme.json" ]; then
+ chmod 600 "/etc/dokploy/traefik/dynamic/acme.json"
+ fi
+ if [ -f "/etc/dokploy/traefik/traefik.yml" ]; then
+ echo "Traefik config already exists ✅"
+ else
+ echo "${config}" > /etc/dokploy/traefik/traefik.yml
+ fi
+ `;
+
+ return command;
+};
+
+export const createDefaultMiddlewares = () => {
+ const config = getDefaultMiddlewares();
+ const command = `
+ if [ -f "/etc/dokploy/traefik/dynamic/middlewares.yml" ]; then
+ echo "Middlewares config already exists ✅"
+ else
+ echo "${config}" > /etc/dokploy/traefik/dynamic/middlewares.yml
+ fi
+ `;
+ return command;
+};
+
+export const installRClone = () => `
+curl https://rclone.org/install.sh | sudo bash
+`;
+
+export const createTraefikInstance = () => {
+ const command = `
+ # Check if dokpyloy-traefik exists
+ if docker service ls | grep -q 'dokploy-traefik'; then
+ echo "Traefik already exists ✅"
+ else
+ # Create the dokploy-traefik service
+ docker service create \
+ --name dokploy-traefik \
+ --replicas 1 \
+ --constraint 'node.role==manager' \
+ --network dokploy-network \
+ --mount type=bind,src=/etc/dokploy/traefik/traefik.yml,dst=/etc/traefik/traefik.yml \
+ --mount type=bind,src=/etc/dokploy/traefik/dynamic,dst=/etc/dokploy/traefik/dynamic \
+ --mount type=bind,src=/var/run/docker.sock,dst=/var/run/docker.sock \
+ --label traefik.enable=true \
+ --publish mode=host,target=443,published=443 \
+ --publish mode=host,target=80,published=80 \
+ traefik:v3.1.2
+ fi
+ `;
+
+ return command;
+};
+
+const installNixpacks = () => `
+ if command_exists nixpacks; then
+ echo "Nixpacks already installed ✅"
+ else
+ VERSION=1.28.1 bash -c "$(curl -fsSL https://nixpacks.com/install.sh)"
+ echo "Nixpacks version 1.28.1 installed ✅"
+ fi
+`;
+
+const installBuildpacks = () => `
+ if command_exists pack; then
+ echo "Buildpacks already installed ✅"
+ else
+ curl -sSL "https://github.com/buildpacks/pack/releases/download/v0.35.0/pack-v0.35.0-linux.tgz" | tar -C /usr/local/bin/ --no-same-owner -xzv pack
+ echo "Buildpacks version 0.35.0 installed ✅"
+ fi
+`;
diff --git a/apps/dokploy/server/setup/traefik-setup.ts b/apps/dokploy/server/setup/traefik-setup.ts
index a6c1ceaa2..27bc99a4f 100644
--- a/apps/dokploy/server/setup/traefik-setup.ts
+++ b/apps/dokploy/server/setup/traefik-setup.ts
@@ -2,8 +2,9 @@ import { chmodSync, existsSync, mkdirSync, writeFileSync } from "node:fs";
import path from "node:path";
import type { ContainerTaskSpec, CreateServiceOptions } from "dockerode";
import { dump } from "js-yaml";
-import { DYNAMIC_TRAEFIK_PATH, MAIN_TRAEFIK_PATH, docker } from "../constants";
-import { pullImage } from "../utils/docker/utils";
+import { paths } from "../constants";
+import { pullImage, pullRemoteImage } from "../utils/docker/utils";
+import { getRemoteDocker } from "../utils/servers/remote-docker";
import type { FileConfig } from "../utils/traefik/file-types";
import type { MainTraefikConfig } from "../utils/traefik/types";
@@ -14,12 +15,15 @@ const TRAEFIK_PORT = Number.parseInt(process.env.TRAEFIK_PORT ?? "", 10) || 80;
interface TraefikOptions {
enableDashboard?: boolean;
env?: string[];
+ serverId?: string;
}
export const initializeTraefik = async ({
enableDashboard = false,
env,
+ serverId,
}: TraefikOptions = {}) => {
+ const { MAIN_TRAEFIK_PATH, DYNAMIC_TRAEFIK_PATH } = paths(!!serverId);
const imageName = "traefik:v3.1.2";
const containerName = "dokploy-traefik";
const settings: CreateServiceOptions = {
@@ -83,8 +87,13 @@ export const initializeTraefik = async ({
],
},
};
+ const docker = await getRemoteDocker(serverId);
try {
- await pullImage(imageName);
+ if (serverId) {
+ await pullRemoteImage(imageName, serverId);
+ } else {
+ await pullImage(imageName);
+ }
const service = docker.getService(containerName);
const inspect = await service.inspect();
@@ -115,6 +124,7 @@ export const initializeTraefik = async ({
};
export const createDefaultServerTraefikConfig = () => {
+ const { DYNAMIC_TRAEFIK_PATH } = paths();
const configFilePath = path.join(DYNAMIC_TRAEFIK_PATH, "dokploy.yml");
if (existsSync(configFilePath)) {
@@ -153,17 +163,7 @@ export const createDefaultServerTraefikConfig = () => {
);
};
-export const createDefaultTraefikConfig = () => {
- const mainConfig = path.join(MAIN_TRAEFIK_PATH, "traefik.yml");
- const acmeJsonPath = path.join(DYNAMIC_TRAEFIK_PATH, "acme.json");
-
- if (existsSync(acmeJsonPath)) {
- chmodSync(acmeJsonPath, "600");
- }
- if (existsSync(mainConfig)) {
- console.log("Main config already exists");
- return;
- }
+export const getDefaultTraefikConfig = () => {
const configObject: MainTraefikConfig = {
providers: {
...(process.env.NODE_ENV === "development"
@@ -221,16 +221,77 @@ export const createDefaultTraefikConfig = () => {
};
const yamlStr = dump(configObject);
+
+ return yamlStr;
+};
+
+export const getDefaultServerTraefikConfig = () => {
+ const configObject: MainTraefikConfig = {
+ providers: {
+ swarm: {
+ exposedByDefault: false,
+ watch: false,
+ },
+ docker: {
+ exposedByDefault: false,
+ },
+ file: {
+ directory: "/etc/dokploy/traefik/dynamic",
+ watch: true,
+ },
+ },
+ entryPoints: {
+ web: {
+ address: `:${TRAEFIK_PORT}`,
+ },
+ websecure: {
+ address: `:${TRAEFIK_SSL_PORT}`,
+ http: {
+ tls: {
+ certResolver: "letsencrypt",
+ },
+ },
+ },
+ },
+ api: {
+ insecure: true,
+ },
+ certificatesResolvers: {
+ letsencrypt: {
+ acme: {
+ email: "test@localhost.com",
+ storage: "/etc/dokploy/traefik/dynamic/acme.json",
+ httpChallenge: {
+ entryPoint: "web",
+ },
+ },
+ },
+ },
+ };
+
+ const yamlStr = dump(configObject);
+
+ return yamlStr;
+};
+
+export const createDefaultTraefikConfig = () => {
+ const { MAIN_TRAEFIK_PATH, DYNAMIC_TRAEFIK_PATH } = paths();
+ const mainConfig = path.join(MAIN_TRAEFIK_PATH, "traefik.yml");
+ const acmeJsonPath = path.join(DYNAMIC_TRAEFIK_PATH, "acme.json");
+
+ if (existsSync(acmeJsonPath)) {
+ chmodSync(acmeJsonPath, "600");
+ }
+ if (existsSync(mainConfig)) {
+ console.log("Main config already exists");
+ return;
+ }
+ const yamlStr = getDefaultTraefikConfig();
mkdirSync(MAIN_TRAEFIK_PATH, { recursive: true });
writeFileSync(mainConfig, yamlStr, "utf8");
};
-export const createDefaultMiddlewares = () => {
- const middlewaresPath = path.join(DYNAMIC_TRAEFIK_PATH, "middlewares.yml");
- if (existsSync(middlewaresPath)) {
- console.log("Default middlewares already exists");
- return;
- }
+export const getDefaultMiddlewares = () => {
const defaultMiddlewares = {
http: {
middlewares: {
@@ -244,6 +305,16 @@ export const createDefaultMiddlewares = () => {
},
};
const yamlStr = dump(defaultMiddlewares);
+ return yamlStr;
+};
+export const createDefaultMiddlewares = () => {
+ const { DYNAMIC_TRAEFIK_PATH } = paths();
+ const middlewaresPath = path.join(DYNAMIC_TRAEFIK_PATH, "middlewares.yml");
+ if (existsSync(middlewaresPath)) {
+ console.log("Default middlewares already exists");
+ return;
+ }
+ const yamlStr = getDefaultMiddlewares();
mkdirSync(DYNAMIC_TRAEFIK_PATH, { recursive: true });
writeFileSync(middlewaresPath, yamlStr, "utf8");
};
diff --git a/apps/dokploy/server/utils/access-log/handler.ts b/apps/dokploy/server/utils/access-log/handler.ts
index 3fd7e5c99..24baa023b 100644
--- a/apps/dokploy/server/utils/access-log/handler.ts
+++ b/apps/dokploy/server/utils/access-log/handler.ts
@@ -1,5 +1,5 @@
import { findAdmin, updateAdmin } from "@/server/api/services/admin";
-import { DYNAMIC_TRAEFIK_PATH } from "@/server/constants";
+import { paths } from "@/server/constants";
import { type RotatingFileStream, createStream } from "rotating-file-stream";
import { execAsync } from "../process/execAsync";
@@ -39,6 +39,7 @@ class LogRotationManager {
}
private async activateStream(): Promise {
+ const { DYNAMIC_TRAEFIK_PATH } = paths();
if (this.stream) {
await this.deactivateStream();
}
diff --git a/apps/dokploy/server/utils/backups/index.ts b/apps/dokploy/server/utils/backups/index.ts
index b23f9cb39..747611d98 100644
--- a/apps/dokploy/server/utils/backups/index.ts
+++ b/apps/dokploy/server/utils/backups/index.ts
@@ -1,4 +1,5 @@
import { findAdmin } from "@/server/api/services/admin";
+import { getAllServers } from "@/server/api/services/server";
import { scheduleJob } from "node-schedule";
import { db } from "../../db/index";
import {
@@ -27,6 +28,22 @@ export const initCronJobs = async () => {
});
}
+ const servers = await getAllServers();
+
+ for (const server of servers) {
+ const { appName, serverId } = server;
+ if (serverId) {
+ scheduleJob(serverId, "0 0 * * *", async () => {
+ console.log(
+ `SERVER-BACKUP[${new Date().toLocaleString()}] Running Cleanup ${appName}`,
+ );
+ await cleanUpUnusedImages(serverId);
+ await cleanUpDockerBuilder(serverId);
+ await cleanUpSystemPrune(serverId);
+ });
+ }
+ }
+
const pgs = await db.query.postgres.findMany({
with: {
backups: {
diff --git a/apps/dokploy/server/utils/backups/mariadb.ts b/apps/dokploy/server/utils/backups/mariadb.ts
index 39278fb31..84c520478 100644
--- a/apps/dokploy/server/utils/backups/mariadb.ts
+++ b/apps/dokploy/server/utils/backups/mariadb.ts
@@ -1,12 +1,14 @@
-import { unlink } from "node:fs/promises";
import path from "node:path";
import type { BackupSchedule } from "@/server/api/services/backup";
import type { Mariadb } from "@/server/api/services/mariadb";
import { findProjectById } from "@/server/api/services/project";
-import { getServiceContainer } from "../docker/utils";
+import {
+ getRemoteServiceContainer,
+ getServiceContainer,
+} from "../docker/utils";
import { sendDatabaseBackupNotifications } from "../notifications/database-backup";
-import { execAsync } from "../process/execAsync";
-import { uploadToS3 } from "./utils";
+import { execAsync, execAsyncRemote } from "../process/execAsync";
+import { getS3Credentials } from "./utils";
export const runMariadbBackup = async (
mariadb: Mariadb,
@@ -18,22 +20,29 @@ export const runMariadbBackup = async (
const destination = backup.destination;
const backupFileName = `${new Date().toISOString()}.sql.gz`;
const bucketDestination = path.join(prefix, backupFileName);
- const containerPath = `/backup/${backupFileName}`;
- const hostPath = `./${backupFileName}`;
try {
- const { Id: containerId } = await getServiceContainer(appName);
- await execAsync(
- `docker exec ${containerId} sh -c "rm -rf /backup && mkdir -p /backup"`,
- );
+ const rcloneFlags = getS3Credentials(destination);
+ const rcloneDestination = `:s3:${destination.bucket}/${bucketDestination}`;
- await execAsync(
- `docker exec ${containerId} sh -c "mariadb-dump --user='${databaseUser}' --password='${databasePassword}' --databases ${database} | gzip > ${containerPath}"`,
- );
- await execAsync(
- `docker cp ${containerId}:/backup/${backupFileName} ${hostPath}`,
- );
- await uploadToS3(destination, bucketDestination, hostPath);
+ const rcloneCommand = `rclone rcat ${rcloneFlags.join(" ")} "${rcloneDestination}"`;
+ if (mariadb.serverId) {
+ const { Id: containerId } = await getRemoteServiceContainer(
+ mariadb.serverId,
+ appName,
+ );
+ const mariadbDumpCommand = `docker exec ${containerId} sh -c "mariadb-dump --user='${databaseUser}' --password='${databasePassword}' --databases ${database} | gzip"`;
+
+ await execAsyncRemote(
+ mariadb.serverId,
+ `${mariadbDumpCommand} | ${rcloneCommand}`,
+ );
+ } else {
+ const { Id: containerId } = await getServiceContainer(appName);
+ const mariadbDumpCommand = `docker exec ${containerId} sh -c "mariadb-dump --user='${databaseUser}' --password='${databasePassword}' --databases ${database} | gzip"`;
+
+ await execAsync(`${mariadbDumpCommand} | ${rcloneCommand}`);
+ }
await sendDatabaseBackupNotifications({
applicationName: name,
@@ -52,7 +61,5 @@ export const runMariadbBackup = async (
errorMessage: error?.message || "Error message not provided",
});
throw error;
- } finally {
- await unlink(hostPath);
}
};
diff --git a/apps/dokploy/server/utils/backups/mongo.ts b/apps/dokploy/server/utils/backups/mongo.ts
index 15f0e897b..1ce592939 100644
--- a/apps/dokploy/server/utils/backups/mongo.ts
+++ b/apps/dokploy/server/utils/backups/mongo.ts
@@ -1,12 +1,14 @@
-import { unlink } from "node:fs/promises";
import path from "node:path";
import type { BackupSchedule } from "@/server/api/services/backup";
import type { Mongo } from "@/server/api/services/mongo";
import { findProjectById } from "@/server/api/services/project";
-import { getServiceContainer } from "../docker/utils";
+import {
+ getRemoteServiceContainer,
+ getServiceContainer,
+} from "../docker/utils";
import { sendDatabaseBackupNotifications } from "../notifications/database-backup";
-import { execAsync } from "../process/execAsync";
-import { uploadToS3 } from "./utils";
+import { execAsync, execAsyncRemote } from "../process/execAsync";
+import { getS3Credentials } from "./utils";
// mongodb://mongo:Bqh7AQl-PRbnBu@localhost:27017/?tls=false&directConnection=true
export const runMongoBackup = async (mongo: Mongo, backup: BackupSchedule) => {
@@ -16,20 +18,28 @@ export const runMongoBackup = async (mongo: Mongo, backup: BackupSchedule) => {
const destination = backup.destination;
const backupFileName = `${new Date().toISOString()}.dump.gz`;
const bucketDestination = path.join(prefix, backupFileName);
- const containerPath = `/backup/${backupFileName}`;
- const hostPath = `./${backupFileName}`;
try {
- const { Id: containerId } = await getServiceContainer(appName);
- await execAsync(
- `docker exec ${containerId} sh -c "rm -rf /backup && mkdir -p /backup"`,
- );
+ const rcloneFlags = getS3Credentials(destination);
+ const rcloneDestination = `:s3:${destination.bucket}/${bucketDestination}`;
- await execAsync(
- `docker exec ${containerId} sh -c "mongodump -d '${database}' -u '${databaseUser}' -p '${databasePassword}' --authenticationDatabase=admin --archive=${containerPath} --gzip"`,
- );
- await execAsync(`docker cp ${containerId}:${containerPath} ${hostPath}`);
- await uploadToS3(destination, bucketDestination, hostPath);
+ const rcloneCommand = `rclone rcat ${rcloneFlags.join(" ")} "${rcloneDestination}"`;
+ if (mongo.serverId) {
+ const { Id: containerId } = await getRemoteServiceContainer(
+ mongo.serverId,
+ appName,
+ );
+ const mongoDumpCommand = `docker exec ${containerId} sh -c "mongodump -d '${database}' -u '${databaseUser}' -p '${databasePassword}' --authenticationDatabase=admin --gzip"`;
+
+ await execAsyncRemote(
+ mongo.serverId,
+ `${mongoDumpCommand} | ${rcloneCommand}`,
+ );
+ } else {
+ const { Id: containerId } = await getServiceContainer(appName);
+ const mongoDumpCommand = `docker exec ${containerId} sh -c "mongodump -d '${database}' -u '${databaseUser}' -p '${databasePassword}' --authenticationDatabase=admin --gzip"`;
+ await execAsync(`${mongoDumpCommand} | ${rcloneCommand}`);
+ }
await sendDatabaseBackupNotifications({
applicationName: name,
@@ -48,8 +58,6 @@ export const runMongoBackup = async (mongo: Mongo, backup: BackupSchedule) => {
errorMessage: error?.message || "Error message not provided",
});
throw error;
- } finally {
- await unlink(hostPath);
}
};
// mongorestore -d monguito -u mongo -p Bqh7AQl-PRbnBu --authenticationDatabase admin --gzip --archive=2024-04-13T05:03:58.937Z.dump.gz
diff --git a/apps/dokploy/server/utils/backups/mysql.ts b/apps/dokploy/server/utils/backups/mysql.ts
index 4d4801fab..a2de63219 100644
--- a/apps/dokploy/server/utils/backups/mysql.ts
+++ b/apps/dokploy/server/utils/backups/mysql.ts
@@ -3,10 +3,13 @@ import path from "node:path";
import type { BackupSchedule } from "@/server/api/services/backup";
import type { MySql } from "@/server/api/services/mysql";
import { findProjectById } from "@/server/api/services/project";
-import { getServiceContainer } from "../docker/utils";
+import {
+ getRemoteServiceContainer,
+ getServiceContainer,
+} from "../docker/utils";
import { sendDatabaseBackupNotifications } from "../notifications/database-backup";
-import { execAsync } from "../process/execAsync";
-import { uploadToS3 } from "./utils";
+import { execAsync, execAsyncRemote } from "../process/execAsync";
+import { getS3Credentials } from "./utils";
export const runMySqlBackup = async (mysql: MySql, backup: BackupSchedule) => {
const { appName, databaseRootPassword, projectId, name } = mysql;
@@ -15,23 +18,29 @@ export const runMySqlBackup = async (mysql: MySql, backup: BackupSchedule) => {
const destination = backup.destination;
const backupFileName = `${new Date().toISOString()}.sql.gz`;
const bucketDestination = path.join(prefix, backupFileName);
- const containerPath = `/backup/${backupFileName}`;
- const hostPath = `./${backupFileName}`;
try {
- const { Id: containerId } = await getServiceContainer(appName);
+ const rcloneFlags = getS3Credentials(destination);
+ const rcloneDestination = `:s3:${destination.bucket}/${bucketDestination}`;
- await execAsync(
- `docker exec ${containerId} sh -c "rm -rf /backup && mkdir -p /backup"`,
- );
+ const rcloneCommand = `rclone rcat ${rcloneFlags.join(" ")} "${rcloneDestination}"`;
+ if (mysql.serverId) {
+ const { Id: containerId } = await getRemoteServiceContainer(
+ mysql.serverId,
+ appName,
+ );
+ const mysqlDumpCommand = `docker exec ${containerId} sh -c "mysqldump --default-character-set=utf8mb4 -u 'root' --password='${databaseRootPassword}' --single-transaction --no-tablespaces --quick '${database}' | gzip"`;
- await execAsync(
- `docker exec ${containerId} sh -c "mysqldump --default-character-set=utf8mb4 -u 'root' --password='${databaseRootPassword}' --single-transaction --no-tablespaces --quick '${database}' | gzip > ${containerPath}"`,
- );
- await execAsync(
- `docker cp ${containerId}:/backup/${backupFileName} ${hostPath}`,
- );
- await uploadToS3(destination, bucketDestination, hostPath);
+ await execAsyncRemote(
+ mysql.serverId,
+ `${mysqlDumpCommand} | ${rcloneCommand}`,
+ );
+ } else {
+ const { Id: containerId } = await getServiceContainer(appName);
+ const mysqlDumpCommand = `docker exec ${containerId} sh -c "mysqldump --default-character-set=utf8mb4 -u 'root' --password='${databaseRootPassword}' --single-transaction --no-tablespaces --quick '${database}' | gzip"`;
+
+ await execAsync(`${mysqlDumpCommand} | ${rcloneCommand}`);
+ }
await sendDatabaseBackupNotifications({
applicationName: name,
projectName: project.name,
@@ -49,7 +58,5 @@ export const runMySqlBackup = async (mysql: MySql, backup: BackupSchedule) => {
errorMessage: error?.message || "Error message not provided",
});
throw error;
- } finally {
- await unlink(hostPath);
}
};
diff --git a/apps/dokploy/server/utils/backups/postgres.ts b/apps/dokploy/server/utils/backups/postgres.ts
index ec7af81fe..236fec580 100644
--- a/apps/dokploy/server/utils/backups/postgres.ts
+++ b/apps/dokploy/server/utils/backups/postgres.ts
@@ -1,12 +1,14 @@
-import { unlink } from "node:fs/promises";
import path from "node:path";
import type { BackupSchedule } from "@/server/api/services/backup";
import type { Postgres } from "@/server/api/services/postgres";
import { findProjectById } from "@/server/api/services/project";
-import { getServiceContainer } from "../docker/utils";
+import {
+ getRemoteServiceContainer,
+ getServiceContainer,
+} from "../docker/utils";
import { sendDatabaseBackupNotifications } from "../notifications/database-backup";
-import { execAsync } from "../process/execAsync";
-import { uploadToS3 } from "./utils";
+import { execAsync, execAsyncRemote } from "../process/execAsync";
+import { getS3Credentials } from "./utils";
export const runPostgresBackup = async (
postgres: Postgres,
@@ -19,20 +21,29 @@ export const runPostgresBackup = async (
const destination = backup.destination;
const backupFileName = `${new Date().toISOString()}.sql.gz`;
const bucketDestination = path.join(prefix, backupFileName);
- const containerPath = `/backup/${backupFileName}`;
- const hostPath = `./${backupFileName}`;
try {
- const { Id: containerId } = await getServiceContainer(appName);
+ const rcloneFlags = getS3Credentials(destination);
+ const rcloneDestination = `:s3:${destination.bucket}/${bucketDestination}`;
- await execAsync(
- `docker exec ${containerId} /bin/bash -c "rm -rf /backup && mkdir -p /backup"`,
- );
- await execAsync(
- `docker exec ${containerId} sh -c "pg_dump -Fc --no-acl --no-owner -h localhost -U ${databaseUser} --no-password '${database}' | gzip > ${containerPath}"`,
- );
- await execAsync(`docker cp ${containerId}:${containerPath} ${hostPath}`);
+ const rcloneCommand = `rclone rcat ${rcloneFlags.join(" ")} "${rcloneDestination}"`;
+ if (postgres.serverId) {
+ const { Id: containerId } = await getRemoteServiceContainer(
+ postgres.serverId,
+ appName,
+ );
+ const pgDumpCommand = `docker exec ${containerId} sh -c "pg_dump -Fc --no-acl --no-owner -h localhost -U ${databaseUser} --no-password '${database}' | gzip"`;
+
+ await execAsyncRemote(
+ postgres.serverId,
+ `${pgDumpCommand} | ${rcloneCommand}`,
+ );
+ } else {
+ const { Id: containerId } = await getServiceContainer(appName);
+
+ const pgDumpCommand = `docker exec ${containerId} sh -c "pg_dump -Fc --no-acl --no-owner -h localhost -U ${databaseUser} --no-password '${database}' | gzip"`;
+ await execAsync(`${pgDumpCommand} | ${rcloneCommand}`);
+ }
- await uploadToS3(destination, bucketDestination, hostPath);
await sendDatabaseBackupNotifications({
applicationName: name,
projectName: project.name,
@@ -51,7 +62,6 @@ export const runPostgresBackup = async (
throw error;
} finally {
- await unlink(hostPath);
}
};
diff --git a/apps/dokploy/server/utils/backups/utils.ts b/apps/dokploy/server/utils/backups/utils.ts
index 3d9eff6a2..41ba15359 100644
--- a/apps/dokploy/server/utils/backups/utils.ts
+++ b/apps/dokploy/server/utils/backups/utils.ts
@@ -1,42 +1,11 @@
-import { readFile } from "node:fs/promises";
import type { BackupSchedule } from "@/server/api/services/backup";
import type { Destination } from "@/server/api/services/destination";
-import { PutObjectCommand, S3Client } from "@aws-sdk/client-s3";
import { scheduleJob, scheduledJobs } from "node-schedule";
import { runMariadbBackup } from "./mariadb";
import { runMongoBackup } from "./mongo";
import { runMySqlBackup } from "./mysql";
import { runPostgresBackup } from "./postgres";
-export const uploadToS3 = async (
- destination: Destination,
- destinationBucketPath: string,
- filePath: string,
-) => {
- const { accessKey, secretAccessKey, bucket, region, endpoint } = destination;
-
- const s3Client = new S3Client({
- region: region,
- ...(endpoint && {
- endpoint: endpoint,
- }),
- credentials: {
- accessKeyId: accessKey,
- secretAccessKey: secretAccessKey,
- },
- forcePathStyle: true,
- });
-
- const fileContent = await readFile(filePath);
- const command = new PutObjectCommand({
- Bucket: bucket,
- Key: destinationBucketPath,
- Body: fileContent,
- });
-
- await s3Client.send(command);
-};
-
export const scheduleBackup = (backup: BackupSchedule) => {
const { schedule, backupId, databaseType, postgres, mysql, mongo, mariadb } =
backup;
@@ -57,3 +26,18 @@ export const removeScheduleBackup = (backupId: string) => {
const currentJob = scheduledJobs[backupId];
currentJob?.cancel();
};
+
+export const getS3Credentials = (destination: Destination) => {
+ const { accessKey, secretAccessKey, bucket, region, endpoint } = destination;
+ const rcloneFlags = [
+ // `--s3-provider=Cloudflare`,
+ `--s3-access-key-id=${accessKey}`,
+ `--s3-secret-access-key=${secretAccessKey}`,
+ `--s3-region=${region}`,
+ `--s3-endpoint=${endpoint}`,
+ "--s3-no-check-bucket",
+ "--s3-force-path-style",
+ ];
+
+ return rcloneFlags;
+};
diff --git a/apps/dokploy/server/utils/builders/compose.ts b/apps/dokploy/server/utils/builders/compose.ts
index cc0711319..47ec15201 100644
--- a/apps/dokploy/server/utils/builders/compose.ts
+++ b/apps/dokploy/server/utils/builders/compose.ts
@@ -5,11 +5,15 @@ import {
writeFileSync,
} from "node:fs";
import { dirname, join } from "node:path";
-import { COMPOSE_PATH } from "@/server/constants";
+import { paths } from "@/server/constants";
import type { InferResultType } from "@/server/types/with";
import boxen from "boxen";
-import { writeDomainsToCompose } from "../docker/domain";
-import { prepareEnvironmentVariables } from "../docker/utils";
+import {
+ writeDomainsToCompose,
+ writeDomainsToComposeRemote,
+} from "../docker/domain";
+import { encodeBase64, prepareEnvironmentVariables } from "../docker/utils";
+import { execAsyncRemote } from "../process/execAsync";
import { spawnAsync } from "../process/spawnAsync";
export type ComposeNested = InferResultType<
@@ -20,6 +24,7 @@ export const buildCompose = async (compose: ComposeNested, logPath: string) => {
const writeStream = createWriteStream(logPath, { flags: "a" });
const { sourceType, appName, mounts, composeType, domains } = compose;
try {
+ const { COMPOSE_PATH } = paths();
const command = createCommand(compose);
await writeDomainsToCompose(compose, domains);
createEnvFile(compose);
@@ -69,6 +74,63 @@ Compose Type: ${composeType} ✅`;
}
};
+export const getBuildComposeCommand = async (
+ compose: ComposeNested,
+ logPath: string,
+) => {
+ const { COMPOSE_PATH } = paths(true);
+ const { sourceType, appName, mounts, composeType, domains, composePath } =
+ compose;
+ const command = createCommand(compose);
+ const envCommand = getCreateEnvFileCommand(compose);
+ const projectPath = join(COMPOSE_PATH, compose.appName, "code");
+
+ const newCompose = await writeDomainsToComposeRemote(
+ compose,
+ domains,
+ logPath,
+ );
+ const logContent = `
+App Name: ${appName}
+Build Compose 🐳
+Detected: ${mounts.length} mounts 📂
+Command: docker ${command}
+Source Type: docker ${sourceType} ✅
+Compose Type: ${composeType} ✅`;
+
+ const logBox = boxen(logContent, {
+ padding: {
+ left: 1,
+ right: 1,
+ bottom: 1,
+ },
+ width: 80,
+ borderStyle: "double",
+ });
+
+ const bashCommand = `
+ set -e
+ {
+ echo "${logBox}" >> "${logPath}"
+
+ ${newCompose}
+
+ ${envCommand}
+
+ cd "${projectPath}";
+
+ docker ${command.split(" ").join(" ")} >> "${logPath}" 2>&1 || { echo "Error: ❌ Docker command failed" >> "${logPath}"; exit 1; }
+
+ echo "Docker Compose Deployed: ✅" >> "${logPath}"
+ } || {
+ echo "Error: ❌ Script execution failed" >> "${logPath}"
+ exit 1
+ }
+ `;
+
+ return await execAsyncRemote(compose.serverId, bashCommand);
+};
+
const sanitizeCommand = (command: string) => {
const sanitizedCommand = command.trim();
@@ -102,6 +164,7 @@ export const createCommand = (compose: ComposeNested) => {
};
const createEnvFile = (compose: ComposeNested) => {
+ const { COMPOSE_PATH } = paths();
const { env, composePath, appName } = compose;
const composeFilePath =
join(COMPOSE_PATH, appName, "code", composePath) ||
@@ -124,3 +187,30 @@ const createEnvFile = (compose: ComposeNested) => {
}
writeFileSync(envFilePath, envFileContent);
};
+
+export const getCreateEnvFileCommand = (compose: ComposeNested) => {
+ const { COMPOSE_PATH } = paths(true);
+ const { env, composePath, appName } = compose;
+ const composeFilePath =
+ join(COMPOSE_PATH, appName, "code", composePath) ||
+ join(COMPOSE_PATH, appName, "code", "docker-compose.yml");
+
+ const envFilePath = join(dirname(composeFilePath), ".env");
+
+ let envContent = env || "";
+ if (!envContent.includes("DOCKER_CONFIG")) {
+ envContent += "\nDOCKER_CONFIG=/root/.docker/config.json";
+ }
+
+ if (compose.randomize) {
+ envContent += `\nCOMPOSE_PREFIX=${compose.suffix}`;
+ }
+
+ const envFileContent = prepareEnvironmentVariables(envContent).join("\n");
+
+ const encodedContent = encodeBase64(envFileContent);
+ return `
+touch ${envFilePath};
+echo "${encodedContent}" | base64 -d > "${envFilePath}";
+ `;
+};
diff --git a/apps/dokploy/server/utils/builders/docker-file.ts b/apps/dokploy/server/utils/builders/docker-file.ts
index 8a1962c9c..ab5cc0669 100644
--- a/apps/dokploy/server/utils/builders/docker-file.ts
+++ b/apps/dokploy/server/utils/builders/docker-file.ts
@@ -6,7 +6,7 @@ import {
getDockerContextPath,
} from "../filesystem/directory";
import { spawnAsync } from "../process/spawnAsync";
-import { createEnvFile } from "./utils";
+import { createEnvFile, createEnvFileCommand } from "./utils";
export const buildCustomDocker = async (
application: ApplicationNested,
@@ -57,3 +57,60 @@ export const buildCustomDocker = async (
throw error;
}
};
+
+export const getDockerCommand = (
+ application: ApplicationNested,
+ logPath: string,
+) => {
+ const { appName, env, publishDirectory, buildArgs, dockerBuildStage } =
+ application;
+ const dockerFilePath = getBuildAppDirectory(application);
+
+ try {
+ const image = `${appName}`;
+
+ const defaultContextPath =
+ dockerFilePath.substring(0, dockerFilePath.lastIndexOf("/") + 1) || ".";
+ const args = prepareEnvironmentVariables(buildArgs);
+
+ const dockerContextPath =
+ getDockerContextPath(application) || defaultContextPath;
+
+ const commandArgs = ["build", "-t", image, "-f", dockerFilePath, "."];
+
+ if (dockerBuildStage) {
+ commandArgs.push("--target", dockerBuildStage);
+ }
+
+ for (const arg of args) {
+ commandArgs.push("--build-arg", arg);
+ }
+
+ /*
+ Do not generate an environment file when publishDirectory is specified,
+ as it could be publicly exposed.
+ */
+ let command = "";
+ if (!publishDirectory) {
+ command += createEnvFileCommand(dockerFilePath, env);
+ }
+
+ command = `
+echo "Building ${appName}" >> ${logPath};
+cd ${dockerContextPath} >> ${logPath} 2>> ${logPath} || {
+ echo "❌ The path ${dockerContextPath} does not exist" >> ${logPath};
+ exit 1;
+}
+
+docker ${commandArgs.join(" ")} >> ${logPath} 2>> ${logPath} || {
+ echo "❌ Docker build failed" >> ${logPath};
+ exit 1;
+}
+echo "✅ Docker build completed." >> ${logPath};
+ `;
+
+ return command;
+ } catch (error) {
+ throw error;
+ }
+};
diff --git a/apps/dokploy/server/utils/builders/drop.ts b/apps/dokploy/server/utils/builders/drop.ts
index a61981c51..b376251af 100644
--- a/apps/dokploy/server/utils/builders/drop.ts
+++ b/apps/dokploy/server/utils/builders/drop.ts
@@ -1,13 +1,29 @@
import fs from "node:fs/promises";
import path, { join } from "node:path";
-import { APPLICATIONS_PATH } from "@/server/constants";
+import type { Application } from "@/server/api/services/application";
+import { findServerById } from "@/server/api/services/server";
+import { paths } from "@/server/constants";
import AdmZip from "adm-zip";
-import { recreateDirectory } from "../filesystem/directory";
+import { Client, type SFTPWrapper } from "ssh2";
+import {
+ recreateDirectory,
+ recreateDirectoryRemote,
+} from "../filesystem/directory";
+import { readSSHKey } from "../filesystem/ssh";
+import { execAsyncRemote } from "../process/execAsync";
+
+export const unzipDrop = async (zipFile: File, application: Application) => {
+ let sftp: SFTPWrapper | null = null;
-export const unzipDrop = async (zipFile: File, appName: string) => {
try {
+ const { appName } = application;
+ const { APPLICATIONS_PATH } = paths(!!application.serverId);
const outputPath = join(APPLICATIONS_PATH, appName, "code");
- await recreateDirectory(outputPath);
+ if (application.serverId) {
+ await recreateDirectoryRemote(outputPath, application.serverId);
+ } else {
+ await recreateDirectory(outputPath);
+ }
const arrayBuffer = await zipFile.arrayBuffer();
const buffer = Buffer.from(arrayBuffer);
@@ -28,6 +44,9 @@ export const unzipDrop = async (zipFile: File, appName: string) => {
? rootEntries[0]?.entryName.split("/")[0]
: "";
+ if (application.serverId) {
+ sftp = await getSFTPConnection(application.serverId);
+ }
for (const entry of zipEntries) {
let filePath = entry.entryName;
@@ -42,15 +61,64 @@ export const unzipDrop = async (zipFile: File, appName: string) => {
if (!filePath) continue;
const fullPath = path.join(outputPath, filePath);
- if (entry.isDirectory) {
- await fs.mkdir(fullPath, { recursive: true });
+
+ if (application.serverId) {
+ if (entry.isDirectory) {
+ await execAsyncRemote(application.serverId, `mkdir -p ${fullPath}`);
+ } else {
+ if (sftp === null) throw new Error("No SFTP connection available");
+ await uploadFileToServer(sftp, entry.getData(), fullPath);
+ }
} else {
- await fs.mkdir(path.dirname(fullPath), { recursive: true });
- await fs.writeFile(fullPath, entry.getData());
+ if (entry.isDirectory) {
+ await fs.mkdir(fullPath, { recursive: true });
+ } else {
+ await fs.mkdir(path.dirname(fullPath), { recursive: true });
+ await fs.writeFile(fullPath, entry.getData());
+ }
}
}
} catch (error) {
console.error("Error processing ZIP file:", error);
throw error;
+ } finally {
+ sftp?.end();
}
};
+
+const getSFTPConnection = async (serverId: string): Promise => {
+ const server = await findServerById(serverId);
+ if (!server.sshKeyId) throw new Error("No SSH key available for this server");
+
+ const keys = await readSSHKey(server.sshKeyId);
+ return new Promise((resolve, reject) => {
+ const conn = new Client();
+ conn
+ .on("ready", () => {
+ conn.sftp((err, sftp) => {
+ if (err) return reject(err);
+ resolve(sftp);
+ });
+ })
+ .connect({
+ host: server.ipAddress,
+ port: server.port,
+ username: server.username,
+ privateKey: keys.privateKey,
+ timeout: 99999,
+ });
+ });
+};
+
+const uploadFileToServer = (
+ sftp: SFTPWrapper,
+ data: Buffer,
+ remotePath: string,
+): Promise => {
+ return new Promise((resolve, reject) => {
+ sftp.writeFile(remotePath, data, (err) => {
+ if (err) return reject(err);
+ resolve();
+ });
+ });
+};
diff --git a/apps/dokploy/server/utils/builders/heroku.ts b/apps/dokploy/server/utils/builders/heroku.ts
index ebe872b22..be7f7877b 100644
--- a/apps/dokploy/server/utils/builders/heroku.ts
+++ b/apps/dokploy/server/utils/builders/heroku.ts
@@ -30,11 +30,44 @@ export const buildHeroku = async (
if (writeStream.writable) {
writeStream.write(data);
}
- // Stream the data
- console.log(data);
});
return true;
} catch (e) {
throw e;
}
};
+
+export const getHerokuCommand = (
+ application: ApplicationNested,
+ logPath: string,
+) => {
+ const { env, appName } = application;
+
+ const buildAppDirectory = getBuildAppDirectory(application);
+ const envVariables = prepareEnvironmentVariables(env);
+
+ const args = [
+ "build",
+ appName,
+ "--path",
+ buildAppDirectory,
+ "--builder",
+ "heroku/builder:24",
+ ];
+
+ for (const env of envVariables) {
+ args.push("--env", env);
+ }
+
+ const command = `pack ${args.join(" ")}`;
+ const bashCommand = `
+echo "Starting heroku build..." >> ${logPath};
+${command} >> ${logPath} 2>> ${logPath} || {
+ echo "❌ Heroku build failed" >> ${logPath};
+ exit 1;
+}
+echo "✅ Heroku build completed." >> ${logPath};
+ `;
+
+ return bashCommand;
+};
diff --git a/apps/dokploy/server/utils/builders/index.ts b/apps/dokploy/server/utils/builders/index.ts
index b3ca2e05e..4f7262a8c 100644
--- a/apps/dokploy/server/utils/builders/index.ts
+++ b/apps/dokploy/server/utils/builders/index.ts
@@ -1,5 +1,4 @@
import { createWriteStream } from "node:fs";
-import { docker } from "@/server/constants";
import type { InferResultType } from "@/server/types/with";
import type { CreateServiceOptions } from "dockerode";
import { uploadImage } from "../cluster/upload";
@@ -11,11 +10,12 @@ import {
generateVolumeMounts,
prepareEnvironmentVariables,
} from "../docker/utils";
-import { buildCustomDocker } from "./docker-file";
-import { buildHeroku } from "./heroku";
-import { buildNixpacks } from "./nixpacks";
-import { buildPaketo } from "./paketo";
-import { buildStatic } from "./static";
+import { getRemoteDocker } from "../servers/remote-docker";
+import { buildCustomDocker, getDockerCommand } from "./docker-file";
+import { buildHeroku, getHerokuCommand } from "./heroku";
+import { buildNixpacks, getNixpacksCommand } from "./nixpacks";
+import { buildPaketo, getPaketoCommand } from "./paketo";
+import { buildStatic, getStaticCommand } from "./static";
// NIXPACKS codeDirectory = where is the path of the code directory
// HEROKU codeDirectory = where is the path of the code directory
@@ -65,6 +65,25 @@ export const buildApplication = async (
}
};
+export const getBuildCommand = (
+ application: ApplicationNested,
+ logPath: string,
+) => {
+ const { buildType } = application;
+ switch (buildType) {
+ case "nixpacks":
+ return getNixpacksCommand(application, logPath);
+ case "heroku_buildpacks":
+ return getHerokuCommand(application, logPath);
+ case "paketo_buildpacks":
+ return getPaketoCommand(application, logPath);
+ case "static":
+ return getStaticCommand(application, logPath);
+ case "dockerfile":
+ return getDockerCommand(application, logPath);
+ }
+};
+
export const mechanizeDockerContainer = async (
application: ApplicationNested,
) => {
@@ -101,11 +120,12 @@ export const mechanizeDockerContainer = async (
} = generateConfigContainer(application);
const bindsMount = generateBindMounts(mounts);
- const filesMount = generateFileMounts(appName, mounts);
+ const filesMount = generateFileMounts(appName, application);
const envVariables = prepareEnvironmentVariables(env);
const image = getImageName(application);
const authConfig = getAuthConfig(application);
+ const docker = await getRemoteDocker(application.serverId);
const settings: CreateServiceOptions = {
authconfig: authConfig,
diff --git a/apps/dokploy/server/utils/builders/nixpacks.ts b/apps/dokploy/server/utils/builders/nixpacks.ts
index 6769901f8..2d81a7c01 100644
--- a/apps/dokploy/server/utils/builders/nixpacks.ts
+++ b/apps/dokploy/server/utils/builders/nixpacks.ts
@@ -1,6 +1,6 @@
-import type { WriteStream } from "node:fs";
+import { type WriteStream, existsSync, mkdirSync } from "node:fs";
import path from "node:path";
-import { buildStatic } from "@/server/utils/builders/static";
+import { buildStatic, getStaticCommand } from "@/server/utils/builders/static";
import { nanoid } from "nanoid";
import type { ApplicationNested } from ".";
import { prepareEnvironmentVariables } from "../docker/utils";
@@ -11,7 +11,7 @@ export const buildNixpacks = async (
application: ApplicationNested,
writeStream: WriteStream,
) => {
- const { env, appName, publishDirectory } = application;
+ const { env, appName, publishDirectory, serverId } = application;
const buildAppDirectory = getBuildAppDirectory(application);
const buildContainerId = `${appName}-${nanoid(10)}`;
@@ -42,7 +42,6 @@ export const buildNixpacks = async (
and copy the artifacts on the host filesystem.
Then, remove the container and create a static build.
*/
-
if (publishDirectory) {
await spawnAsync(
"docker",
@@ -50,12 +49,22 @@ export const buildNixpacks = async (
writeToStream,
);
+ const localPath = path.join(buildAppDirectory, publishDirectory);
+
+ if (!existsSync(path.dirname(localPath))) {
+ mkdirSync(path.dirname(localPath), { recursive: true });
+ }
+
+ // https://docs.docker.com/reference/cli/docker/container/cp/
+ const isDirectory =
+ publishDirectory.endsWith("/") || !path.extname(publishDirectory);
+
await spawnAsync(
"docker",
[
"cp",
- `${buildContainerId}:/app/${publishDirectory}`,
- path.join(buildAppDirectory, publishDirectory),
+ `${buildContainerId}:/app/${publishDirectory}${isDirectory ? "/." : ""}`,
+ localPath,
],
writeToStream,
);
@@ -71,3 +80,59 @@ export const buildNixpacks = async (
throw e;
}
};
+
+export const getNixpacksCommand = (
+ application: ApplicationNested,
+ logPath: string,
+) => {
+ const { env, appName, publishDirectory, serverId } = application;
+
+ const buildAppDirectory = getBuildAppDirectory(application);
+ const buildContainerId = `${appName}-${nanoid(10)}`;
+ const envVariables = prepareEnvironmentVariables(env);
+
+ const args = ["build", buildAppDirectory, "--name", appName];
+
+ for (const env of envVariables) {
+ args.push("--env", env);
+ }
+
+ if (publishDirectory) {
+ /* No need for any start command, since we'll use nginx later on */
+ args.push("--no-error-without-start");
+ }
+ const command = `nixpacks ${args.join(" ")}`;
+ let bashCommand = `
+echo "Starting nixpacks build..." >> ${logPath};
+${command} >> ${logPath} 2>> ${logPath} || {
+ echo "❌ Nixpacks build failed" >> ${logPath};
+ exit 1;
+}
+echo "✅ Nixpacks build completed." >> ${logPath};
+ `;
+
+ /*
+ Run the container with the image created by nixpacks,
+ and copy the artifacts on the host filesystem.
+ Then, remove the container and create a static build.
+ */
+ if (publishDirectory) {
+ const localPath = path.join(buildAppDirectory, publishDirectory);
+ const isDirectory =
+ publishDirectory.endsWith("/") || !path.extname(publishDirectory);
+
+ bashCommand += `
+docker create --name ${buildContainerId} ${appName}
+mkdir -p ${localPath}
+docker cp ${buildContainerId}:/app/${publishDirectory}${isDirectory ? "/." : ""} ${path.join(buildAppDirectory, publishDirectory)} >> ${logPath} 2>> ${logPath} || {
+ docker rm ${buildContainerId}
+ echo "❌ Copying ${publishDirectory} to ${path.join(buildAppDirectory, publishDirectory)} failed" >> ${logPath};
+ exit 1;
+}
+docker rm ${buildContainerId}
+${getStaticCommand(application, logPath)}
+ `;
+ }
+
+ return bashCommand;
+};
diff --git a/apps/dokploy/server/utils/builders/paketo.ts b/apps/dokploy/server/utils/builders/paketo.ts
index 14a781c6d..1faf42aea 100644
--- a/apps/dokploy/server/utils/builders/paketo.ts
+++ b/apps/dokploy/server/utils/builders/paketo.ts
@@ -4,7 +4,6 @@ import { prepareEnvironmentVariables } from "../docker/utils";
import { getBuildAppDirectory } from "../filesystem/directory";
import { spawnAsync } from "../process/spawnAsync";
-// TODO: integrate in the vps sudo chown -R $(whoami) ~/.docker
export const buildPaketo = async (
application: ApplicationNested,
writeStream: WriteStream,
@@ -36,3 +35,38 @@ export const buildPaketo = async (
throw e;
}
};
+
+export const getPaketoCommand = (
+ application: ApplicationNested,
+ logPath: string,
+) => {
+ const { env, appName } = application;
+
+ const buildAppDirectory = getBuildAppDirectory(application);
+ const envVariables = prepareEnvironmentVariables(env);
+
+ const args = [
+ "build",
+ appName,
+ "--path",
+ buildAppDirectory,
+ "--builder",
+ "paketobuildpacks/builder-jammy-full",
+ ];
+
+ for (const env of envVariables) {
+ args.push("--env", env);
+ }
+
+ const command = `pack ${args.join(" ")}`;
+ const bashCommand = `
+echo "Starting Paketo build..." >> ${logPath};
+${command} >> ${logPath} 2>> ${logPath} || {
+ echo "❌ Paketo build failed" >> ${logPath};
+ exit 1;
+}
+echo "✅ Paketo build completed." >> ${logPath};
+ `;
+
+ return bashCommand;
+};
diff --git a/apps/dokploy/server/utils/builders/static.ts b/apps/dokploy/server/utils/builders/static.ts
index 37f1beefe..2f3d4bc2f 100644
--- a/apps/dokploy/server/utils/builders/static.ts
+++ b/apps/dokploy/server/utils/builders/static.ts
@@ -1,7 +1,10 @@
import type { WriteStream } from "node:fs";
-import { buildCustomDocker } from "@/server/utils/builders/docker-file";
+import {
+ buildCustomDocker,
+ getDockerCommand,
+} from "@/server/utils/builders/docker-file";
import type { ApplicationNested } from ".";
-import { createFile } from "../docker/utils";
+import { createFile, getCreateFileCommand } from "../docker/utils";
import { getBuildAppDirectory } from "../filesystem/directory";
export const buildStatic = async (
@@ -36,3 +39,31 @@ export const buildStatic = async (
throw e;
}
};
+
+export const getStaticCommand = (
+ application: ApplicationNested,
+ logPath: string,
+) => {
+ const { publishDirectory } = application;
+ const buildAppDirectory = getBuildAppDirectory(application);
+
+ let command = getCreateFileCommand(
+ buildAppDirectory,
+ "Dockerfile",
+ [
+ "FROM nginx:alpine",
+ "WORKDIR /usr/share/nginx/html/",
+ `COPY ${publishDirectory || "."} .`,
+ ].join("\n"),
+ );
+
+ command += getDockerCommand(
+ {
+ ...application,
+ buildType: "dockerfile",
+ dockerfile: "Dockerfile",
+ },
+ logPath,
+ );
+ return command;
+};
diff --git a/apps/dokploy/server/utils/builders/utils.ts b/apps/dokploy/server/utils/builders/utils.ts
index a5ce53627..3eeb45225 100644
--- a/apps/dokploy/server/utils/builders/utils.ts
+++ b/apps/dokploy/server/utils/builders/utils.ts
@@ -10,3 +10,12 @@ export const createEnvFile = (directory: string, env: string | null) => {
const envFileContent = prepareEnvironmentVariables(env).join("\n");
writeFileSync(envFilePath, envFileContent);
};
+
+export const createEnvFileCommand = (directory: string, env: string | null) => {
+ const envFilePath = join(dirname(directory), ".env");
+ if (!existsSync(dirname(envFilePath))) {
+ mkdirSync(dirname(envFilePath), { recursive: true });
+ }
+ const envFileContent = prepareEnvironmentVariables(env).join("\n");
+ return `echo "${envFileContent}" > ${envFilePath}`;
+};
diff --git a/apps/dokploy/server/utils/databases/mariadb.ts b/apps/dokploy/server/utils/databases/mariadb.ts
index 8ead88418..9465d47e2 100644
--- a/apps/dokploy/server/utils/databases/mariadb.ts
+++ b/apps/dokploy/server/utils/databases/mariadb.ts
@@ -1,6 +1,4 @@
-import type { Mariadb } from "@/server/api/services/mariadb";
-import type { Mount } from "@/server/api/services/mount";
-import { docker } from "@/server/constants";
+import type { InferResultType } from "@/server/types/with";
import type { CreateServiceOptions } from "dockerode";
import {
calculateResources,
@@ -9,11 +7,10 @@ import {
generateVolumeMounts,
prepareEnvironmentVariables,
} from "../docker/utils";
+import { getRemoteDocker } from "../servers/remote-docker";
-type MariadbWithMounts = Mariadb & {
- mounts: Mount[];
-};
-export const buildMariadb = async (mariadb: MariadbWithMounts) => {
+export type MariadbNested = InferResultType<"mariadb", { mounts: true }>;
+export const buildMariadb = async (mariadb: MariadbNested) => {
const {
appName,
env,
@@ -43,7 +40,9 @@ export const buildMariadb = async (mariadb: MariadbWithMounts) => {
const envVariables = prepareEnvironmentVariables(defaultMariadbEnv);
const volumesMount = generateVolumeMounts(mounts);
const bindsMount = generateBindMounts(mounts);
- const filesMount = generateFileMounts(appName, mounts);
+ const filesMount = generateFileMounts(appName, mariadb);
+
+ const docker = await getRemoteDocker(mariadb.serverId);
const settings: CreateServiceOptions = {
Name: appName,
diff --git a/apps/dokploy/server/utils/databases/mongo.ts b/apps/dokploy/server/utils/databases/mongo.ts
index 85d44f2c7..6b02da867 100644
--- a/apps/dokploy/server/utils/databases/mongo.ts
+++ b/apps/dokploy/server/utils/databases/mongo.ts
@@ -1,7 +1,4 @@
-import type { Mongo } from "@/server/api/services/mongo";
-import type { Mount } from "@/server/api/services/mount";
-import type { Postgres } from "@/server/api/services/postgres";
-import { docker } from "@/server/constants";
+import type { InferResultType } from "@/server/types/with";
import type { CreateServiceOptions } from "dockerode";
import {
calculateResources,
@@ -10,12 +7,11 @@ import {
generateVolumeMounts,
prepareEnvironmentVariables,
} from "../docker/utils";
+import { getRemoteDocker } from "../servers/remote-docker";
-type MongoWithMounts = Mongo & {
- mounts: Mount[];
-};
+export type MongoNested = InferResultType<"mongo", { mounts: true }>;
-export const buildMongo = async (mongo: MongoWithMounts) => {
+export const buildMongo = async (mongo: MongoNested) => {
const {
appName,
env,
@@ -43,7 +39,9 @@ export const buildMongo = async (mongo: MongoWithMounts) => {
const envVariables = prepareEnvironmentVariables(defaultMongoEnv);
const volumesMount = generateVolumeMounts(mounts);
const bindsMount = generateBindMounts(mounts);
- const filesMount = generateFileMounts(appName, mounts);
+ const filesMount = generateFileMounts(appName, mongo);
+
+ const docker = await getRemoteDocker(mongo.serverId);
const settings: CreateServiceOptions = {
Name: appName,
diff --git a/apps/dokploy/server/utils/databases/mysql.ts b/apps/dokploy/server/utils/databases/mysql.ts
index 925492990..3ad266b77 100644
--- a/apps/dokploy/server/utils/databases/mysql.ts
+++ b/apps/dokploy/server/utils/databases/mysql.ts
@@ -1,6 +1,4 @@
-import type { Mount } from "@/server/api/services/mount";
-import type { MySql } from "@/server/api/services/mysql";
-import { docker } from "@/server/constants";
+import type { InferResultType } from "@/server/types/with";
import type { CreateServiceOptions } from "dockerode";
import {
calculateResources,
@@ -9,12 +7,11 @@ import {
generateVolumeMounts,
prepareEnvironmentVariables,
} from "../docker/utils";
+import { getRemoteDocker } from "../servers/remote-docker";
-type MysqlWithMounts = MySql & {
- mounts: Mount[];
-};
+export type MysqlNested = InferResultType<"mysql", { mounts: true }>;
-export const buildMysql = async (mysql: MysqlWithMounts) => {
+export const buildMysql = async (mysql: MysqlNested) => {
const {
appName,
env,
@@ -49,7 +46,9 @@ export const buildMysql = async (mysql: MysqlWithMounts) => {
const envVariables = prepareEnvironmentVariables(defaultMysqlEnv);
const volumesMount = generateVolumeMounts(mounts);
const bindsMount = generateBindMounts(mounts);
- const filesMount = generateFileMounts(appName, mounts);
+ const filesMount = generateFileMounts(appName, mysql);
+
+ const docker = await getRemoteDocker(mysql.serverId);
const settings: CreateServiceOptions = {
Name: appName,
diff --git a/apps/dokploy/server/utils/databases/postgres.ts b/apps/dokploy/server/utils/databases/postgres.ts
index aa4a41510..f7984fbe9 100644
--- a/apps/dokploy/server/utils/databases/postgres.ts
+++ b/apps/dokploy/server/utils/databases/postgres.ts
@@ -1,6 +1,4 @@
-import type { Mount } from "@/server/api/services/mount";
-import type { Postgres } from "@/server/api/services/postgres";
-import { docker } from "@/server/constants";
+import type { InferResultType } from "@/server/types/with";
import type { CreateServiceOptions } from "dockerode";
import {
calculateResources,
@@ -9,12 +7,10 @@ import {
generateVolumeMounts,
prepareEnvironmentVariables,
} from "../docker/utils";
+import { getRemoteDocker } from "../servers/remote-docker";
-type PostgresWithMounts = Postgres & {
- mounts: Mount[];
-};
-
-export const buildPostgres = async (postgres: PostgresWithMounts) => {
+export type PostgresNested = InferResultType<"postgres", { mounts: true }>;
+export const buildPostgres = async (postgres: PostgresNested) => {
const {
appName,
env,
@@ -43,7 +39,9 @@ export const buildPostgres = async (postgres: PostgresWithMounts) => {
const envVariables = prepareEnvironmentVariables(defaultPostgresEnv);
const volumesMount = generateVolumeMounts(mounts);
const bindsMount = generateBindMounts(mounts);
- const filesMount = generateFileMounts(appName, mounts);
+ const filesMount = generateFileMounts(appName, postgres);
+
+ const docker = await getRemoteDocker(postgres.serverId);
const settings: CreateServiceOptions = {
Name: appName,
diff --git a/apps/dokploy/server/utils/databases/redis.ts b/apps/dokploy/server/utils/databases/redis.ts
index 6fc84cc2d..62d972a74 100644
--- a/apps/dokploy/server/utils/databases/redis.ts
+++ b/apps/dokploy/server/utils/databases/redis.ts
@@ -1,6 +1,4 @@
-import type { Mount } from "@/server/api/services/mount";
-import type { Redis } from "@/server/api/services/redis";
-import { docker } from "@/server/constants";
+import type { InferResultType } from "@/server/types/with";
import type { CreateServiceOptions } from "dockerode";
import {
calculateResources,
@@ -9,12 +7,10 @@ import {
generateVolumeMounts,
prepareEnvironmentVariables,
} from "../docker/utils";
+import { getRemoteDocker } from "../servers/remote-docker";
-type RedisWithMounts = Redis & {
- mounts: Mount[];
-};
-
-export const buildRedis = async (redis: RedisWithMounts) => {
+export type RedisNested = InferResultType<"redis", { mounts: true }>;
+export const buildRedis = async (redis: RedisNested) => {
const {
appName,
env,
@@ -41,7 +37,9 @@ export const buildRedis = async (redis: RedisWithMounts) => {
const envVariables = prepareEnvironmentVariables(defaultRedisEnv);
const volumesMount = generateVolumeMounts(mounts);
const bindsMount = generateBindMounts(mounts);
- const filesMount = generateFileMounts(appName, mounts);
+ const filesMount = generateFileMounts(appName, redis);
+
+ const docker = await getRemoteDocker(redis.serverId);
const settings: CreateServiceOptions = {
Name: appName,
diff --git a/apps/dokploy/server/utils/docker/domain.ts b/apps/dokploy/server/utils/docker/domain.ts
index 4d9f98741..5b0ac0768 100644
--- a/apps/dokploy/server/utils/docker/domain.ts
+++ b/apps/dokploy/server/utils/docker/domain.ts
@@ -3,19 +3,36 @@ import { writeFile } from "node:fs/promises";
import { join } from "node:path";
import type { Compose } from "@/server/api/services/compose";
import type { Domain } from "@/server/api/services/domain";
-import { COMPOSE_PATH } from "@/server/constants";
+import { paths } from "@/server/constants";
import { dump, load } from "js-yaml";
-import { cloneRawBitbucketRepository } from "../providers/bitbucket";
-import { cloneGitRawRepository } from "../providers/git";
-import { cloneRawGithubRepository } from "../providers/github";
-import { cloneRawGitlabRepository } from "../providers/gitlab";
-import { createComposeFileRaw } from "../providers/raw";
+import { execAsyncRemote } from "../process/execAsync";
+import {
+ cloneRawBitbucketRepository,
+ cloneRawBitbucketRepositoryRemote,
+} from "../providers/bitbucket";
+import {
+ cloneGitRawRepository,
+ cloneRawGitRepositoryRemote,
+} from "../providers/git";
+import {
+ cloneRawGithubRepository,
+ cloneRawGithubRepositoryRemote,
+} from "../providers/github";
+import {
+ cloneRawGitlabRepository,
+ cloneRawGitlabRepositoryRemote,
+} from "../providers/gitlab";
+import {
+ createComposeFileRaw,
+ createComposeFileRawRemote,
+} from "../providers/raw";
import { randomizeSpecificationFile } from "./compose";
import type {
ComposeSpecification,
DefinitionsService,
PropertiesNetworks,
} from "./types";
+import { encodeBase64 } from "./utils";
export const cloneCompose = async (compose: Compose) => {
if (compose.sourceType === "github") {
@@ -31,7 +48,22 @@ export const cloneCompose = async (compose: Compose) => {
}
};
+export const cloneComposeRemote = async (compose: Compose) => {
+ if (compose.sourceType === "github") {
+ await cloneRawGithubRepositoryRemote(compose);
+ } else if (compose.sourceType === "gitlab") {
+ await cloneRawGitlabRepositoryRemote(compose);
+ } else if (compose.sourceType === "bitbucket") {
+ await cloneRawBitbucketRepositoryRemote(compose);
+ } else if (compose.sourceType === "git") {
+ await cloneRawGitRepositoryRemote(compose);
+ } else if (compose.sourceType === "raw") {
+ await createComposeFileRawRemote(compose);
+ }
+};
+
export const getComposePath = (compose: Compose) => {
+ const { COMPOSE_PATH } = paths(!!compose.serverId);
const { appName, sourceType, composePath } = compose;
let path = "";
@@ -57,6 +89,30 @@ export const loadDockerCompose = async (
return null;
};
+export const loadDockerComposeRemote = async (
+ compose: Compose,
+): Promise => {
+ const path = getComposePath(compose);
+ try {
+ if (!compose.serverId) {
+ return null;
+ }
+ const { stdout, stderr } = await execAsyncRemote(
+ compose.serverId,
+ `cat ${path}`,
+ );
+
+ if (stderr) {
+ return null;
+ }
+ if (!stdout) return null;
+ const parsedConfig = load(stdout) as ComposeSpecification;
+ return parsedConfig;
+ } catch (err) {
+ return null;
+ }
+};
+
export const readComposeFile = async (compose: Compose) => {
const path = getComposePath(compose);
if (existsSync(path)) {
@@ -84,12 +140,51 @@ export const writeDomainsToCompose = async (
}
};
+export const writeDomainsToComposeRemote = async (
+ compose: Compose,
+ domains: Domain[],
+ logPath: string,
+) => {
+ if (!domains.length) {
+ return "";
+ }
+
+ try {
+ const composeConverted = await addDomainToCompose(compose, domains);
+ const path = getComposePath(compose);
+
+ if (!composeConverted) {
+ return `
+echo "❌ Error: Compose file not found" >> ${logPath};
+exit 1;
+ `;
+ }
+ if (compose.serverId) {
+ const composeString = dump(composeConverted, { lineWidth: 1000 });
+ const encodedContent = encodeBase64(composeString);
+ return `echo "${encodedContent}" | base64 -d > "${path}";`;
+ }
+ } catch (error) {
+ // @ts-ignore
+ return `echo "❌ Has occured an error: ${error?.message || error}" >> ${logPath};
+exit 1;
+ `;
+ }
+};
+// (node:59875) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 SIGTERM listeners added to [process]. Use emitter.setMaxListeners() to increase limit
export const addDomainToCompose = async (
compose: Compose,
domains: Domain[],
) => {
const { appName } = compose;
- let result = await loadDockerCompose(compose);
+
+ let result: ComposeSpecification | null;
+
+ if (compose.serverId) {
+ result = await loadDockerComposeRemote(compose); // aca hay que ir al servidor e ir a traer el compose file al servidor
+ } else {
+ result = await loadDockerCompose(compose);
+ }
if (!result || domains.length === 0) {
return null;
diff --git a/apps/dokploy/server/utils/docker/utils.ts b/apps/dokploy/server/utils/docker/utils.ts
index 68411b4c3..3dcc27ac0 100644
--- a/apps/dokploy/server/utils/docker/utils.ts
+++ b/apps/dokploy/server/utils/docker/utils.ts
@@ -1,11 +1,17 @@
import fs from "node:fs";
import path from "node:path";
import type { Readable } from "node:stream";
-import { APPLICATIONS_PATH, docker } from "@/server/constants";
+import { docker, paths } from "@/server/constants";
import type { ContainerInfo, ResourceRequirements } from "dockerode";
import { parse } from "dotenv";
import type { ApplicationNested } from "../builders";
-import { execAsync } from "../process/execAsync";
+import type { MariadbNested } from "../databases/mariadb";
+import type { MongoNested } from "../databases/mongo";
+import type { MysqlNested } from "../databases/mysql";
+import type { PostgresNested } from "../databases/postgres";
+import type { RedisNested } from "../databases/redis";
+import { execAsync, execAsyncRemote } from "../process/execAsync";
+import { getRemoteDocker } from "../servers/remote-docker";
interface RegistryAuth {
username: string;
@@ -51,6 +57,51 @@ export const pullImage = async (
}
};
+export const pullRemoteImage = async (
+ dockerImage: string,
+ serverId: string,
+ onData?: (data: any) => void,
+ authConfig?: Partial,
+): Promise => {
+ try {
+ if (!dockerImage) {
+ throw new Error("Docker image not found");
+ }
+
+ const remoteDocker = await getRemoteDocker(serverId);
+
+ await new Promise((resolve, reject) => {
+ remoteDocker.pull(
+ dockerImage,
+ { authconfig: authConfig },
+ (err, stream) => {
+ if (err) {
+ reject(err);
+ return;
+ }
+
+ remoteDocker.modem.followProgress(
+ stream as Readable,
+ (err: Error | null, res) => {
+ if (!err) {
+ resolve(res);
+ }
+ if (err) {
+ reject(err);
+ }
+ },
+ (event) => {
+ onData?.(event);
+ },
+ );
+ },
+ );
+ });
+ } catch (error) {
+ throw error;
+ }
+};
+
export const containerExists = async (containerName: string) => {
const container = docker.getContainer(containerName);
try {
@@ -70,6 +121,15 @@ export const stopService = async (appName: string) => {
}
};
+export const stopServiceRemote = async (serverId: string, appName: string) => {
+ try {
+ await execAsyncRemote(serverId, `docker service scale ${appName}=0 `);
+ } catch (error) {
+ console.error(error);
+ return error;
+ }
+};
+
export const getContainerByName = (name: string): Promise => {
const opts = {
limit: 1,
@@ -89,27 +149,39 @@ export const getContainerByName = (name: string): Promise => {
});
});
};
-export const cleanUpUnusedImages = async () => {
+export const cleanUpUnusedImages = async (serverId?: string) => {
try {
- await execAsync("docker image prune --all --force");
+ if (serverId) {
+ await execAsyncRemote(serverId, "docker image prune --all --force");
+ } else {
+ await execAsync("docker image prune --all --force");
+ }
} catch (error) {
console.error(error);
throw error;
}
};
-export const cleanStoppedContainers = async () => {
+export const cleanStoppedContainers = async (serverId?: string) => {
try {
- await execAsync("docker container prune --force");
+ if (serverId) {
+ await execAsyncRemote(serverId, "docker container prune --force");
+ } else {
+ await execAsync("docker container prune --force");
+ }
} catch (error) {
console.error(error);
throw error;
}
};
-export const cleanUpUnusedVolumes = async () => {
+export const cleanUpUnusedVolumes = async (serverId?: string) => {
try {
- await execAsync("docker volume prune --all --force");
+ if (serverId) {
+ await execAsyncRemote(serverId, "docker volume prune --all --force");
+ } else {
+ await execAsync("docker volume prune --all --force");
+ }
} catch (error) {
console.error(error);
throw error;
@@ -133,12 +205,23 @@ export const cleanUpInactiveContainers = async () => {
}
};
-export const cleanUpDockerBuilder = async () => {
- await execAsync("docker builder prune --all --force");
+export const cleanUpDockerBuilder = async (serverId?: string) => {
+ if (serverId) {
+ await execAsyncRemote(serverId, "docker builder prune --all --force");
+ } else {
+ await execAsync("docker builder prune --all --force");
+ }
};
-export const cleanUpSystemPrune = async () => {
- await execAsync("docker system prune --all --force --volumes");
+export const cleanUpSystemPrune = async (serverId?: string) => {
+ if (serverId) {
+ await execAsyncRemote(
+ serverId,
+ "docker system prune --all --force --volumes",
+ );
+ } else {
+ await execAsync("docker system prune --all --force --volumes");
+ }
};
export const startService = async (appName: string) => {
@@ -150,9 +233,26 @@ export const startService = async (appName: string) => {
}
};
-export const removeService = async (appName: string) => {
+export const startServiceRemote = async (serverId: string, appName: string) => {
try {
- await execAsync(`docker service rm ${appName}`);
+ await execAsyncRemote(serverId, `docker service scale ${appName}=1 `);
+ } catch (error) {
+ console.error(error);
+ throw error;
+ }
+};
+
+export const removeService = async (
+ appName: string,
+ serverId?: string | null,
+) => {
+ try {
+ const command = `docker service rm ${appName}`;
+ if (serverId) {
+ await execAsyncRemote(serverId, command);
+ } else {
+ await execAsync(command);
+ }
} catch (error) {
return error;
}
@@ -306,8 +406,16 @@ export const generateBindMounts = (mounts: ApplicationNested["mounts"]) => {
export const generateFileMounts = (
appName: string,
- mounts: ApplicationNested["mounts"],
+ service:
+ | ApplicationNested
+ | MongoNested
+ | MariadbNested
+ | MysqlNested
+ | PostgresNested
+ | RedisNested,
) => {
+ const { mounts } = service;
+ const { APPLICATIONS_PATH } = paths(!!service.serverId);
if (!mounts || mounts.length === 0) {
return [];
}
@@ -346,6 +454,26 @@ export const createFile = async (
throw error;
}
};
+export const encodeBase64 = (content: string) =>
+ Buffer.from(content, "utf-8").toString("base64");
+
+export const getCreateFileCommand = (
+ outputPath: string,
+ filePath: string,
+ content: string,
+) => {
+ const fullPath = path.join(outputPath, filePath);
+ if (fullPath.endsWith(path.sep) || filePath.endsWith("/")) {
+ return `mkdir -p ${fullPath};`;
+ }
+
+ const directory = path.dirname(fullPath);
+ const encodedContent = encodeBase64(content);
+ return `
+ mkdir -p ${directory};
+ echo "${encodedContent}" | base64 -d > "${fullPath}";
+ `;
+};
export const getServiceContainer = async (appName: string) => {
try {
@@ -369,3 +497,29 @@ export const getServiceContainer = async (appName: string) => {
throw error;
}
};
+
+export const getRemoteServiceContainer = async (
+ serverId: string,
+ appName: string,
+) => {
+ try {
+ const filter = {
+ status: ["running"],
+ label: [`com.docker.swarm.service.name=${appName}`],
+ };
+ const remoteDocker = await getRemoteDocker(serverId);
+ const containers = await remoteDocker.listContainers({
+ filters: JSON.stringify(filter),
+ });
+
+ if (containers.length === 0 || !containers[0]) {
+ throw new Error(`No container found with name: ${appName}`);
+ }
+
+ const container = containers[0];
+
+ return container;
+ } catch (error) {
+ throw error;
+ }
+};
diff --git a/apps/dokploy/server/utils/filesystem/directory.ts b/apps/dokploy/server/utils/filesystem/directory.ts
index 9fdcfbfe4..bc3b03ebf 100644
--- a/apps/dokploy/server/utils/filesystem/directory.ts
+++ b/apps/dokploy/server/utils/filesystem/directory.ts
@@ -1,12 +1,8 @@
import fs, { promises as fsPromises } from "node:fs";
import path from "node:path";
import type { Application } from "@/server/api/services/application";
-import {
- APPLICATIONS_PATH,
- COMPOSE_PATH,
- MONITORING_PATH,
-} from "@/server/constants";
-import { execAsync } from "../process/execAsync";
+import { paths } from "@/server/constants";
+import { execAsync, execAsyncRemote } from "../process/execAsync";
export const recreateDirectory = async (pathFolder: string): Promise => {
try {
@@ -17,6 +13,20 @@ export const recreateDirectory = async (pathFolder: string): Promise => {
}
};
+export const recreateDirectoryRemote = async (
+ pathFolder: string,
+ serverId: string | null,
+): Promise => {
+ try {
+ await execAsyncRemote(
+ serverId,
+ `rm -rf ${pathFolder}; mkdir -p ${pathFolder}`,
+ );
+ } catch (error) {
+ console.error(`Error recreating directory '${pathFolder}':`, error);
+ }
+};
+
export const removeDirectoryIfExistsContent = async (
path: string,
): Promise => {
@@ -34,31 +44,57 @@ export const removeFileOrDirectory = async (path: string) => {
}
};
-export const removeDirectoryCode = async (appName: string) => {
+export const removeDirectoryCode = async (
+ appName: string,
+ serverId?: string | null,
+) => {
+ const { APPLICATIONS_PATH } = paths(!!serverId);
const directoryPath = path.join(APPLICATIONS_PATH, appName);
-
+ const command = `rm -rf ${directoryPath}`;
try {
- await execAsync(`rm -rf ${directoryPath}`);
+ if (serverId) {
+ await execAsyncRemote(serverId, command);
+ } else {
+ await execAsync(command);
+ }
} catch (error) {
console.error(`Error to remove ${directoryPath}: ${error}`);
throw error;
}
};
-export const removeComposeDirectory = async (appName: string) => {
+export const removeComposeDirectory = async (
+ appName: string,
+ serverId?: string | null,
+) => {
+ const { COMPOSE_PATH } = paths(!!serverId);
const directoryPath = path.join(COMPOSE_PATH, appName);
+ const command = `rm -rf ${directoryPath}`;
try {
- await execAsync(`rm -rf ${directoryPath}`);
+ if (serverId) {
+ await execAsyncRemote(serverId, command);
+ } else {
+ await execAsync(command);
+ }
} catch (error) {
console.error(`Error to remove ${directoryPath}: ${error}`);
throw error;
}
};
-export const removeMonitoringDirectory = async (appName: string) => {
+export const removeMonitoringDirectory = async (
+ appName: string,
+ serverId?: string | null,
+) => {
+ const { MONITORING_PATH } = paths(!!serverId);
const directoryPath = path.join(MONITORING_PATH, appName);
+ const command = `rm -rf ${directoryPath}`;
try {
- await execAsync(`rm -rf ${directoryPath}`);
+ if (serverId) {
+ await execAsyncRemote(serverId, command);
+ } else {
+ await execAsync(command);
+ }
} catch (error) {
console.error(`Error to remove ${directoryPath}: ${error}`);
throw error;
@@ -66,6 +102,7 @@ export const removeMonitoringDirectory = async (appName: string) => {
};
export const getBuildAppDirectory = (application: Application) => {
+ const { APPLICATIONS_PATH } = paths(!!application.serverId);
const { appName, buildType, sourceType, customGitBuildPath, dockerfile } =
application;
let buildPath = "";
@@ -95,6 +132,7 @@ export const getBuildAppDirectory = (application: Application) => {
};
export const getDockerContextPath = (application: Application) => {
+ const { APPLICATIONS_PATH } = paths(!!application.serverId);
const { appName, dockerContextPath } = application;
if (!dockerContextPath) {
diff --git a/apps/dokploy/server/utils/filesystem/ssh.ts b/apps/dokploy/server/utils/filesystem/ssh.ts
index ed37badb3..62bf076af 100644
--- a/apps/dokploy/server/utils/filesystem/ssh.ts
+++ b/apps/dokploy/server/utils/filesystem/ssh.ts
@@ -1,9 +1,10 @@
import * as fs from "node:fs";
import * as path from "node:path";
-import { SSH_PATH } from "@/server/constants";
+import { paths } from "@/server/constants";
import { spawnAsync } from "../process/spawnAsync";
-const readSSHKey = async (id: string) => {
+export const readSSHKey = async (id: string) => {
+ const { SSH_PATH } = paths();
try {
if (!fs.existsSync(SSH_PATH)) {
fs.mkdirSync(SSH_PATH, { recursive: true });
@@ -27,6 +28,7 @@ export const saveSSHKey = async (
publicKey: string,
privateKey: string,
) => {
+ const { SSH_PATH } = paths();
const applicationDirectory = SSH_PATH;
const privateKeyPath = path.join(applicationDirectory, `${id}_rsa`);
@@ -42,6 +44,7 @@ export const saveSSHKey = async (
};
export const generateSSHKey = async (type: "rsa" | "ed25519" = "rsa") => {
+ const { SSH_PATH } = paths();
const applicationDirectory = SSH_PATH;
if (!fs.existsSync(applicationDirectory)) {
@@ -85,6 +88,7 @@ export const generateSSHKey = async (type: "rsa" | "ed25519" = "rsa") => {
export const removeSSHKey = async (id: string) => {
try {
+ const { SSH_PATH } = paths();
const publicKeyPath = path.join(SSH_PATH, `${id}_rsa.pub`);
const privateKeyPath = path.join(SSH_PATH, `${id}_rsa`);
await fs.promises.unlink(publicKeyPath);
diff --git a/apps/dokploy/server/utils/process/execAsync.ts b/apps/dokploy/server/utils/process/execAsync.ts
index 35b903e7b..d6df6551f 100644
--- a/apps/dokploy/server/utils/process/execAsync.ts
+++ b/apps/dokploy/server/utils/process/execAsync.ts
@@ -1,3 +1,73 @@
import { exec } from "node:child_process";
import util from "node:util";
+import { findServerById } from "@/server/api/services/server";
+import { Client } from "ssh2";
+import { readSSHKey } from "../filesystem/ssh";
export const execAsync = util.promisify(exec);
+
+export const execAsyncRemote = async (
+ serverId: string | null,
+ command: string,
+): Promise<{ stdout: string; stderr: string }> => {
+ if (!serverId) return { stdout: "", stderr: "" };
+ const server = await findServerById(serverId);
+ if (!server.sshKeyId) throw new Error("No SSH key available for this server");
+
+ const keys = await readSSHKey(server.sshKeyId);
+
+ let stdout = "";
+ let stderr = "";
+ return new Promise((resolve, reject) => {
+ const conn = new Client();
+
+ sleep(1000);
+ conn
+ .once("ready", () => {
+ conn.exec(command, (err, stream) => {
+ if (err) throw err;
+ stream
+ .on("close", (code: number, signal: string) => {
+ conn.end();
+ if (code === 0) {
+ resolve({ stdout, stderr });
+ } else {
+ reject(
+ new Error(
+ `Command exited with code ${code}. Stderr: ${stderr}, command: ${command}`,
+ ),
+ );
+ }
+ })
+ .on("data", (data: string) => {
+ stdout += data.toString();
+ })
+ .stderr.on("data", (data) => {
+ stderr += data.toString();
+ });
+ });
+ })
+ .on("error", (err) => {
+ conn.end();
+ if (err.level === "client-authentication") {
+ reject(
+ new Error(
+ `Authentication failed: Invalid SSH private key. ❌ Error: ${err.message} ${err.level}`,
+ ),
+ );
+ } else {
+ reject(new Error(`SSH connection error: ${err.message}`));
+ }
+ })
+ .connect({
+ host: server.ipAddress,
+ port: server.port,
+ username: server.username,
+ privateKey: keys.privateKey,
+ timeout: 99999,
+ });
+ });
+};
+
+export const sleep = (ms: number) => {
+ return new Promise((resolve) => setTimeout(resolve, ms));
+};
diff --git a/apps/dokploy/server/utils/providers/bitbucket.ts b/apps/dokploy/server/utils/providers/bitbucket.ts
index 5d9716fb5..cf8c5fed6 100644
--- a/apps/dokploy/server/utils/providers/bitbucket.ts
+++ b/apps/dokploy/server/utils/providers/bitbucket.ts
@@ -2,7 +2,7 @@ import { createWriteStream } from "node:fs";
import { join } from "node:path";
import { findBitbucketById } from "@/server/api/services/bitbucket";
import type { Compose } from "@/server/api/services/compose";
-import { APPLICATIONS_PATH, COMPOSE_PATH } from "@/server/constants";
+import { paths } from "@/server/constants";
import type {
apiBitbucketTestConnection,
apiFindBitbucketBranches,
@@ -10,6 +10,7 @@ import type {
import type { InferResultType } from "@/server/types/with";
import { TRPCError } from "@trpc/server";
import { recreateDirectory } from "../filesystem/directory";
+import { execAsyncRemote } from "../process/execAsync";
import { spawnAsync } from "../process/spawnAsync";
export type ApplicationWithBitbucket = InferResultType<
@@ -27,6 +28,7 @@ export const cloneBitbucketRepository = async (
logPath: string,
isCompose = false,
) => {
+ const { COMPOSE_PATH, APPLICATIONS_PATH } = paths();
const writeStream = createWriteStream(logPath, { flags: "a" });
const {
appName,
@@ -79,6 +81,7 @@ export const cloneBitbucketRepository = async (
};
export const cloneRawBitbucketRepository = async (entity: Compose) => {
+ const { COMPOSE_PATH } = paths();
const {
appName,
bitbucketRepository,
@@ -117,6 +120,102 @@ export const cloneRawBitbucketRepository = async (entity: Compose) => {
}
};
+export const cloneRawBitbucketRepositoryRemote = async (compose: Compose) => {
+ const { COMPOSE_PATH } = paths(true);
+ const {
+ appName,
+ bitbucketRepository,
+ bitbucketOwner,
+ bitbucketBranch,
+ bitbucketId,
+ serverId,
+ } = compose;
+
+ if (!serverId) {
+ throw new TRPCError({
+ code: "NOT_FOUND",
+ message: "Server not found",
+ });
+ }
+ if (!bitbucketId) {
+ throw new TRPCError({
+ code: "NOT_FOUND",
+ message: "Bitbucket Provider not found",
+ });
+ }
+
+ const bitbucketProvider = await findBitbucketById(bitbucketId);
+ const basePath = COMPOSE_PATH;
+ const outputPath = join(basePath, appName, "code");
+ const repoclone = `bitbucket.org/${bitbucketOwner}/${bitbucketRepository}.git`;
+ const cloneUrl = `https://${bitbucketProvider?.bitbucketUsername}:${bitbucketProvider?.appPassword}@${repoclone}`;
+
+ try {
+ const command = `
+ rm -rf ${outputPath};
+ git clone --branch ${bitbucketBranch} --depth 1 ${cloneUrl} ${outputPath}
+ `;
+ await execAsyncRemote(serverId, command);
+ } catch (error) {
+ throw error;
+ }
+};
+
+export const getBitbucketCloneCommand = async (
+ entity: ApplicationWithBitbucket | ComposeWithBitbucket,
+ logPath: string,
+ isCompose = false,
+) => {
+ const { COMPOSE_PATH, APPLICATIONS_PATH } = paths(true);
+ const {
+ appName,
+ bitbucketRepository,
+ bitbucketOwner,
+ bitbucketBranch,
+ bitbucketId,
+ serverId,
+ bitbucket,
+ } = entity;
+
+ if (!serverId) {
+ throw new TRPCError({
+ code: "NOT_FOUND",
+ message: "Server not found",
+ });
+ }
+
+ if (!bitbucketId) {
+ const command = `
+ echo "Error: ❌ Bitbucket Provider not found" >> ${logPath};
+ exit 1;
+ `;
+ await execAsyncRemote(serverId, command);
+ throw new TRPCError({
+ code: "NOT_FOUND",
+ message: "Bitbucket Provider not found",
+ });
+ }
+
+ const bitbucketProvider = await findBitbucketById(bitbucketId);
+ const basePath = isCompose ? COMPOSE_PATH : APPLICATIONS_PATH;
+ const outputPath = join(basePath, appName, "code");
+ await recreateDirectory(outputPath);
+ const repoclone = `bitbucket.org/${bitbucketOwner}/${bitbucketRepository}.git`;
+ const cloneUrl = `https://${bitbucketProvider?.bitbucketUsername}:${bitbucketProvider?.appPassword}@${repoclone}`;
+
+ const cloneCommand = `
+rm -rf ${outputPath};
+mkdir -p ${outputPath};
+if ! git clone --branch ${bitbucketBranch} --depth 1 --progress ${cloneUrl} ${outputPath} >> ${logPath} 2>&1; then
+ echo "❌ [ERROR] Fail to clone the repository ${repoclone}" >> ${logPath};
+ exit 1;
+fi
+echo "Cloned ${repoclone} to ${outputPath}: ✅" >> ${logPath};
+ `;
+
+ return cloneCommand;
+};
+
export const getBitbucketRepositories = async (bitbucketId?: string) => {
if (!bitbucketId) {
return [];
@@ -126,7 +225,7 @@ export const getBitbucketRepositories = async (bitbucketId?: string) => {
const username =
bitbucketProvider.bitbucketWorkspaceName ||
bitbucketProvider.bitbucketUsername;
- const url = `https://api.bitbucket.org/2.0/repositories/${username}`;
+ const url = `https://api.bitbucket.org/2.0/repositories/${username}?pagelen=100`;
try {
const response = await fetch(url, {
diff --git a/apps/dokploy/server/utils/providers/docker.ts b/apps/dokploy/server/utils/providers/docker.ts
index c77a6721b..77c4db57f 100644
--- a/apps/dokploy/server/utils/providers/docker.ts
+++ b/apps/dokploy/server/utils/providers/docker.ts
@@ -47,3 +47,40 @@ export const buildDocker = async (
writeStream.end();
}
};
+
+export const buildRemoteDocker = async (
+ application: ApplicationNested,
+ logPath: string,
+) => {
+ const { sourceType, dockerImage, username, password } = application;
+
+ try {
+ if (!dockerImage) {
+ throw new Error("Docker image not found");
+ }
+ let command = `
+echo "Pulling ${dockerImage}" >> ${logPath};
+ `;
+
+ if (username && password) {
+ command += `
+if ! docker login --username ${username} --password ${password} https://index.docker.io/v1/ >> ${logPath} 2>&1; then
+ echo "❌ Login failed" >> ${logPath};
+ exit 1;
+fi
+`;
+ }
+
+ command += `
+docker pull ${dockerImage} >> ${logPath} 2>> ${logPath} || {
+ echo "❌ Pulling image failed" >> ${logPath};
+ exit 1;
+}
+
+echo "✅ Pulling image completed." >> ${logPath};
+`;
+ return command;
+ } catch (error) {
+ throw error;
+ }
+};
diff --git a/apps/dokploy/server/utils/providers/git.ts b/apps/dokploy/server/utils/providers/git.ts
index bad7e615f..d348c0e50 100644
--- a/apps/dokploy/server/utils/providers/git.ts
+++ b/apps/dokploy/server/utils/providers/git.ts
@@ -1,10 +1,11 @@
import { createWriteStream } from "node:fs";
import path, { join } from "node:path";
+import type { Compose } from "@/server/api/services/compose";
import { updateSSHKeyById } from "@/server/api/services/ssh-key";
-import { APPLICATIONS_PATH, COMPOSE_PATH, SSH_PATH } from "@/server/constants";
+import { paths } from "@/server/constants";
import { TRPCError } from "@trpc/server";
import { recreateDirectory } from "../filesystem/directory";
-import { execAsync } from "../process/execAsync";
+import { execAsync, execAsyncRemote } from "../process/execAsync";
import { spawnAsync } from "../process/spawnAsync";
export const cloneGitRepository = async (
@@ -17,6 +18,7 @@ export const cloneGitRepository = async (
logPath: string,
isCompose = false,
) => {
+ const { SSH_PATH, COMPOSE_PATH, APPLICATIONS_PATH } = paths();
const { appName, customGitUrl, customGitBranch, customGitSSHKeyId } = entity;
if (!customGitUrl || !customGitBranch) {
@@ -87,12 +89,88 @@ export const cloneGitRepository = async (
}
};
+export const getCustomGitCloneCommand = async (
+ entity: {
+ appName: string;
+ customGitUrl?: string | null;
+ customGitBranch?: string | null;
+ customGitSSHKeyId?: string | null;
+ serverId: string | null;
+ },
+ logPath: string,
+ isCompose = false,
+) => {
+ const { SSH_PATH, COMPOSE_PATH, APPLICATIONS_PATH } = paths(true);
+ const {
+ appName,
+ customGitUrl,
+ customGitBranch,
+ customGitSSHKeyId,
+ serverId,
+ } = entity;
+
+ if (!customGitUrl || !customGitBranch) {
+ const command = `
+ echo "Error: ❌ Repository not found" >> ${logPath};
+ exit 1;
+ `;
+
+ await execAsyncRemote(serverId, command);
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Error: Repository not found",
+ });
+ }
+
+ const keyPath = path.join(SSH_PATH, `${customGitSSHKeyId}_rsa`);
+ const basePath = isCompose ? COMPOSE_PATH : APPLICATIONS_PATH;
+ const outputPath = join(basePath, appName, "code");
+ const knownHostsPath = path.join(SSH_PATH, "known_hosts");
+
+ if (customGitSSHKeyId) {
+ await updateSSHKeyById({
+ sshKeyId: customGitSSHKeyId,
+ lastUsedAt: new Date().toISOString(),
+ });
+ }
+ try {
+ const command = [];
+ if (!isHttpOrHttps(customGitUrl)) {
+ command.push(addHostToKnownHostsCommand(customGitUrl));
+ }
+ command.push(`rm -rf ${outputPath};`);
+ command.push(`mkdir -p ${outputPath};`);
+ command.push(
+ `echo "Cloning Custom Git ${customGitUrl}" to ${outputPath}: ✅ >> ${logPath};`,
+ );
+ if (customGitSSHKeyId) {
+ command.push(
+ `GIT_SSH_COMMAND="ssh -i ${keyPath} -o UserKnownHostsFile=${knownHostsPath}"`,
+ );
+ }
+
+ command.push(
+ `if ! git clone --branch ${customGitBranch} --depth 1 --progress ${customGitUrl} ${outputPath} >> ${logPath} 2>&1; then
+ echo "❌ [ERROR] Fail to clone the repository ${customGitUrl}" >> ${logPath};
+ exit 1;
+ fi
+ `,
+ );
+ command.push(`echo "Cloned Custom Git ${customGitUrl}: ✅" >> ${logPath};`);
+ return command.join("\n");
+ } catch (error) {
+ console.log(error);
+ throw error;
+ }
+};
+
const isHttpOrHttps = (url: string): boolean => {
const regex = /^https?:\/\//;
return regex.test(url);
};
const addHostToKnownHosts = async (repositoryURL: string) => {
+ const { SSH_PATH } = paths();
const { domain, port } = sanitizeRepoPathSSH(repositoryURL);
const knownHostsPath = path.join(SSH_PATH, "known_hosts");
@@ -104,6 +182,14 @@ const addHostToKnownHosts = async (repositoryURL: string) => {
throw error;
}
};
+
+const addHostToKnownHostsCommand = (repositoryURL: string) => {
+ const { SSH_PATH } = paths();
+ const { domain, port } = sanitizeRepoPathSSH(repositoryURL);
+ const knownHostsPath = path.join(SSH_PATH, "known_hosts");
+
+ return `ssh-keyscan -p ${port} ${domain} >> ${knownHostsPath};`;
+};
const sanitizeRepoPathSSH = (input: string) => {
const SSH_PATH_RE = new RegExp(
[
@@ -155,6 +241,7 @@ export const cloneGitRawRepository = async (entity: {
});
}
+ const { SSH_PATH, COMPOSE_PATH } = paths();
const keyPath = path.join(SSH_PATH, `${customGitSSHKeyId}_rsa`);
const basePath = COMPOSE_PATH;
const outputPath = join(basePath, appName, "code");
@@ -197,3 +284,64 @@ export const cloneGitRawRepository = async (entity: {
throw error;
}
};
+
+export const cloneRawGitRepositoryRemote = async (compose: Compose) => {
+ const {
+ appName,
+ customGitBranch,
+ customGitUrl,
+ customGitSSHKeyId,
+ serverId,
+ } = compose;
+
+ if (!serverId) {
+ throw new TRPCError({
+ code: "NOT_FOUND",
+ message: "Server not found",
+ });
+ }
+ if (!customGitUrl) {
+ throw new TRPCError({
+ code: "NOT_FOUND",
+ message: "Git Provider not found",
+ });
+ }
+
+ const { SSH_PATH, COMPOSE_PATH } = paths(true);
+ const keyPath = path.join(SSH_PATH, `${customGitSSHKeyId}_rsa`);
+ const basePath = COMPOSE_PATH;
+ const outputPath = join(basePath, appName, "code");
+ const knownHostsPath = path.join(SSH_PATH, "known_hosts");
+
+ if (customGitSSHKeyId) {
+ await updateSSHKeyById({
+ sshKeyId: customGitSSHKeyId,
+ lastUsedAt: new Date().toISOString(),
+ });
+ }
+ try {
+ const command = [];
+ if (!isHttpOrHttps(customGitUrl)) {
+ command.push(addHostToKnownHostsCommand(customGitUrl));
+ }
+ command.push(`rm -rf ${outputPath};`);
+ command.push(`mkdir -p ${outputPath};`);
+ if (customGitSSHKeyId) {
+ command.push(
+ `GIT_SSH_COMMAND="ssh -i ${keyPath} -o UserKnownHostsFile=${knownHostsPath}"`,
+ );
+ }
+
+ command.push(
+ `if ! git clone --branch ${customGitBranch} --depth 1 --progress ${customGitUrl} ${outputPath} ; then
+ echo "[ERROR] Fail to clone the repository ";
+ exit 1;
+ fi
+ `,
+ );
+
+ await execAsyncRemote(serverId, command.join("\n"));
+ } catch (error) {
+ throw error;
+ }
+};
diff --git a/apps/dokploy/server/utils/providers/github.ts b/apps/dokploy/server/utils/providers/github.ts
index 62297761f..e828d010b 100644
--- a/apps/dokploy/server/utils/providers/github.ts
+++ b/apps/dokploy/server/utils/providers/github.ts
@@ -1,6 +1,6 @@
import { createWriteStream } from "node:fs";
import { join } from "node:path";
-import { APPLICATIONS_PATH, COMPOSE_PATH } from "@/server/constants";
+import { paths } from "@/server/constants";
import type { InferResultType } from "@/server/types/with";
import { createAppAuth } from "@octokit/auth-app";
import { TRPCError } from "@trpc/server";
@@ -11,6 +11,7 @@ import { spawnAsync } from "../process/spawnAsync";
import type { Compose } from "@/server/api/services/compose";
import { type Github, findGithubById } from "@/server/api/services/github";
import type { apiFindGithubBranches } from "@/server/db/schema";
+import { execAsyncRemote } from "../process/execAsync";
export const authGithub = (githubProvider: Github) => {
if (!haveGithubRequirements(githubProvider)) {
@@ -78,6 +79,7 @@ export const cloneGithubRepository = async (
logPath: string,
isCompose = false,
) => {
+ const { APPLICATIONS_PATH, COMPOSE_PATH } = paths();
const writeStream = createWriteStream(logPath, { flags: "a" });
const { appName, repository, owner, branch, githubId } = entity;
@@ -142,6 +144,76 @@ export const cloneGithubRepository = async (
}
};
+export const getGithubCloneCommand = async (
+ entity: ApplicationWithGithub | ComposeWithGithub,
+ logPath: string,
+ isCompose = false,
+) => {
+ const { appName, repository, owner, branch, githubId, serverId } = entity;
+
+ if (!serverId) {
+ throw new TRPCError({
+ code: "NOT_FOUND",
+ message: "Server not found",
+ });
+ }
+
+ if (!githubId) {
+ const command = `
+ echo "Error: ❌ Github Provider not found" >> ${logPath};
+ exit 1;
+ `;
+
+ await execAsyncRemote(serverId, command);
+ throw new TRPCError({
+ code: "NOT_FOUND",
+ message: "GitHub Provider not found",
+ });
+ }
+
+ const requirements = getErrorCloneRequirements(entity);
+
+ // Build log messages
+ let logMessages = "";
+ if (requirements.length > 0) {
+ logMessages += `\nGitHub Repository configuration failed for application: ${appName}\n`;
+ logMessages += "Reasons:\n";
+ logMessages += requirements.join("\n");
+ const escapedLogMessages = logMessages
+ .replace(/\\/g, "\\\\")
+ .replace(/"/g, '\\"')
+ .replace(/\n/g, "\\n");
+
+ const bashCommand = `
+ echo "${escapedLogMessages}" >> ${logPath};
+ exit 1; # Exit with error code
+ `;
+
+ await execAsyncRemote(serverId, bashCommand);
+ return;
+ }
+ const { COMPOSE_PATH, APPLICATIONS_PATH } = paths(true);
+ const githubProvider = await findGithubById(githubId);
+ const basePath = isCompose ? COMPOSE_PATH : APPLICATIONS_PATH;
+ const outputPath = join(basePath, appName, "code");
+ const octokit = authGithub(githubProvider);
+ const token = await getGithubToken(octokit);
+ const repoclone = `github.com/${owner}/${repository}.git`;
+ const cloneUrl = `https://oauth2:${token}@${repoclone}`;
+
+ const cloneCommand = `
+rm -rf ${outputPath};
+mkdir -p ${outputPath};
+if ! git clone --branch ${branch} --depth 1 --progress ${cloneUrl} ${outputPath} >> ${logPath} 2>&1; then
+ echo "❌ [ERROR] Fallo al clonar el repositorio ${repoclone}" >> ${logPath};
+ exit 1;
+fi
+echo "Cloned ${repoclone} to ${outputPath}: ✅" >> ${logPath};
+ `;
+
+ return cloneCommand;
+};
+
export const cloneRawGithubRepository = async (entity: Compose) => {
const { appName, repository, owner, branch, githubId } = entity;
@@ -151,6 +223,7 @@ export const cloneRawGithubRepository = async (entity: Compose) => {
message: "GitHub Provider not found",
});
}
+ const { COMPOSE_PATH } = paths();
const githubProvider = await findGithubById(githubId);
const basePath = COMPOSE_PATH;
const outputPath = join(basePath, appName, "code");
@@ -175,6 +248,41 @@ export const cloneRawGithubRepository = async (entity: Compose) => {
}
};
+export const cloneRawGithubRepositoryRemote = async (compose: Compose) => {
+ const { appName, repository, owner, branch, githubId, serverId } = compose;
+
+ if (!serverId) {
+ throw new TRPCError({
+ code: "NOT_FOUND",
+ message: "Server not found",
+ });
+ }
+ if (!githubId) {
+ throw new TRPCError({
+ code: "NOT_FOUND",
+ message: "GitHub Provider not found",
+ });
+ }
+
+ const { COMPOSE_PATH } = paths(true);
+ const githubProvider = await findGithubById(githubId);
+ const basePath = COMPOSE_PATH;
+ const outputPath = join(basePath, appName, "code");
+ const octokit = authGithub(githubProvider);
+ const token = await getGithubToken(octokit);
+ const repoclone = `github.com/${owner}/${repository}.git`;
+ const cloneUrl = `https://oauth2:${token}@${repoclone}`;
+ try {
+ const command = `
+ rm -rf ${outputPath};
+ git clone --branch ${branch} --depth 1 ${cloneUrl} ${outputPath}
+ `;
+ await execAsyncRemote(serverId, command);
+ } catch (error) {
+ throw error;
+ }
+};
+
export const getGithubRepositories = async (githubId?: string) => {
if (!githubId) {
return [];
diff --git a/apps/dokploy/server/utils/providers/gitlab.ts b/apps/dokploy/server/utils/providers/gitlab.ts
index 5d3f12c5a..43f868d45 100644
--- a/apps/dokploy/server/utils/providers/gitlab.ts
+++ b/apps/dokploy/server/utils/providers/gitlab.ts
@@ -6,11 +6,12 @@ import {
findGitlabById,
updateGitlab,
} from "@/server/api/services/gitlab";
-import { APPLICATIONS_PATH, COMPOSE_PATH } from "@/server/constants";
+import { paths } from "@/server/constants";
import type { apiGitlabTestConnection } from "@/server/db/schema";
import type { InferResultType } from "@/server/types/with";
import { TRPCError } from "@trpc/server";
import { recreateDirectory } from "../filesystem/directory";
+import { execAsyncRemote } from "../process/execAsync";
import { spawnAsync } from "../process/spawnAsync";
export const refreshGitlabToken = async (gitlabProviderId: string) => {
@@ -118,6 +119,8 @@ export const cloneGitlabRepository = async (
message: "Error: GitLab repository information is incomplete.",
});
}
+
+ const { COMPOSE_PATH, APPLICATIONS_PATH } = paths();
const basePath = isCompose ? COMPOSE_PATH : APPLICATIONS_PATH;
const outputPath = join(basePath, appName, "code");
await recreateDirectory(outputPath);
@@ -153,6 +156,85 @@ export const cloneGitlabRepository = async (
}
};
+export const getGitlabCloneCommand = async (
+ entity: ApplicationWithGitlab | ComposeWithGitlab,
+ logPath: string,
+ isCompose = false,
+) => {
+ const {
+ appName,
+ gitlabRepository,
+ gitlabOwner,
+ gitlabPathNamespace,
+ gitlabBranch,
+ gitlabId,
+ serverId,
+ gitlab,
+ } = entity;
+
+ if (!serverId) {
+ throw new TRPCError({
+ code: "NOT_FOUND",
+ message: "Server not found",
+ });
+ }
+
+ if (!gitlabId) {
+ const command = `
+ echo "Error: ❌ Gitlab Provider not found" >> ${logPath};
+ exit 1;
+ `;
+
+ await execAsyncRemote(serverId, command);
+ throw new TRPCError({
+ code: "NOT_FOUND",
+ message: "Gitlab Provider not found",
+ });
+ }
+
+ const requirements = getErrorCloneRequirements(entity);
+
+ // Build log messages
+ let logMessages = "";
+ if (requirements.length > 0) {
+ logMessages += `\nGitLab Repository configuration failed for application: ${appName}\n`;
+ logMessages += "Reasons:\n";
+ logMessages += requirements.join("\n");
+ const escapedLogMessages = logMessages
+ .replace(/\\/g, "\\\\")
+ .replace(/"/g, '\\"')
+ .replace(/\n/g, "\\n");
+
+ const bashCommand = `
+ echo "${escapedLogMessages}" >> ${logPath};
+ exit 1; # Exit with error code
+ `;
+
+ await execAsyncRemote(serverId, bashCommand);
+ return;
+ }
+
+ const { COMPOSE_PATH, APPLICATIONS_PATH } = paths(true);
+ await refreshGitlabToken(gitlabId);
+ const basePath = isCompose ? COMPOSE_PATH : APPLICATIONS_PATH;
+ const outputPath = join(basePath, appName, "code");
+ await recreateDirectory(outputPath);
+ const repoclone = `gitlab.com/${gitlabPathNamespace}.git`;
+ const cloneUrl = `https://oauth2:${gitlab?.accessToken}@${repoclone}`;
+
+ const cloneCommand = `
+rm -rf ${outputPath};
+mkdir -p ${outputPath};
+if ! git clone --branch ${gitlabBranch} --depth 1 --progress ${cloneUrl} ${outputPath} >> ${logPath} 2>&1; then
+ echo "❌ [ERROR] Fail to clone the repository ${repoclone}" >> ${logPath};
+ exit 1;
+fi
+echo "Cloned ${repoclone} to ${outputPath}: ✅" >> ${logPath};
+ `;
+
+ return cloneCommand;
+};
+
export const getGitlabRepositories = async (gitlabId?: string) => {
if (!gitlabId) {
return [];
@@ -264,7 +346,7 @@ export const cloneRawGitlabRepository = async (entity: Compose) => {
}
const gitlabProvider = await findGitlabById(gitlabId);
-
+ const { COMPOSE_PATH } = paths();
await refreshGitlabToken(gitlabId);
const basePath = COMPOSE_PATH;
const outputPath = join(basePath, appName, "code");
@@ -288,6 +370,39 @@ export const cloneRawGitlabRepository = async (entity: Compose) => {
}
};
+export const cloneRawGitlabRepositoryRemote = async (compose: Compose) => {
+ const { appName, gitlabPathNamespace, branch, gitlabId, serverId } = compose;
+
+ if (!serverId) {
+ throw new TRPCError({
+ code: "NOT_FOUND",
+ message: "Server not found",
+ });
+ }
+ if (!gitlabId) {
+ throw new TRPCError({
+ code: "NOT_FOUND",
+ message: "Gitlab Provider not found",
+ });
+ }
+ const gitlabProvider = await findGitlabById(gitlabId);
+ const { COMPOSE_PATH } = paths(true);
+ await refreshGitlabToken(gitlabId);
+ const basePath = COMPOSE_PATH;
+ const outputPath = join(basePath, appName, "code");
+ const repoclone = `gitlab.com/${gitlabPathNamespace}.git`;
+ const cloneUrl = `https://oauth2:${gitlabProvider?.accessToken}@${repoclone}`;
+ try {
+ const command = `
+ rm -rf ${outputPath};
+ git clone --branch ${branch} --depth 1 ${cloneUrl} ${outputPath}
+ `;
+ await execAsyncRemote(serverId, command);
+ } catch (error) {
+ throw error;
+ }
+};
+
export const testGitlabConnection = async (
input: typeof apiGitlabTestConnection._type,
) => {
diff --git a/apps/dokploy/server/utils/providers/raw.ts b/apps/dokploy/server/utils/providers/raw.ts
index 9bf818a30..238216fa0 100644
--- a/apps/dokploy/server/utils/providers/raw.ts
+++ b/apps/dokploy/server/utils/providers/raw.ts
@@ -2,10 +2,13 @@ import { createWriteStream } from "node:fs";
import { writeFile } from "node:fs/promises";
import { join } from "node:path";
import type { Compose } from "@/server/api/services/compose";
-import { COMPOSE_PATH } from "@/server/constants";
+import { paths } from "@/server/constants";
+import { encodeBase64 } from "../docker/utils";
import { recreateDirectory } from "../filesystem/directory";
+import { execAsyncRemote } from "../process/execAsync";
export const createComposeFile = async (compose: Compose, logPath: string) => {
+ const { COMPOSE_PATH } = paths();
const { appName, composeFile } = compose;
const writeStream = createWriteStream(logPath, { flags: "a" });
const outputPath = join(COMPOSE_PATH, appName, "code");
@@ -27,7 +30,26 @@ export const createComposeFile = async (compose: Compose, logPath: string) => {
}
};
+export const getCreateComposeFileCommand = (
+ compose: Compose,
+ logPath: string,
+) => {
+ const { COMPOSE_PATH } = paths(true);
+ const { appName, composeFile } = compose;
+ const outputPath = join(COMPOSE_PATH, appName, "code");
+ const filePath = join(outputPath, "docker-compose.yml");
+ const encodedContent = encodeBase64(composeFile);
+ const bashCommand = `
+ rm -rf ${outputPath};
+ mkdir -p ${outputPath};
+ echo "${encodedContent}" | base64 -d > "${filePath}";
+ echo "File 'docker-compose.yml' created: ✅" >> ${logPath};
+ `;
+ return bashCommand;
+};
+
export const createComposeFileRaw = async (compose: Compose) => {
+ const { COMPOSE_PATH } = paths();
const { appName, composeFile } = compose;
const outputPath = join(COMPOSE_PATH, appName, "code");
const filePath = join(outputPath, "docker-compose.yml");
@@ -38,3 +60,22 @@ export const createComposeFileRaw = async (compose: Compose) => {
throw error;
}
};
+
+export const createComposeFileRawRemote = async (compose: Compose) => {
+ const { COMPOSE_PATH } = paths(true);
+ const { appName, composeFile, serverId } = compose;
+ const outputPath = join(COMPOSE_PATH, appName, "code");
+ const filePath = join(outputPath, "docker-compose.yml");
+
+ try {
+ const encodedContent = encodeBase64(composeFile);
+ const command = `
+ rm -rf ${outputPath};
+ mkdir -p ${outputPath};
+ echo "${encodedContent}" | base64 -d > "${filePath}";
+ `;
+ await execAsyncRemote(serverId, command);
+ } catch (error) {
+ throw error;
+ }
+};
diff --git a/apps/dokploy/server/utils/servers/connection.ts b/apps/dokploy/server/utils/servers/connection.ts
new file mode 100644
index 000000000..846c24554
--- /dev/null
+++ b/apps/dokploy/server/utils/servers/connection.ts
@@ -0,0 +1,3 @@
+import { findServerById } from "@/server/api/services/server";
+import { Client } from "ssh2";
+import { readSSHKey } from "../filesystem/ssh";
diff --git a/apps/dokploy/server/utils/servers/remote-docker.ts b/apps/dokploy/server/utils/servers/remote-docker.ts
new file mode 100644
index 000000000..f7704b687
--- /dev/null
+++ b/apps/dokploy/server/utils/servers/remote-docker.ts
@@ -0,0 +1,23 @@
+import { findServerById } from "@/server/api/services/server";
+import { docker } from "@/server/constants";
+import Dockerode from "dockerode";
+import { readSSHKey } from "../filesystem/ssh";
+
+export const getRemoteDocker = async (serverId?: string | null) => {
+ if (!serverId) return docker;
+ const server = await findServerById(serverId);
+ if (!server.sshKeyId) return docker;
+ const keys = await readSSHKey(server.sshKeyId);
+ const dockerode = new Dockerode({
+ host: server.ipAddress,
+ port: server.port,
+ username: server.username,
+ protocol: "ssh",
+ // @ts-ignore
+ sshOptions: {
+ privateKey: keys.privateKey,
+ },
+ });
+
+ return dockerode;
+};
diff --git a/apps/dokploy/server/utils/traefik/application.ts b/apps/dokploy/server/utils/traefik/application.ts
index 87216fb3d..c149e2ad7 100644
--- a/apps/dokploy/server/utils/traefik/application.ts
+++ b/apps/dokploy/server/utils/traefik/application.ts
@@ -1,8 +1,10 @@
import fs, { writeFileSync } from "node:fs";
import path from "node:path";
import type { Domain } from "@/server/api/services/domain";
-import { DYNAMIC_TRAEFIK_PATH, MAIN_TRAEFIK_PATH } from "@/server/constants";
+import { paths } from "@/server/constants";
import { dump, load } from "js-yaml";
+import { encodeBase64 } from "../docker/utils";
+import { execAsyncRemote } from "../process/execAsync";
import type { FileConfig, HttpLoadBalancerService } from "./file-types";
export const createTraefikConfig = (appName: string) => {
@@ -38,6 +40,7 @@ export const createTraefikConfig = (appName: string) => {
},
};
const yamlStr = dump(config);
+ const { DYNAMIC_TRAEFIK_PATH } = paths();
fs.mkdirSync(DYNAMIC_TRAEFIK_PATH, { recursive: true });
writeFileSync(
path.join(DYNAMIC_TRAEFIK_PATH, `${appName}.yml`),
@@ -46,16 +49,40 @@ export const createTraefikConfig = (appName: string) => {
);
};
-export const removeTraefikConfig = async (appName: string) => {
+export const removeTraefikConfig = async (
+ appName: string,
+ serverId?: string | null,
+) => {
try {
+ const { DYNAMIC_TRAEFIK_PATH } = paths(!!serverId);
const configPath = path.join(DYNAMIC_TRAEFIK_PATH, `${appName}.yml`);
+
+ if (serverId) {
+ await execAsyncRemote(serverId, `rm ${configPath}`);
+ } else {
+ if (fs.existsSync(configPath)) {
+ await fs.promises.unlink(configPath);
+ }
+ }
if (fs.existsSync(configPath)) {
await fs.promises.unlink(configPath);
}
} catch (error) {}
};
+export const removeTraefikConfigRemote = async (
+ appName: string,
+ serverId: string,
+) => {
+ try {
+ const { DYNAMIC_TRAEFIK_PATH } = paths(true);
+ const configPath = path.join(DYNAMIC_TRAEFIK_PATH, `${appName}.yml`);
+ await execAsyncRemote(serverId, `rm ${configPath}`);
+ } catch (error) {}
+};
+
export const loadOrCreateConfig = (appName: string): FileConfig => {
+ const { DYNAMIC_TRAEFIK_PATH } = paths();
const configPath = path.join(DYNAMIC_TRAEFIK_PATH, `${appName}.yml`);
if (fs.existsSync(configPath)) {
const yamlStr = fs.readFileSync(configPath, "utf8");
@@ -67,7 +94,29 @@ export const loadOrCreateConfig = (appName: string): FileConfig => {
return { http: { routers: {}, services: {} } };
};
+export const loadOrCreateConfigRemote = async (
+ serverId: string,
+ appName: string,
+) => {
+ const { DYNAMIC_TRAEFIK_PATH } = paths(true);
+ const fileConfig: FileConfig = { http: { routers: {}, services: {} } };
+ const configPath = path.join(DYNAMIC_TRAEFIK_PATH, `${appName}.yml`);
+ try {
+ const { stdout } = await execAsyncRemote(serverId, `cat ${configPath}`);
+
+ if (!stdout) return fileConfig;
+
+ const parsedConfig = (load(stdout) as FileConfig) || {
+ http: { routers: {}, services: {} },
+ };
+ return parsedConfig;
+ } catch (err) {
+ return fileConfig;
+ }
+};
+
export const readConfig = (appName: string) => {
+ const { DYNAMIC_TRAEFIK_PATH } = paths();
const configPath = path.join(DYNAMIC_TRAEFIK_PATH, `${appName}.yml`);
if (fs.existsSync(configPath)) {
const yamlStr = fs.readFileSync(configPath, "utf8");
@@ -76,7 +125,20 @@ export const readConfig = (appName: string) => {
return null;
};
+export const readRemoteConfig = async (serverId: string, appName: string) => {
+ const { DYNAMIC_TRAEFIK_PATH } = paths(true);
+ const configPath = path.join(DYNAMIC_TRAEFIK_PATH, `${appName}.yml`);
+ try {
+ const { stdout } = await execAsyncRemote(serverId, `cat ${configPath}`);
+ if (!stdout) return null;
+ return stdout;
+ } catch (err) {
+ return null;
+ }
+};
+
export const readMonitoringConfig = () => {
+ const { DYNAMIC_TRAEFIK_PATH } = paths();
const configPath = path.join(DYNAMIC_TRAEFIK_PATH, "access.log");
if (fs.existsSync(configPath)) {
const yamlStr = fs.readFileSync(configPath, "utf8");
@@ -85,8 +147,14 @@ export const readMonitoringConfig = () => {
return null;
};
-export const readConfigInPath = (pathFile: string) => {
+export const readConfigInPath = async (pathFile: string, serverId?: string) => {
const configPath = path.join(pathFile);
+
+ if (serverId) {
+ const { stdout } = await execAsyncRemote(serverId, `cat ${configPath}`);
+ if (!stdout) return null;
+ return stdout;
+ }
if (fs.existsSync(configPath)) {
const yamlStr = fs.readFileSync(configPath, "utf8");
return yamlStr;
@@ -96,6 +164,7 @@ export const readConfigInPath = (pathFile: string) => {
export const writeConfig = (appName: string, traefikConfig: string) => {
try {
+ const { DYNAMIC_TRAEFIK_PATH } = paths();
const configPath = path.join(DYNAMIC_TRAEFIK_PATH, `${appName}.yml`);
fs.writeFileSync(configPath, traefikConfig, "utf8");
} catch (e) {
@@ -103,12 +172,36 @@ export const writeConfig = (appName: string, traefikConfig: string) => {
}
};
-export const writeTraefikConfigInPath = (
- pathFile: string,
+export const writeConfigRemote = async (
+ serverId: string,
+ appName: string,
traefikConfig: string,
+) => {
+ try {
+ const { DYNAMIC_TRAEFIK_PATH } = paths(true);
+ const configPath = path.join(DYNAMIC_TRAEFIK_PATH, `${appName}.yml`);
+ await execAsyncRemote(serverId, `echo '${traefikConfig}' > ${configPath}`);
+ } catch (e) {
+ console.error("Error saving the YAML config file:", e);
+ }
+};
+
+export const writeTraefikConfigInPath = async (
+ pathFile: string,
+ traefikConfig: string,
+ serverId?: string,
) => {
try {
const configPath = path.join(pathFile);
+ if (serverId) {
+ const encoded = encodeBase64(traefikConfig);
+ await execAsyncRemote(
+ serverId,
+ `echo "${encoded}" | base64 -d > "${configPath}"`,
+ );
+ } else {
+ fs.writeFileSync(configPath, traefikConfig, "utf8");
+ }
fs.writeFileSync(configPath, traefikConfig, "utf8");
} catch (e) {
console.error("Error saving the YAML config file:", e);
@@ -120,6 +213,7 @@ export const writeTraefikConfig = (
appName: string,
) => {
try {
+ const { DYNAMIC_TRAEFIK_PATH } = paths();
const configPath = path.join(DYNAMIC_TRAEFIK_PATH, `${appName}.yml`);
const yamlStr = dump(traefikConfig);
fs.writeFileSync(configPath, yamlStr, "utf8");
@@ -128,6 +222,21 @@ export const writeTraefikConfig = (
}
};
+export const writeTraefikConfigRemote = async (
+ traefikConfig: FileConfig,
+ appName: string,
+ serverId: string,
+) => {
+ try {
+ const { DYNAMIC_TRAEFIK_PATH } = paths(true);
+ const configPath = path.join(DYNAMIC_TRAEFIK_PATH, `${appName}.yml`);
+ const yamlStr = dump(traefikConfig);
+ await execAsyncRemote(serverId, `echo '${yamlStr}' > ${configPath}`);
+ } catch (e) {
+ console.error("Error saving the YAML config file:", e);
+ }
+};
+
export const createServiceConfig = (
appName: string,
domain: Domain,
diff --git a/apps/dokploy/server/utils/traefik/domain.ts b/apps/dokploy/server/utils/traefik/domain.ts
index 3a34f0456..684586fca 100644
--- a/apps/dokploy/server/utils/traefik/domain.ts
+++ b/apps/dokploy/server/utils/traefik/domain.ts
@@ -3,14 +3,23 @@ import type { ApplicationNested } from "../builders";
import {
createServiceConfig,
loadOrCreateConfig,
+ loadOrCreateConfigRemote,
removeTraefikConfig,
+ removeTraefikConfigRemote,
writeTraefikConfig,
+ writeTraefikConfigRemote,
} from "./application";
import type { FileConfig, HttpRouter } from "./file-types";
export const manageDomain = async (app: ApplicationNested, domain: Domain) => {
const { appName } = app;
- const config: FileConfig = loadOrCreateConfig(appName);
+ let config: FileConfig;
+
+ if (app.serverId) {
+ config = await loadOrCreateConfigRemote(app.serverId, appName);
+ } else {
+ config = loadOrCreateConfig(appName);
+ }
const serviceName = `${appName}-service-${domain.uniqueConfigKey}`;
const routerName = `${appName}-router-${domain.uniqueConfigKey}`;
const routerNameSecure = `${appName}-router-websecure-${domain.uniqueConfigKey}`;
@@ -36,11 +45,26 @@ export const manageDomain = async (app: ApplicationNested, domain: Domain) => {
}
config.http.services[serviceName] = createServiceConfig(appName, domain);
- writeTraefikConfig(config, appName);
+
+ if (app.serverId) {
+ await writeTraefikConfigRemote(config, appName, app.serverId);
+ } else {
+ writeTraefikConfig(config, appName);
+ }
};
-export const removeDomain = async (appName: string, uniqueKey: number) => {
- const config: FileConfig = loadOrCreateConfig(appName);
+export const removeDomain = async (
+ application: ApplicationNested,
+ uniqueKey: number,
+) => {
+ const { appName, serverId } = application;
+ let config: FileConfig;
+
+ if (serverId) {
+ config = await loadOrCreateConfigRemote(serverId, appName);
+ } else {
+ config = loadOrCreateConfig(appName);
+ }
const routerKey = `${appName}-router-${uniqueKey}`;
const routerSecureKey = `${appName}-router-websecure-${uniqueKey}`;
@@ -61,9 +85,17 @@ export const removeDomain = async (appName: string, uniqueKey: number) => {
config?.http?.routers &&
Object.keys(config?.http?.routers).length === 0
) {
- await removeTraefikConfig(appName);
+ if (serverId) {
+ await removeTraefikConfigRemote(appName, serverId);
+ } else {
+ await removeTraefikConfig(appName);
+ }
} else {
- writeTraefikConfig(config, appName);
+ if (serverId) {
+ await writeTraefikConfigRemote(config, appName, serverId);
+ } else {
+ writeTraefikConfig(config, appName);
+ }
}
};
diff --git a/apps/dokploy/server/utils/traefik/middleware.ts b/apps/dokploy/server/utils/traefik/middleware.ts
index 09c78a193..df8360510 100644
--- a/apps/dokploy/server/utils/traefik/middleware.ts
+++ b/apps/dokploy/server/utils/traefik/middleware.ts
@@ -1,8 +1,10 @@
import { existsSync, readFileSync, writeFileSync } from "node:fs";
import { join } from "node:path";
-import { DYNAMIC_TRAEFIK_PATH } from "@/server/constants";
+import { paths } from "@/server/constants";
import { dump, load } from "js-yaml";
import type { ApplicationNested } from "../builders";
+import { execAsyncRemote } from "../process/execAsync";
+import { writeTraefikConfigRemote } from "./application";
import type { FileConfig } from "./file-types";
export const addMiddleware = (config: FileConfig, middlewareName: string) => {
@@ -42,7 +44,7 @@ export const deleteMiddleware = (
}
};
-export const deleteAllMiddlewares = (application: ApplicationNested) => {
+export const deleteAllMiddlewares = async (application: ApplicationNested) => {
const config = loadMiddlewares();
const { security, appName, redirects } = application;
@@ -59,10 +61,15 @@ export const deleteAllMiddlewares = (application: ApplicationNested) => {
}
}
- writeMiddleware(config);
+ if (application.serverId) {
+ await writeTraefikConfigRemote(config, "middlewares", application.serverId);
+ } else {
+ writeMiddleware(config);
+ }
};
export const loadMiddlewares = () => {
+ const { DYNAMIC_TRAEFIK_PATH } = paths();
const configPath = join(DYNAMIC_TRAEFIK_PATH, "middlewares.yml");
if (!existsSync(configPath)) {
throw new Error(`File not found: ${configPath}`);
@@ -72,7 +79,28 @@ export const loadMiddlewares = () => {
return config;
};
+export const loadRemoteMiddlewares = async (serverId: string) => {
+ const { DYNAMIC_TRAEFIK_PATH } = paths(true);
+ const configPath = join(DYNAMIC_TRAEFIK_PATH, "middlewares.yml");
+
+ try {
+ const { stdout, stderr } = await execAsyncRemote(
+ serverId,
+ `cat ${configPath}`,
+ );
+
+ if (stderr) {
+ console.error(`Error: ${stderr}`);
+ throw new Error(`File not found: ${configPath}`);
+ }
+ const config = load(stdout) as FileConfig;
+ return config;
+ } catch (error) {
+ throw new Error(`File not found: ${configPath}`);
+ }
+};
export const writeMiddleware = (config: T) => {
+ const { DYNAMIC_TRAEFIK_PATH } = paths();
const configPath = join(DYNAMIC_TRAEFIK_PATH, "middlewares.yml");
const newYamlContent = dump(config);
writeFileSync(configPath, newYamlContent, "utf8");
diff --git a/apps/dokploy/server/utils/traefik/redirect.ts b/apps/dokploy/server/utils/traefik/redirect.ts
index 6e0922a61..6b3ea4ae9 100644
--- a/apps/dokploy/server/utils/traefik/redirect.ts
+++ b/apps/dokploy/server/utils/traefik/redirect.ts
@@ -1,15 +1,32 @@
import type { Redirect } from "@/server/api/services/redirect";
-import { loadOrCreateConfig, writeTraefikConfig } from "./application";
+import type { ApplicationNested } from "../builders";
+import {
+ loadOrCreateConfig,
+ loadOrCreateConfigRemote,
+ writeTraefikConfig,
+ writeTraefikConfigRemote,
+} from "./application";
import type { FileConfig } from "./file-types";
import {
addMiddleware,
deleteMiddleware,
loadMiddlewares,
+ loadRemoteMiddlewares,
writeMiddleware,
} from "./middleware";
-export const updateRedirectMiddleware = (appName: string, data: Redirect) => {
- const config = loadMiddlewares();
+export const updateRedirectMiddleware = async (
+ application: ApplicationNested,
+ data: Redirect,
+) => {
+ const { appName, serverId } = application;
+ let config: FileConfig;
+
+ if (serverId) {
+ config = await loadRemoteMiddlewares(serverId);
+ } else {
+ config = loadMiddlewares();
+ }
const middlewareName = `redirect-${appName}-${data.uniqueConfigKey}`;
if (config?.http?.middlewares?.[middlewareName]) {
@@ -22,10 +39,26 @@ export const updateRedirectMiddleware = (appName: string, data: Redirect) => {
};
}
- writeMiddleware(config);
+ if (serverId) {
+ await writeTraefikConfigRemote(config, "middlewares", serverId);
+ } else {
+ writeMiddleware(config);
+ }
};
-export const createRedirectMiddleware = (appName: string, data: Redirect) => {
- const config = loadMiddlewares();
+export const createRedirectMiddleware = async (
+ application: ApplicationNested,
+ data: Redirect,
+) => {
+ const { appName, serverId } = application;
+
+ let config: FileConfig;
+
+ if (serverId) {
+ config = await loadRemoteMiddlewares(serverId);
+ } else {
+ config = loadMiddlewares();
+ }
+
const middlewareName = `redirect-${appName}-${data.uniqueConfigKey}`;
const newMiddleware = {
[middlewareName]: {
@@ -44,25 +77,56 @@ export const createRedirectMiddleware = (appName: string, data: Redirect) => {
};
}
- const appConfig = loadOrCreateConfig(appName);
+ let appConfig: FileConfig;
+
+ if (serverId) {
+ appConfig = await loadOrCreateConfigRemote(serverId, appName);
+ } else {
+ appConfig = loadOrCreateConfig(appName);
+ }
addMiddleware(appConfig, middlewareName);
- writeTraefikConfig(appConfig, appName);
- writeMiddleware(config);
+ if (serverId) {
+ await writeTraefikConfigRemote(config, "middlewares", serverId);
+ await writeTraefikConfigRemote(appConfig, appName, serverId);
+ } else {
+ writeMiddleware(config);
+ writeTraefikConfig(appConfig, appName);
+ }
};
-export const removeRedirectMiddleware = (appName: string, data: Redirect) => {
- const config = loadMiddlewares();
+export const removeRedirectMiddleware = async (
+ application: ApplicationNested,
+ data: Redirect,
+) => {
+ const { appName, serverId } = application;
+ let config: FileConfig;
+
+ if (serverId) {
+ config = await loadRemoteMiddlewares(serverId);
+ } else {
+ config = loadMiddlewares();
+ }
const middlewareName = `redirect-${appName}-${data.uniqueConfigKey}`;
if (config?.http?.middlewares?.[middlewareName]) {
delete config.http.middlewares[middlewareName];
}
-
- const appConfig = loadOrCreateConfig(appName);
+ let appConfig: FileConfig;
+ if (serverId) {
+ appConfig = await loadOrCreateConfigRemote(serverId, appName);
+ } else {
+ appConfig = loadOrCreateConfig(appName);
+ }
deleteMiddleware(appConfig, middlewareName);
- writeTraefikConfig(appConfig, appName);
- writeMiddleware(config);
+
+ if (serverId) {
+ await writeTraefikConfigRemote(config, "middlewares", serverId);
+ await writeTraefikConfigRemote(appConfig, appName, serverId);
+ } else {
+ writeTraefikConfig(appConfig, appName);
+ writeMiddleware(config);
+ }
};
diff --git a/apps/dokploy/server/utils/traefik/registry.ts b/apps/dokploy/server/utils/traefik/registry.ts
index 2335179fc..eca4401df 100644
--- a/apps/dokploy/server/utils/traefik/registry.ts
+++ b/apps/dokploy/server/utils/traefik/registry.ts
@@ -1,12 +1,13 @@
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import type { Registry } from "@/server/api/services/registry";
-import { REGISTRY_PATH } from "@/server/constants";
+import { paths } from "@/server/constants";
import { dump, load } from "js-yaml";
import { removeDirectoryIfExistsContent } from "../filesystem/directory";
import type { FileConfig, HttpRouter } from "./file-types";
export const manageRegistry = async (registry: Registry) => {
+ const { REGISTRY_PATH } = paths();
if (!existsSync(REGISTRY_PATH)) {
mkdirSync(REGISTRY_PATH, { recursive: true });
}
@@ -36,6 +37,7 @@ export const manageRegistry = async (registry: Registry) => {
};
export const removeSelfHostedRegistry = async () => {
+ const { REGISTRY_PATH } = paths();
await removeDirectoryIfExistsContent(REGISTRY_PATH);
};
@@ -60,6 +62,7 @@ const createRegistryRouterConfig = async (registry: Registry) => {
};
const loadOrCreateConfig = (): FileConfig => {
+ const { REGISTRY_PATH } = paths();
const configPath = join(REGISTRY_PATH, "registry.yml");
if (existsSync(configPath)) {
const yamlStr = readFileSync(configPath, "utf8");
diff --git a/apps/dokploy/server/utils/traefik/security.ts b/apps/dokploy/server/utils/traefik/security.ts
index 484151bcb..3b3d81bbb 100644
--- a/apps/dokploy/server/utils/traefik/security.ts
+++ b/apps/dokploy/server/utils/traefik/security.ts
@@ -1,6 +1,12 @@
import type { Security } from "@/server/api/services/security";
import * as bcrypt from "bcrypt";
-import { loadOrCreateConfig, writeTraefikConfig } from "./application";
+import type { ApplicationNested } from "../builders";
+import {
+ loadOrCreateConfig,
+ loadOrCreateConfigRemote,
+ writeTraefikConfig,
+ writeTraefikConfigRemote,
+} from "./application";
import type {
BasicAuthMiddleware,
FileConfig,
@@ -10,14 +16,22 @@ import {
addMiddleware,
deleteMiddleware,
loadMiddlewares,
+ loadRemoteMiddlewares,
writeMiddleware,
} from "./middleware";
export const createSecurityMiddleware = async (
- appName: string,
+ application: ApplicationNested,
data: Security,
) => {
- const config = loadMiddlewares();
+ const { appName, serverId } = application;
+ let config: FileConfig;
+
+ if (serverId) {
+ config = await loadRemoteMiddlewares(serverId);
+ } else {
+ config = loadMiddlewares();
+ }
const middlewareName = `auth-${appName}`;
const user = `${data.username}:${await bcrypt.hash(data.password, 10)}`;
@@ -38,17 +52,42 @@ export const createSecurityMiddleware = async (
};
}
}
+ let appConfig: FileConfig;
- const appConfig = loadOrCreateConfig(appName);
-
+ if (serverId) {
+ appConfig = await loadOrCreateConfigRemote(serverId, appName);
+ } else {
+ appConfig = loadOrCreateConfig(appName);
+ }
addMiddleware(appConfig, middlewareName);
- writeTraefikConfig(appConfig, appName);
- writeMiddleware(config);
+ if (serverId) {
+ await writeTraefikConfigRemote(config, "middlewares", serverId);
+ await writeTraefikConfigRemote(appConfig, appName, serverId);
+ } else {
+ writeTraefikConfig(appConfig, appName);
+ writeMiddleware(config);
+ }
};
-export const removeSecurityMiddleware = (appName: string, data: Security) => {
- const config = loadMiddlewares();
- const appConfig = loadOrCreateConfig(appName);
+export const removeSecurityMiddleware = async (
+ application: ApplicationNested,
+ data: Security,
+) => {
+ const { appName, serverId } = application;
+ let config: FileConfig;
+
+ if (serverId) {
+ config = await loadRemoteMiddlewares(serverId);
+ } else {
+ config = loadMiddlewares();
+ }
+ let appConfig: FileConfig;
+
+ if (serverId) {
+ appConfig = await loadOrCreateConfigRemote(serverId, appName);
+ } else {
+ appConfig = loadOrCreateConfig(appName);
+ }
const middlewareName = `auth-${appName}`;
if (config.http?.middlewares) {
@@ -67,12 +106,20 @@ export const removeSecurityMiddleware = (appName: string, data: Security) => {
delete config.http.middlewares[middlewareName];
}
deleteMiddleware(appConfig, middlewareName);
- writeTraefikConfig(appConfig, appName);
+ if (serverId) {
+ await writeTraefikConfigRemote(appConfig, appName, serverId);
+ } else {
+ writeTraefikConfig(appConfig, appName);
+ }
}
}
}
- writeMiddleware(config);
+ if (serverId) {
+ await writeTraefikConfigRemote(config, "middlewares", serverId);
+ } else {
+ writeMiddleware(config);
+ }
};
const isBasicAuthMiddleware = (
diff --git a/apps/dokploy/server/utils/traefik/web-server.ts b/apps/dokploy/server/utils/traefik/web-server.ts
index 5073383f6..2b966e731 100644
--- a/apps/dokploy/server/utils/traefik/web-server.ts
+++ b/apps/dokploy/server/utils/traefik/web-server.ts
@@ -1,7 +1,7 @@
import { existsSync, readFileSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import type { Admin } from "@/server/api/services/admin";
-import { MAIN_TRAEFIK_PATH } from "@/server/constants";
+import { paths } from "@/server/constants";
import { dump, load } from "js-yaml";
import { loadOrCreateConfig, writeTraefikConfig } from "./application";
import type { FileConfig } from "./file-types";
@@ -42,6 +42,7 @@ export const updateServerTraefik = (
export const updateLetsEncryptEmail = (newEmail: string | null) => {
try {
if (!newEmail) return;
+ const { MAIN_TRAEFIK_PATH } = paths();
const configPath = join(MAIN_TRAEFIK_PATH, "traefik.yml");
const configContent = readFileSync(configPath, "utf8");
const config = load(configContent) as MainTraefikConfig;
@@ -58,6 +59,7 @@ export const updateLetsEncryptEmail = (newEmail: string | null) => {
};
export const readMainConfig = () => {
+ const { MAIN_TRAEFIK_PATH } = paths();
const configPath = join(MAIN_TRAEFIK_PATH, "traefik.yml");
if (existsSync(configPath)) {
const yamlStr = readFileSync(configPath, "utf8");
@@ -68,6 +70,7 @@ export const readMainConfig = () => {
export const writeMainConfig = (traefikConfig: string) => {
try {
+ const { MAIN_TRAEFIK_PATH } = paths();
const configPath = join(MAIN_TRAEFIK_PATH, "traefik.yml");
writeFileSync(configPath, traefikConfig, "utf8");
} catch (e) {
diff --git a/apps/dokploy/server/wss/docker-container-logs.ts b/apps/dokploy/server/wss/docker-container-logs.ts
index 0c40ecefa..a55951c56 100644
--- a/apps/dokploy/server/wss/docker-container-logs.ts
+++ b/apps/dokploy/server/wss/docker-container-logs.ts
@@ -1,7 +1,10 @@
import type http from "node:http";
import { spawn } from "node-pty";
+import { Client } from "ssh2";
import { WebSocketServer } from "ws";
+import { findServerById } from "../api/services/server";
import { validateWebSocketRequest } from "../auth/auth";
+import { readSSHKey } from "../utils/filesystem/ssh";
import { getShell } from "./utils";
export const setupDockerContainerLogsWebSocketServer = (
@@ -30,6 +33,7 @@ export const setupDockerContainerLogsWebSocketServer = (
const url = new URL(req.url || "", `http://${req.headers.host}`);
const containerId = url.searchParams.get("containerId");
const tail = url.searchParams.get("tail");
+ const serverId = url.searchParams.get("serverId");
const { user, session } = await validateWebSocketRequest(req);
if (!containerId) {
@@ -42,41 +46,86 @@ export const setupDockerContainerLogsWebSocketServer = (
return;
}
try {
- const shell = getShell();
- const ptyProcess = spawn(
- shell,
- ["-c", `docker container logs --tail ${tail} --follow ${containerId}`],
- {
- name: "xterm-256color",
- cwd: process.env.HOME,
- env: process.env,
- encoding: "utf8",
- cols: 80,
- rows: 30,
- },
- );
+ if (serverId) {
+ const server = await findServerById(serverId);
- ptyProcess.onData((data) => {
- ws.send(data);
- });
- ws.on("close", () => {
- ptyProcess.kill();
- });
- ws.on("message", (message) => {
- try {
- let command: string | Buffer[] | Buffer | ArrayBuffer;
- if (Buffer.isBuffer(message)) {
- command = message.toString("utf8");
- } else {
- command = message;
+ if (!server.sshKeyId) return;
+ const keys = await readSSHKey(server.sshKeyId);
+ const client = new Client();
+ new Promise((resolve, reject) => {
+ client
+ .once("ready", () => {
+ const command = `
+ bash -c "docker container logs --tail ${tail} --follow ${containerId}"
+ `;
+ client.exec(command, (err, stream) => {
+ if (err) {
+ console.error("Execution error:", err);
+ reject(err);
+ return;
+ }
+ stream
+ .on("close", () => {
+ console.log("Connection closed ✅");
+ client.end();
+ resolve();
+ })
+ .on("data", (data: string) => {
+ ws.send(data.toString());
+ })
+ .stderr.on("data", (data) => {
+ ws.send(data.toString());
+ });
+ });
+ })
+ .connect({
+ host: server.ipAddress,
+ port: server.port,
+ username: server.username,
+ privateKey: keys.privateKey,
+ timeout: 99999,
+ });
+ });
+ } else {
+ const shell = getShell();
+ const ptyProcess = spawn(
+ shell,
+ [
+ "-c",
+ `docker container logs --tail ${tail} --follow ${containerId}`,
+ ],
+ {
+ name: "xterm-256color",
+ cwd: process.env.HOME,
+ env: process.env,
+ encoding: "utf8",
+ cols: 80,
+ rows: 30,
+ },
+ );
+
+ ptyProcess.onData((data) => {
+ ws.send(data);
+ });
+ ws.on("close", () => {
+ ptyProcess.kill();
+ });
+ ws.on("message", (message) => {
+ try {
+ let command: string | Buffer[] | Buffer | ArrayBuffer;
+ if (Buffer.isBuffer(message)) {
+ command = message.toString("utf8");
+ } else {
+ command = message;
+ }
+ ptyProcess.write(command.toString());
+ } catch (error) {
+ // @ts-ignore
+ const errorMessage = error?.message as unknown as string;
+ ws.send(errorMessage);
}
- ptyProcess.write(command.toString());
- } catch (error) {
- // @ts-ignore
- const errorMessage = error?.message as unknown as string;
- ws.send(errorMessage);
- }
- });
+ });
+ }
} catch (error) {
// @ts-ignore
const errorMessage = error?.message as unknown as string;
diff --git a/apps/dokploy/server/wss/docker-container-terminal.ts b/apps/dokploy/server/wss/docker-container-terminal.ts
index 252c8465a..a7b728922 100644
--- a/apps/dokploy/server/wss/docker-container-terminal.ts
+++ b/apps/dokploy/server/wss/docker-container-terminal.ts
@@ -1,7 +1,10 @@
import type http from "node:http";
import { spawn } from "node-pty";
+import { Client } from "ssh2";
import { WebSocketServer } from "ws";
+import { findServerById } from "../api/services/server";
import { validateWebSocketRequest } from "../auth/auth";
+import { readSSHKey } from "../utils/filesystem/ssh";
import { getShell } from "./utils";
export const setupDockerContainerTerminalWebSocketServer = (
@@ -30,6 +33,7 @@ export const setupDockerContainerTerminalWebSocketServer = (
const url = new URL(req.url || "", `http://${req.headers.host}`);
const containerId = url.searchParams.get("containerId");
const activeWay = url.searchParams.get("activeWay");
+ const serverId = url.searchParams.get("serverId");
const { user, session } = await validateWebSocketRequest(req);
if (!containerId) {
@@ -42,41 +46,107 @@ export const setupDockerContainerTerminalWebSocketServer = (
return;
}
try {
- const shell = getShell();
- const ptyProcess = spawn(
- shell,
- ["-c", `docker exec -it ${containerId} ${activeWay}`],
- {
- name: "xterm-256color",
- cwd: process.env.HOME,
- env: process.env,
- encoding: "utf8",
- cols: 80,
- rows: 30,
- },
- );
+ if (serverId) {
+ const server = await findServerById(serverId);
+ if (!server.sshKeyId)
+ throw new Error("No SSH key available for this server");
- ptyProcess.onData((data) => {
- ws.send(data);
- });
- ws.on("close", () => {
- ptyProcess.kill();
- });
- ws.on("message", (message) => {
- try {
- let command: string | Buffer[] | Buffer | ArrayBuffer;
- if (Buffer.isBuffer(message)) {
- command = message.toString("utf8");
- } else {
- command = message;
+ const keys = await readSSHKey(server.sshKeyId);
+ const conn = new Client();
+ let stdout = "";
+ let stderr = "";
+ conn
+ .once("ready", () => {
+ conn.exec(
+ `docker exec -it ${containerId} ${activeWay}`,
+ { pty: true },
+ (err, stream) => {
+ if (err) throw err;
+
+ stream
+ .on("close", (code: number, signal: string) => {
+ console.log(
+ `Stream :: close :: code: ${code}, signal: ${signal}`,
+ );
+ ws.send(`\nContainer closed with code: ${code}\n`);
+ conn.end();
+ })
+ .on("data", (data: string) => {
+ stdout += data.toString();
+ ws.send(data.toString());
+ })
+ .stderr.on("data", (data) => {
+ stderr += data.toString();
+ ws.send(data.toString());
+ console.error("Error: ", data.toString());
+ });
+
+ ws.on("message", (message) => {
+ try {
+ let command: string | Buffer[] | Buffer | ArrayBuffer;
+ if (Buffer.isBuffer(message)) {
+ command = message.toString("utf8");
+ } else {
+ command = message;
+ }
+ stream.write(command.toString());
+ } catch (error) {
+ // @ts-ignore
+ const errorMessage = error?.message as unknown as string;
+ ws.send(errorMessage);
+ }
+ });
+
+ ws.on("close", () => {
+ stream.end();
+ });
+ },
+ );
+ })
+ .connect({
+ host: server.ipAddress,
+ port: server.port,
+ username: server.username,
+ privateKey: keys.privateKey,
+ timeout: 99999,
+ });
+ } else {
+ const shell = getShell();
+ const ptyProcess = spawn(
+ shell,
+ ["-c", `docker exec -it ${containerId} ${activeWay}`],
+ {
+ name: "xterm-256color",
+ cwd: process.env.HOME,
+ env: process.env,
+ encoding: "utf8",
+ cols: 80,
+ rows: 30,
+ },
+ );
+
+ ptyProcess.onData((data) => {
+ ws.send(data);
+ });
+ ws.on("close", () => {
+ ptyProcess.kill();
+ });
+ ws.on("message", (message) => {
+ try {
+ let command: string | Buffer[] | Buffer | ArrayBuffer;
+ if (Buffer.isBuffer(message)) {
+ command = message.toString("utf8");
+ } else {
+ command = message;
+ }
+ ptyProcess.write(command.toString());
+ } catch (error) {
+ // @ts-ignore
+ const errorMessage = error?.message as unknown as string;
+ ws.send(errorMessage);
}
- ptyProcess.write(command.toString());
- } catch (error) {
- // @ts-ignore
- const errorMessage = error?.message as unknown as string;
- ws.send(errorMessage);
- }
- });
+ });
+ }
} catch (error) {
// @ts-ignore
const errorMessage = error?.message as unknown as string;
diff --git a/apps/dokploy/server/wss/docker-stats.ts b/apps/dokploy/server/wss/docker-stats.ts
index d5f7ada8b..ed1dc46f0 100644
--- a/apps/dokploy/server/wss/docker-stats.ts
+++ b/apps/dokploy/server/wss/docker-stats.ts
@@ -46,7 +46,6 @@ export const setupDockerStatsMonitoringSocketServer = (
ws.close();
return;
}
-
const intervalId = setInterval(async () => {
try {
const filter = {
@@ -72,7 +71,11 @@ export const setupDockerStatsMonitoringSocketServer = (
return;
}
- await recordAdvancedStats(appName, container?.Id);
+ const stats = await docker.getContainer(container.Id).stats({
+ stream: false,
+ });
+
+ await recordAdvancedStats(stats, appName);
const data = await getLastAdvancedStatsFile(appName);
ws.send(
diff --git a/apps/dokploy/server/wss/listen-deployment.ts b/apps/dokploy/server/wss/listen-deployment.ts
index f27860e37..1e38bcdf7 100644
--- a/apps/dokploy/server/wss/listen-deployment.ts
+++ b/apps/dokploy/server/wss/listen-deployment.ts
@@ -1,7 +1,10 @@
import { spawn } from "node:child_process";
import type http from "node:http";
+import { Client } from "ssh2";
import { WebSocketServer } from "ws";
+import { findServerById } from "../api/services/server";
import { validateWebSocketRequest } from "../auth/auth";
+import { readSSHKey } from "../utils/filesystem/ssh";
export const setupDeploymentLogsWebSocketServer = (
server: http.Server,
@@ -27,6 +30,7 @@ export const setupDeploymentLogsWebSocketServer = (
wssTerm.on("connection", async (ws, req) => {
const url = new URL(req.url || "", `http://${req.headers.host}`);
const logPath = url.searchParams.get("logPath");
+ const serverId = url.searchParams.get("serverId");
const { user, session } = await validateWebSocketRequest(req);
if (!logPath) {
@@ -39,20 +43,63 @@ export const setupDeploymentLogsWebSocketServer = (
ws.close();
return;
}
+
try {
- const tail = spawn("tail", ["-n", "+1", "-f", logPath]);
+ if (serverId) {
+ const server = await findServerById(serverId);
- tail.stdout.on("data", (data) => {
- ws.send(data.toString());
- });
+ if (!server.sshKeyId) return;
+ const keys = await readSSHKey(server.sshKeyId);
+ const client = new Client();
+ new Promise((resolve, reject) => {
+ client
+ .on("ready", () => {
+ const command = `
+ tail -n +1 -f ${logPath};
+ `;
+ client.exec(command, (err, stream) => {
+ if (err) {
+ console.error("Execution error:", err);
+ reject(err);
+ return;
+ }
+ stream
+ .on("close", () => {
+ console.log("Connection closed ✅");
+ client.end();
+ resolve();
+ })
+ .on("data", (data: string) => {
+ ws.send(data.toString());
+ })
+ .stderr.on("data", (data) => {
+ ws.send(data.toString());
+ });
+ });
+ })
+ .connect({
+ host: server.ipAddress,
+ port: server.port,
+ username: server.username,
+ privateKey: keys.privateKey,
+ timeout: 99999,
+ });
+ });
+ } else {
+ const tail = spawn("tail", ["-n", "+1", "-f", logPath]);
- tail.stderr.on("data", (data) => {
- ws.send(new Error(`tail error: ${data.toString()}`).message);
- });
+ tail.stdout.on("data", (data) => {
+ ws.send(data.toString());
+ });
+
+ tail.stderr.on("data", (data) => {
+ ws.send(new Error(`tail error: ${data.toString()}`).message);
+ });
+ }
} catch (error) {
// @ts-ignore
// const errorMessage = error?.message as unknown as string;
- // ws.send(errorMessage);
+ ws.send(errorMessage);
}
});
};
diff --git a/apps/dokploy/server/wss/terminal.ts b/apps/dokploy/server/wss/terminal.ts
index 666c59642..2f2ff1a8c 100644
--- a/apps/dokploy/server/wss/terminal.ts
+++ b/apps/dokploy/server/wss/terminal.ts
@@ -1,12 +1,11 @@
-import { writeFileSync } from "node:fs";
import type http from "node:http";
-import { tmpdir } from "node:os";
-import { join } from "node:path";
+import path from "node:path";
import { spawn } from "node-pty";
import { publicIpv4, publicIpv6 } from "public-ip";
import { WebSocketServer } from "ws";
-import { findAdmin } from "../api/services/admin";
+import { findServerById } from "../api/services/server";
import { validateWebSocketRequest } from "../auth/auth";
+import { paths } from "../constants";
export const getPublicIpWithFallback = async () => {
// @ts-ignore
@@ -50,63 +49,61 @@ export const setupTerminalWebSocketServer = (
}
});
- // eslint-disable-next-line @typescript-eslint/no-misused-promises
wssTerm.on("connection", async (ws, req) => {
const url = new URL(req.url || "", `http://${req.headers.host}`);
- const userSSH = url.searchParams.get("userSSH");
+ const serverId = url.searchParams.get("serverId");
const { user, session } = await validateWebSocketRequest(req);
- if (!user || !session) {
+ if (!user || !session || !serverId) {
ws.close();
return;
}
- if (user) {
- const admin = await findAdmin();
- const privateKey = admin.sshPrivateKey || "";
- const tempDir = tmpdir();
- const tempKeyPath = join(tempDir, "temp_ssh_key");
- writeFileSync(tempKeyPath, privateKey, { encoding: "utf8", mode: 0o600 });
- const sshUser = userSSH;
- const ip =
- process.env.NODE_ENV === "production"
- ? await getPublicIpWithFallback()
- : "localhost";
+ const server = await findServerById(serverId);
- const sshCommand = [
- "ssh",
- ...((process.env.NODE_ENV === "production" && ["-i", tempKeyPath]) ||
- []),
- `${sshUser}@${ip}`,
- ];
- const ptyProcess = spawn("ssh", sshCommand.slice(1), {
- name: "xterm-256color",
- cwd: process.env.HOME,
- env: process.env,
- encoding: "utf8",
- cols: 80,
- rows: 30,
- });
-
- ptyProcess.onData((data) => {
- ws.send(data);
- });
- ws.on("message", (message) => {
- try {
- let command: string | Buffer[] | Buffer | ArrayBuffer;
- if (Buffer.isBuffer(message)) {
- command = message.toString("utf8");
- } else {
- command = message;
- }
- ptyProcess.write(command.toString());
- } catch (error) {
- console.log(error);
- }
- });
-
- ws.on("close", () => {
- ptyProcess.kill();
- });
+ if (!server) {
+ ws.close();
+ return;
}
+ const { SSH_PATH } = paths();
+ const privateKey = path.join(SSH_PATH, `${server.sshKeyId}_rsa`);
+ const sshCommand = [
+ "ssh",
+ "-o",
+ "StrictHostKeyChecking=no",
+ "-i",
+ privateKey,
+ "-p",
+ `${server.port}`,
+ `${server.username}@${server.ipAddress}`,
+ ];
+ const ptyProcess = spawn("ssh", sshCommand.slice(1), {
+ name: "xterm-256color",
+ cwd: process.env.HOME,
+ env: process.env,
+ encoding: "utf8",
+ cols: 80,
+ rows: 30,
+ });
+
+ ptyProcess.onData((data) => {
+ ws.send(data);
+ });
+ ws.on("message", (message) => {
+ try {
+ let command: string | Buffer[] | Buffer | ArrayBuffer;
+ if (Buffer.isBuffer(message)) {
+ command = message.toString("utf8");
+ } else {
+ command = message;
+ }
+ ptyProcess.write(command.toString());
+ } catch (error) {
+ console.log(error);
+ }
+ });
+
+ ws.on("close", () => {
+ ptyProcess.kill();
+ });
});
};
diff --git a/apps/dokploy/templates/roundcube/docker-compose.yml b/apps/dokploy/templates/roundcube/docker-compose.yml
new file mode 100644
index 000000000..440f907dd
--- /dev/null
+++ b/apps/dokploy/templates/roundcube/docker-compose.yml
@@ -0,0 +1,17 @@
+services:
+ roundcubemail:
+ image: roundcube/roundcubemail:1.6.9-apache
+ volumes:
+ - ./www:/var/www/html
+ - ./db/sqlite:/var/roundcube/db
+ environment:
+ - ROUNDCUBEMAIL_DB_TYPE=sqlite
+ - ROUNDCUBEMAIL_SKIN=elastic
+ - ROUNDCUBEMAIL_DEFAULT_HOST=${DEFAULT_HOST}
+ - ROUNDCUBEMAIL_SMTP_SERVER=${SMTP_SERVER}
+ networks:
+ - dokploy-network
+
+networks:
+ dokploy-network:
+ external: true
diff --git a/apps/dokploy/templates/roundcube/index.ts b/apps/dokploy/templates/roundcube/index.ts
new file mode 100644
index 000000000..8df8c743c
--- /dev/null
+++ b/apps/dokploy/templates/roundcube/index.ts
@@ -0,0 +1,24 @@
+import {
+ type DomainSchema,
+ type Schema,
+ type Template,
+ generateRandomDomain,
+} from "../utils";
+
+export function generate(schema: Schema): Template {
+ const randomDomain = generateRandomDomain(schema);
+ const envs = [
+ "DEFAULT_HOST=tls://mail.example.com",
+ "SMTP_SERVER=tls://mail.example.com",
+ ];
+
+ const domains: DomainSchema[] = [
+ {
+ host: randomDomain,
+ port: 80,
+ serviceName: "roundcubemail",
+ },
+ ];
+
+ return { envs, domains };
+}
diff --git a/apps/dokploy/templates/templates.ts b/apps/dokploy/templates/templates.ts
index e3508d1b1..3f5f8c25f 100644
--- a/apps/dokploy/templates/templates.ts
+++ b/apps/dokploy/templates/templates.ts
@@ -525,5 +525,20 @@ export const templates: TemplateData[] = [
},
tags: ["self-hosted", "open-source", "analytics"],
load: () => import("./erpnext/index").then((m) => m.generate),
+ },
+ {
+ id: "roundcube",
+ name: "Roundcube",
+ version: "1.6.9",
+ description:
+ "Free and open source webmail software for the masses, written in PHP.",
+ logo: "roundcube.svg",
+ links: {
+ github: "https://github.com/roundcube/roundcubemail",
+ website: "https://roundcube.net/",
+ docs: "https://roundcube.net/about/",
+ },
+ tags: ["self-hosted", "email", "webmail"],
+ load: () => import("./roundcube/index").then((m) => m.generate),
},
];
diff --git a/apps/dokploy/templates/utils/index.ts b/apps/dokploy/templates/utils/index.ts
index 7be92dad2..9f2345b6c 100644
--- a/apps/dokploy/templates/utils/index.ts
+++ b/apps/dokploy/templates/utils/index.ts
@@ -2,6 +2,7 @@ import { randomBytes } from "node:crypto";
import { readFile } from "node:fs/promises";
import { join } from "node:path";
import type { Domain } from "@/server/api/services/domain";
+// import { IS_CLOUD } from "@/server/constants";
import { TRPCError } from "@trpc/server";
import { templates } from "../templates";
import type { TemplatesKeys } from "../types/templates-data.type";
@@ -28,7 +29,8 @@ export const generateRandomDomain = ({
}: Schema): string => {
const hash = randomBytes(3).toString("hex");
const slugIp = serverIp.replaceAll(".", "-");
- return `${projectName}-${hash}${process.env.NODE_ENV === "production" ? `-${slugIp}` : ""}.traefik.me`;
+
+ return `${projectName}-${hash}${slugIp === "" ? "" : `-${slugIp}`}.traefik.me`;
};
export const generateHash = (projectName: string, quantity = 3): string => {
diff --git a/apps/website/components/Footer.tsx b/apps/website/components/Footer.tsx
index c1235add3..88ac33a5a 100644
--- a/apps/website/components/Footer.tsx
+++ b/apps/website/components/Footer.tsx
@@ -33,7 +33,7 @@ export function Footer() {
diff --git a/apps/website/public/canary.sh b/apps/website/public/canary.sh
index 3a9102b00..32f95bff9 100644
--- a/apps/website/public/canary.sh
+++ b/apps/website/public/canary.sh
@@ -1,114 +1,133 @@
#!/bin/bash
+install_dokploy() {
+ if [ "$(id -u)" != "0" ]; then
+ echo "This script must be run as root" >&2
+ exit 1
+ fi
-if [ "$(id -u)" != "0" ]; then
- echo "This script must be run as root" >&2
- exit 1
-fi
-
-# check if is Mac OS
-if [ "$(uname)" = "Darwin" ]; then
- echo "This script must be run on Linux" >&2
- exit 1
-fi
+ # check if is Mac OS
+ if [ "$(uname)" = "Darwin" ]; then
+ echo "This script must be run on Linux" >&2
+ exit 1
+ fi
-# check if is running inside a container
-if [ -f /.dockerenv ]; then
- echo "This script must be run on Linux" >&2
- exit 1
-fi
+ # check if is running inside a container
+ if [ -f /.dockerenv ]; then
+ echo "This script must be run on Linux" >&2
+ exit 1
+ fi
-# check if something is running on port 80
-if ss -tulnp | grep ':80 ' >/dev/null; then
- echo "Error: something is already running on port 80" >&2
- exit 1
-fi
+ # check if something is running on port 80
+ if ss -tulnp | grep ':80 ' >/dev/null; then
+ echo "Error: something is already running on port 80" >&2
+ exit 1
+ fi
-# check if something is running on port 443
-if ss -tulnp | grep ':443 ' >/dev/null; then
- echo "Error: something is already running on port 443" >&2
- exit 1
-fi
+ # check if something is running on port 443
+ if ss -tulnp | grep ':443 ' >/dev/null; then
+ echo "Error: something is already running on port 443" >&2
+ exit 1
+ fi
-command_exists() {
- command -v "$@" > /dev/null 2>&1
-}
+ command_exists() {
+ command -v "$@" > /dev/null 2>&1
+ }
-if command_exists docker; then
- echo "Docker already installed"
-else
- curl -sSL https://get.docker.com | sh
-fi
-
-docker swarm leave --force 2>/dev/null
-
-get_ip() {
- # Try to get IPv4
- local ipv4=$(curl -4s https://ifconfig.io 2>/dev/null)
-
- if [ -n "$ipv4" ]; then
- echo "$ipv4"
+ if command_exists docker; then
+ echo "Docker already installed"
else
- # Try to get IPv6
- local ipv6=$(curl -6s https://ifconfig.io 2>/dev/null)
- if [ -n "$ipv6" ]; then
- echo "$ipv6"
+ curl -sSL https://get.docker.com | sh
+ fi
+
+ docker swarm leave --force 2>/dev/null
+
+ get_ip() {
+ # Try to get IPv4
+ local ipv4=$(curl -4s https://ifconfig.io 2>/dev/null)
+
+ if [ -n "$ipv4" ]; then
+ echo "$ipv4"
+ else
+ # Try to get IPv6
+ local ipv6=$(curl -6s https://ifconfig.io 2>/dev/null)
+ if [ -n "$ipv6" ]; then
+ echo "$ipv6"
+ fi
fi
- fi
+ }
+
+ advertise_addr=$(get_ip)
+
+ docker swarm init --advertise-addr $advertise_addr
+
+ echo "Swarm initialized"
+
+ docker network rm -f dokploy-network 2>/dev/null
+ docker network create --driver overlay --attachable dokploy-network
+
+ echo "Network created"
+
+ mkdir -p /etc/dokploy
+
+ chmod 777 /etc/dokploy
+
+ docker pull dokploy/dokploy:canary
+
+ # Installation
+ docker service create \
+ --name dokploy \
+ --replicas 1 \
+ --network dokploy-network \
+ --mount type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock \
+ --mount type=bind,source=/etc/dokploy,target=/etc/dokploy \
+ --mount type=volume,source=dokploy-docker-config,target=/root/.docker \
+ --publish published=3000,target=3000,mode=host \
+ --update-parallelism 1 \
+ --update-order stop-first \
+ --constraint 'node.role == manager' \
+ -e RELEASE_TAG=canary \
+ dokploy/dokploy:canary
+
+ GREEN="\033[0;32m"
+ YELLOW="\033[1;33m"
+ BLUE="\033[0;34m"
+ NC="\033[0m" # No Color
+
+ format_ip_for_url() {
+ local ip="$1"
+ if echo "$ip" | grep -q ':'; then
+ # IPv6
+ echo "[${ip}]"
+ else
+ # IPv4
+ echo "${ip}"
+ fi
+ }
+
+ formatted_addr=$(format_ip_for_url "$advertise_addr")
+ echo ""
+ printf "${GREEN}Congratulations, Dokploy is installed!${NC}\n"
+ printf "${BLUE}Wait 15 seconds for the server to start${NC}\n"
+ printf "${YELLOW}Please go to http://${formatted_addr}:3000${NC}\n\n"
+ echo ""
}
-advertise_addr=$(get_ip)
+update_dokploy() {
+ echo "Updating Dokploy..."
+
+ # Pull the latest canary image
+ docker pull dokploy/dokploy:canary
-docker swarm init --advertise-addr $advertise_addr
+ # Update the service
+ docker service update --image dokploy/dokploy:canary dokploy
-echo "Swarm initialized"
-
-docker network rm -f dokploy-network 2>/dev/null
-docker network create --driver overlay --attachable dokploy-network
-
-echo "Network created"
-
-mkdir -p /etc/dokploy
-
-chmod 777 /etc/dokploy
-
-docker pull dokploy/dokploy:canary
-
-# Installation
-docker service create \
- --name dokploy \
- --replicas 1 \
- --network dokploy-network \
- --mount type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock \
- --mount type=bind,source=/etc/dokploy,target=/etc/dokploy \
- --mount type=volume,source=dokploy-docker-config,target=/root/.docker \
- --publish published=3000,target=3000,mode=host \
- --update-parallelism 1 \
- --update-order stop-first \
- --constraint 'node.role == manager' \
- -e RELEASE_TAG=canary \
- dokploy/dokploy:canary
-
-GREEN="\033[0;32m"
-YELLOW="\033[1;33m"
-BLUE="\033[0;34m"
-NC="\033[0m" # No Color
-
-format_ip_for_url() {
- local ip="$1"
- if echo "$ip" | grep -q ':'; then
- # IPv6
- echo "[${ip}]"
- else
- # IPv4
- echo "${ip}"
- fi
+ echo "Dokploy has been updated to the latest canary version."
}
-formatted_addr=$(format_ip_for_url "$advertise_addr")
-echo ""
-printf "${GREEN}Congratulations, Dokploy is installed!${NC}\n"
-printf "${BLUE}Wait 15 seconds for the server to start${NC}\n"
-printf "${YELLOW}Please go to http://${formatted_addr}:3000${NC}\n\n"
-echo ""
-
+# Main script execution
+if [ "$1" = "update" ]; then
+ update_dokploy
+else
+ install_dokploy
+fi
\ No newline at end of file
diff --git a/apps/website/public/feature.sh b/apps/website/public/feature.sh
index 453da012f..fca4fccd0 100644
--- a/apps/website/public/feature.sh
+++ b/apps/website/public/feature.sh
@@ -1,97 +1,117 @@
#!/bin/bash
-
-if [ "$(id -u)" != "0" ]; then
- echo "This script must be run as root" >&2
- exit 1
-fi
-
-# check if is Mac OS
-if [ "$(uname)" = "Darwin" ]; then
- echo "This script must be run on Linux" >&2
- exit 1
-fi
-
-
-# check if is running inside a container
-if [ -f /.dockerenv ]; then
- echo "This script must be run on Linux" >&2
- exit 1
-fi
-
-# check if something is running on port 80
-if ss -tulnp | grep ':80 ' >/dev/null; then
- echo "Error: something is already running on port 80" >&2
- exit 1
-fi
-
-# check if something is running on port 443
-if ss -tulnp | grep ':443 ' >/dev/null; then
- echo "Error: something is already running on port 443" >&2
- exit 1
-fi
-
-command_exists() {
- command -v "$@" > /dev/null 2>&1
-}
-
-if command_exists docker; then
- echo "Docker already installed"
-else
- curl -sSL https://get.docker.com | sh
-fi
-
-docker swarm leave --force 2>/dev/null
-advertise_addr=$(curl -s ifconfig.me)
-
-docker swarm init --advertise-addr $advertise_addr
-
-echo "Swarm initialized"
-
-docker network rm -f dokploy-network 2>/dev/null
-docker network create --driver overlay --attachable dokploy-network
-
-echo "Network created"
-
-mkdir -p /etc/dokploy
-
-chmod 777 /etc/dokploy
-
-docker pull dokploy/dokploy:feature
-
-# Installation
-docker service create \
- --name dokploy \
- --replicas 1 \
- --network dokploy-network \
- --mount type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock \
- --mount type=bind,source=/etc/dokploy,target=/etc/dokploy \
- --mount type=volume,source=dokploy-docker-config,target=/root/.docker \
- --publish published=3000,target=3000,mode=host \
- --update-parallelism 1 \
- --update-order stop-first \
- --constraint 'node.role == manager' \
- -e RELEASE_TAG=feature \
- dokploy/dokploy:feature
-
-GREEN="\033[0;32m"
-YELLOW="\033[1;33m"
-BLUE="\033[0;34m"
-NC="\033[0m" # No Color
-
-format_ip_for_url() {
- local ip="$1"
- if echo "$ip" | grep -q ':'; then
- # IPv6
- echo "[${ip}]"
- else
- # IPv4
- echo "${ip}"
+install_dokploy() {
+ if [ "$(id -u)" != "0" ]; then
+ echo "This script must be run as root" >&2
+ exit 1
fi
+
+ # check if is Mac OS
+ if [ "$(uname)" = "Darwin" ]; then
+ echo "This script must be run on Linux" >&2
+ exit 1
+ fi
+
+
+ # check if is running inside a container
+ if [ -f /.dockerenv ]; then
+ echo "This script must be run on Linux" >&2
+ exit 1
+ fi
+
+ # check if something is running on port 80
+ if ss -tulnp | grep ':80 ' >/dev/null; then
+ echo "Error: something is already running on port 80" >&2
+ exit 1
+ fi
+
+ # check if something is running on port 443
+ if ss -tulnp | grep ':443 ' >/dev/null; then
+ echo "Error: something is already running on port 443" >&2
+ exit 1
+ fi
+
+ command_exists() {
+ command -v "$@" > /dev/null 2>&1
+ }
+
+ if command_exists docker; then
+ echo "Docker already installed"
+ else
+ curl -sSL https://get.docker.com | sh
+ fi
+
+ docker swarm leave --force 2>/dev/null
+ advertise_addr=$(curl -s ifconfig.me)
+
+ docker swarm init --advertise-addr $advertise_addr
+
+ echo "Swarm initialized"
+
+ docker network rm -f dokploy-network 2>/dev/null
+ docker network create --driver overlay --attachable dokploy-network
+
+ echo "Network created"
+
+ mkdir -p /etc/dokploy
+
+ chmod 777 /etc/dokploy
+
+ docker pull dokploy/dokploy:feature
+
+ # Installation
+ docker service create \
+ --name dokploy \
+ --replicas 1 \
+ --network dokploy-network \
+ --mount type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock \
+ --mount type=bind,source=/etc/dokploy,target=/etc/dokploy \
+ --mount type=volume,source=dokploy-docker-config,target=/root/.docker \
+ --publish published=3000,target=3000,mode=host \
+ --update-parallelism 1 \
+ --update-order stop-first \
+ --constraint 'node.role == manager' \
+ -e RELEASE_TAG=feature \
+ dokploy/dokploy:feature
+
+ GREEN="\033[0;32m"
+ YELLOW="\033[1;33m"
+ BLUE="\033[0;34m"
+ NC="\033[0m" # No Color
+
+ format_ip_for_url() {
+ local ip="$1"
+ if echo "$ip" | grep -q ':'; then
+ # IPv6
+ echo "[${ip}]"
+ else
+ # IPv4
+ echo "${ip}"
+ fi
+ }
+
+ formatted_addr=$(format_ip_for_url "$advertise_addr")
+ echo ""
+ printf "${GREEN}Congratulations, Dokploy is installed!${NC}\n"
+ printf "${BLUE}Wait 15 seconds for the server to start${NC}\n"
+ printf "${YELLOW}Please go to http://${formatted_addr}:3000${NC}\n\n"
+ echo ""
}
-formatted_addr=$(format_ip_for_url "$advertise_addr")
-echo ""
-printf "${GREEN}Congratulations, Dokploy is installed!${NC}\n"
-printf "${BLUE}Wait 15 seconds for the server to start${NC}\n"
-printf "${YELLOW}Please go to http://${formatted_addr}:3000${NC}\n\n"
-echo ""
+update_dokploy() {
+ echo "Updating Dokploy..."
+
+ # Pull the latest feature image
+ docker pull dokploy/dokploy:feature
+
+ # Update the service
+ docker service update --image dokploy/dokploy:feature dokploy
+
+ echo "Dokploy has been updated to the latest feature version."
+}
+
+# Main script execution
+if [ "$1" = "update" ]; then
+ update_dokploy
+else
+ install_dokploy
+fi
\ No newline at end of file
diff --git a/apps/website/public/install.sh b/apps/website/public/install.sh
index e68a59fd2..f5b318f2e 100644
--- a/apps/website/public/install.sh
+++ b/apps/website/public/install.sh
@@ -1,112 +1,130 @@
#!/bin/bash
+install_dokploy() {
+ if [ "$(id -u)" != "0" ]; then
+ echo "This script must be run as root" >&2
+ exit 1
+ fi
-if [ "$(id -u)" != "0" ]; then
- echo "This script must be run as root" >&2
- exit 1
-fi
+ # check if is Mac OS
+ if [ "$(uname)" = "Darwin" ]; then
+ echo "This script must be run on Linux" >&2
+ exit 1
+ fi
-# check if is Mac OS
-if [ "$(uname)" = "Darwin" ]; then
- echo "This script must be run on Linux" >&2
- exit 1
-fi
+ # check if is running inside a container
+ if [ -f /.dockerenv ]; then
+ echo "This script must be run on Linux" >&2
+ exit 1
+ fi
+ # check if something is running on port 80
+ if ss -tulnp | grep ':80 ' >/dev/null; then
+ echo "Error: something is already running on port 80" >&2
+ exit 1
+ fi
-# check if is running inside a container
-if [ -f /.dockerenv ]; then
- echo "This script must be run on Linux" >&2
- exit 1
-fi
+ # check if something is running on port 443
+ if ss -tulnp | grep ':443 ' >/dev/null; then
+ echo "Error: something is already running on port 443" >&2
+ exit 1
+ fi
-# check if something is running on port 80
-if ss -tulnp | grep ':80 ' >/dev/null; then
- echo "Error: something is already running on port 80" >&2
- exit 1
-fi
+ command_exists() {
+ command -v "$@" > /dev/null 2>&1
+ }
-# check if something is running on port 443
-if ss -tulnp | grep ':443 ' >/dev/null; then
- echo "Error: something is already running on port 443" >&2
- exit 1
-fi
-
-command_exists() {
- command -v "$@" > /dev/null 2>&1
-}
-
-if command_exists docker; then
- echo "Docker already installed"
-else
- curl -sSL https://get.docker.com | sh
-fi
-
-docker swarm leave --force 2>/dev/null
-
-get_ip() {
- # Try to get IPv4
- local ipv4=$(curl -4s https://ifconfig.io 2>/dev/null)
-
- if [ -n "$ipv4" ]; then
- echo "$ipv4"
+ if command_exists docker; then
+ echo "Docker already installed"
else
- # Try to get IPv6
- local ipv6=$(curl -6s https://ifconfig.io 2>/dev/null)
- if [ -n "$ipv6" ]; then
- echo "$ipv6"
+ curl -sSL https://get.docker.com | sh
+ fi
+
+ docker swarm leave --force 2>/dev/null
+
+ get_ip() {
+ # Try to get IPv4
+ local ipv4=$(curl -4s https://ifconfig.io 2>/dev/null)
+
+ if [ -n "$ipv4" ]; then
+ echo "$ipv4"
+ else
+ # Try to get IPv6
+ local ipv6=$(curl -6s https://ifconfig.io 2>/dev/null)
+ if [ -n "$ipv6" ]; then
+ echo "$ipv6"
+ fi
fi
- fi
+ }
+
+ advertise_addr=$(get_ip)
+
+ docker swarm init --advertise-addr $advertise_addr
+
+ echo "Swarm initialized"
+
+ docker network rm -f dokploy-network 2>/dev/null
+ docker network create --driver overlay --attachable dokploy-network
+
+ echo "Network created"
+
+ mkdir -p /etc/dokploy
+
+ chmod 777 /etc/dokploy
+
+ docker pull dokploy/dokploy:latest
+
+ # Installation
+ docker service create \
+ --name dokploy \
+ --replicas 1 \
+ --network dokploy-network \
+ --mount type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock \
+ --mount type=bind,source=/etc/dokploy,target=/etc/dokploy \
+ --mount type=volume,source=dokploy-docker-config,target=/root/.docker \
+ --publish published=3000,target=3000,mode=host \
+ --update-parallelism 1 \
+ --update-order stop-first \
+ --constraint 'node.role == manager' \
+ dokploy/dokploy:latest
+
+ GREEN="\033[0;32m"
+ YELLOW="\033[1;33m"
+ BLUE="\033[0;34m"
+ NC="\033[0m" # No Color
+
+ format_ip_for_url() {
+ local ip="$1"
+ if echo "$ip" | grep -q ':'; then
+ # IPv6
+ echo "[${ip}]"
+ else
+ # IPv4
+ echo "${ip}"
+ fi
+ }
+
+ formatted_addr=$(format_ip_for_url "$advertise_addr")
+ echo ""
+ printf "${GREEN}Congratulations, Dokploy is installed!${NC}\n"
+ printf "${BLUE}Wait 15 seconds for the server to start${NC}\n"
+ printf "${YELLOW}Please go to http://${formatted_addr}:3000${NC}\n\n"
}
-advertise_addr=$(get_ip)
+update_dokploy() {
+ echo "Updating Dokploy..."
+
+ # Pull the latest image
+ docker pull dokploy/dokploy:latest
-docker swarm init --advertise-addr $advertise_addr
+ # Update the service
+ docker service update --image dokploy/dokploy:latest dokploy
-echo "Swarm initialized"
-
-docker network rm -f dokploy-network 2>/dev/null
-docker network create --driver overlay --attachable dokploy-network
-
-echo "Network created"
-
-mkdir -p /etc/dokploy
-
-chmod 777 /etc/dokploy
-
-docker pull dokploy/dokploy:latest
-
-# Installation
-docker service create \
- --name dokploy \
- --replicas 1 \
- --network dokploy-network \
- --mount type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock \
- --mount type=bind,source=/etc/dokploy,target=/etc/dokploy \
- --mount type=volume,source=dokploy-docker-config,target=/root/.docker \
- --publish published=3000,target=3000,mode=host \
- --update-parallelism 1 \
- --update-order stop-first \
- --constraint 'node.role == manager' \
- dokploy/dokploy:latest
-
-GREEN="\033[0;32m"
-YELLOW="\033[1;33m"
-BLUE="\033[0;34m"
-NC="\033[0m" # No Color
-
-format_ip_for_url() {
- local ip="$1"
- if echo "$ip" | grep -q ':'; then
- # IPv6
- echo "[${ip}]"
- else
- # IPv4
- echo "${ip}"
- fi
+ echo "Dokploy has been updated to the latest version."
}
-formatted_addr=$(format_ip_for_url "$advertise_addr")
-echo ""
-printf "${GREEN}Congratulations, Dokploy is installed!${NC}\n"
-printf "${BLUE}Wait 15 seconds for the server to start${NC}\n"
-printf "${YELLOW}Please go to http://${formatted_addr}:3000${NC}\n\n"
-echo ""
+# Main script execution
+if [ "$1" = "update" ]; then
+ update_dokploy
+else
+ install_dokploy
+fi
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 2ebf93c2a..38557719a 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -122,9 +122,6 @@ importers:
apps/dokploy:
dependencies:
- '@aws-sdk/client-s3':
- specifier: 3.515.0
- version: 3.515.0
'@codemirror/lang-json':
specifier: ^6.0.1
version: 6.0.1
@@ -380,6 +377,9 @@ importers:
sonner:
specifier: ^1.4.0
version: 1.5.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+ ssh2:
+ specifier: 1.15.0
+ version: 1.15.0
superjson:
specifier: ^2.2.1
version: 2.2.1
@@ -459,6 +459,9 @@ importers:
'@types/react-dom':
specifier: 18.3.0
version: 18.3.0
+ '@types/ssh2':
+ specifier: 1.15.1
+ version: 1.15.1
'@types/swagger-ui-react':
specifier: ^4.18.3
version: 4.18.3
@@ -612,173 +615,6 @@ packages:
resolution: {integrity: sha512-9K6xOqeevacvweLGik6LnZCb1fBtCOSIWQs8d096XGeqoLKC33UVMGz9+77Gw44KvbH4pKcQPWo4ZpxkXYj05w==}
engines: {node: '>= 16'}
- '@aws-crypto/crc32@3.0.0':
- resolution: {integrity: sha512-IzSgsrxUcsrejQbPVilIKy16kAT52EwB6zSaI+M3xxIhKh5+aldEyvI+z6erM7TCLB2BJsFrtHjp6/4/sr+3dA==}
-
- '@aws-crypto/crc32c@3.0.0':
- resolution: {integrity: sha512-ENNPPManmnVJ4BTXlOjAgD7URidbAznURqD0KvfREyc4o20DPYdEldU1f5cQ7Jbj0CJJSPaMIk/9ZshdB3210w==}
-
- '@aws-crypto/ie11-detection@3.0.0':
- resolution: {integrity: sha512-341lBBkiY1DfDNKai/wXM3aujNBkXR7tq1URPQDL9wi3AUbI80NR74uF1TXHMm7po1AcnFk8iu2S2IeU/+/A+Q==}
-
- '@aws-crypto/sha1-browser@3.0.0':
- resolution: {integrity: sha512-NJth5c997GLHs6nOYTzFKTbYdMNA6/1XlKVgnZoaZcQ7z7UJlOgj2JdbHE8tiYLS3fzXNCguct77SPGat2raSw==}
-
- '@aws-crypto/sha256-browser@3.0.0':
- resolution: {integrity: sha512-8VLmW2B+gjFbU5uMeqtQM6Nj0/F1bro80xQXCW6CQBWgosFWXTx77aeOF5CAIAmbOK64SdMBJdNr6J41yP5mvQ==}
-
- '@aws-crypto/sha256-js@3.0.0':
- resolution: {integrity: sha512-PnNN7os0+yd1XvXAy23CFOmTbMaDxgxXtTKHybrJ39Y8kGzBATgBFibWJKH6BhytLI/Zyszs87xCOBNyBig6vQ==}
-
- '@aws-crypto/supports-web-crypto@3.0.0':
- resolution: {integrity: sha512-06hBdMwUAb2WFTuGG73LSC0wfPu93xWwo5vL2et9eymgmu3Id5vFAHBbajVWiGhPO37qcsdCap/FqXvJGJWPIg==}
-
- '@aws-crypto/util@3.0.0':
- resolution: {integrity: sha512-2OJlpeJpCR48CC8r+uKVChzs9Iungj9wkZrl8Z041DWEWvyIHILYKCPNzJghKsivj+S3mLo6BVc7mBNzdxA46w==}
-
- '@aws-sdk/client-s3@3.515.0':
- resolution: {integrity: sha512-K527n83hrMUdosxOYTzL63wtlJtmN5SUJZnGY1sUR6UyOrnOr9lS6t3AB6BgHqLFRFZJqSqmhflv2cOD7P1UPg==}
- engines: {node: '>=14.0.0'}
-
- '@aws-sdk/client-sso-oidc@3.515.0':
- resolution: {integrity: sha512-zACa8LNlPUdlNUBqQRf5a3MfouLNtcBfm84v2c8M976DwJrMGONPe1QjyLLsD38uESQiXiVQRruj/b000iMXNw==}
- engines: {node: '>=14.0.0'}
- peerDependencies:
- '@aws-sdk/credential-provider-node': ^3.515.0
-
- '@aws-sdk/client-sso@3.515.0':
- resolution: {integrity: sha512-4oGBLW476zmkdN98lAns3bObRNO+DLOfg4MDUSR6l6GYBV/zGAtoy2O/FhwYKgA2L5h2ZtElGopLlk/1Q0ePLw==}
- engines: {node: '>=14.0.0'}
-
- '@aws-sdk/client-sts@3.515.0':
- resolution: {integrity: sha512-ScYuvaIDgip3atOJIA1FU2n0gJkEdveu1KrrCPathoUCV5zpK8qQmO/n+Fj/7hKFxeKdFbB+4W4CsJWYH94nlg==}
- engines: {node: '>=14.0.0'}
- peerDependencies:
- '@aws-sdk/credential-provider-node': ^3.515.0
-
- '@aws-sdk/core@3.513.0':
- resolution: {integrity: sha512-L+9DL4apWuqNKVOMJ8siAuWoRM9rZf9w1iPv8S2o83WO2jVK7E/m+rNW1dFo9HsA5V1ccDl2H2qLXx24HiHmOw==}
- engines: {node: '>=14.0.0'}
-
- '@aws-sdk/credential-provider-env@3.515.0':
- resolution: {integrity: sha512-45vxdyqhTAaUMERYVWOziG3K8L2TV9G4ryQS/KZ84o7NAybE9GMdoZRVmGHAO7mJJ1wQiYCM/E+i5b3NW9JfNA==}
- engines: {node: '>=14.0.0'}
-
- '@aws-sdk/credential-provider-http@3.515.0':
- resolution: {integrity: sha512-Ba6FXK77vU4WyheiamNjEuTFmir0eAXuJGPO27lBaA8g+V/seXGHScsbOG14aQGDOr2P02OPwKGZrWWA7BFpfQ==}
- engines: {node: '>=14.0.0'}
-
- '@aws-sdk/credential-provider-ini@3.515.0':
- resolution: {integrity: sha512-ouDlNZdv2TKeVEA/YZk2+XklTXyAAGdbWnl4IgN9ItaodWI+lZjdIoNC8BAooVH+atIV/cZgoGTGQL7j2TxJ9A==}
- engines: {node: '>=14.0.0'}
-
- '@aws-sdk/credential-provider-node@3.515.0':
- resolution: {integrity: sha512-Y4kHSpbxksiCZZNcvsiKUd8Fb2XlyUuONEwqWFNL82ZH6TCCjBGS31wJQCSxBHqYcOL3tiORUEJkoO7uS30uQA==}
- engines: {node: '>=14.0.0'}
-
- '@aws-sdk/credential-provider-process@3.515.0':
- resolution: {integrity: sha512-pSjiOA2FM63LHRKNDvEpBRp80FVGT0Mw/gzgbqFXP+sewk0WVonYbEcMDTJptH3VsLPGzqH/DQ1YL/aEIBuXFQ==}
- engines: {node: '>=14.0.0'}
-
- '@aws-sdk/credential-provider-sso@3.515.0':
- resolution: {integrity: sha512-j7vUkiSmuhpBvZYoPTRTI4ePnQbiZMFl6TNhg9b9DprC1zHkucsZnhRhqjOVlrw/H6J4jmcPGcHHTZ5WQNI5xQ==}
- engines: {node: '>=14.0.0'}
-
- '@aws-sdk/credential-provider-web-identity@3.515.0':
- resolution: {integrity: sha512-66+2g4z3fWwdoGReY8aUHvm6JrKZMTRxjuizljVmMyOBttKPeBYXvUTop/g3ZGUx1f8j+C5qsGK52viYBvtjuQ==}
- engines: {node: '>=14.0.0'}
-
- '@aws-sdk/middleware-bucket-endpoint@3.515.0':
- resolution: {integrity: sha512-Vm423j3udFrhKPaKiXtie+6aF05efjX8lhAu5VOruIvbam7olvdWNdkH7sGWlz1ko3CVa7PwOYjGHiOOhxpEOA==}
- engines: {node: '>=14.0.0'}
-
- '@aws-sdk/middleware-expect-continue@3.515.0':
- resolution: {integrity: sha512-TWCXulivab4reOMx/vxa/IwnPX78fLwI9NUoAxjsqB6W9qjmSnPD43BSVeGvbbl/YNmgk7XfMbZb6IgxW7RyzA==}
- engines: {node: '>=14.0.0'}
-
- '@aws-sdk/middleware-flexible-checksums@3.515.0':
- resolution: {integrity: sha512-ydGjnqNeYlJaAkmQeQnS4pZRAAvzefdm8c234Qh0Fg55xRwHTNLp7uYsdfkTjrdAlj6YIO3Zr6vK6VJ6MGCwug==}
- engines: {node: '>=14.0.0'}
-
- '@aws-sdk/middleware-host-header@3.515.0':
- resolution: {integrity: sha512-I1MwWPzdRKM1luvdDdjdGsDjNVPhj9zaIytEchjTY40NcKOg+p2evLD2y69ozzg8pyXK63r8DdvDGOo9QPuh0A==}
- engines: {node: '>=14.0.0'}
-
- '@aws-sdk/middleware-location-constraint@3.515.0':
- resolution: {integrity: sha512-ORFC5oijjTJsHhUXy9o52/vl5Irf6e83bE/8tBp+sVVx81+E8zTTWZbysoa41c0B5Ycd0H3wCWutvjdXT16ydQ==}
- engines: {node: '>=14.0.0'}
-
- '@aws-sdk/middleware-logger@3.515.0':
- resolution: {integrity: sha512-qXomJzg2m/5seQOxHi/yOXOKfSjwrrJSmEmfwJKJyQgdMbBcjz3Cz0H/1LyC6c5hHm6a/SZgSTzDAbAoUmyL+Q==}
- engines: {node: '>=14.0.0'}
-
- '@aws-sdk/middleware-recursion-detection@3.515.0':
- resolution: {integrity: sha512-dokHLbTV3IHRIBrw9mGoxcNTnQsjlm7TpkJhPdGT9T4Mq399EyQo51u6IsVMm07RXLl2Zw7u+u9p+qWBFzmFRA==}
- engines: {node: '>=14.0.0'}
-
- '@aws-sdk/middleware-sdk-s3@3.515.0':
- resolution: {integrity: sha512-vB8JwiTEAqm1UT9xfugnCgl0H0dtBLUQQK99JwQEWjHPZmQ3HQuVkykmJRY3X0hzKMEgqXodz0hZOvf3Hq1mvQ==}
- engines: {node: '>=14.0.0'}
-
- '@aws-sdk/middleware-signing@3.515.0':
- resolution: {integrity: sha512-SdjCyQCL702I07KhCiBFcoh6+NYtnruHJQIzWwMpBteuYHnCHW1k9uZ6pqacsS+Y6qpAKfTVNpQx2zP2s6QoHA==}
- engines: {node: '>=14.0.0'}
-
- '@aws-sdk/middleware-ssec@3.515.0':
- resolution: {integrity: sha512-0qLjKiorosVBzzaV/o7MEyS9xqLLu02qGbP564Z/FZY74JUQEpBNedgveMUbb6lqr85RnOuwZ0GZ0cBRfH2brQ==}
- engines: {node: '>=14.0.0'}
-
- '@aws-sdk/middleware-user-agent@3.515.0':
- resolution: {integrity: sha512-nOqZjGA/GkjuJ5fUshec9Fv6HFd7ovOTxMJbw3MfAhqXuVZ6dKF41lpVJ4imNsgyFt3shUg9WDY8zGFjlYMB3g==}
- engines: {node: '>=14.0.0'}
-
- '@aws-sdk/region-config-resolver@3.515.0':
- resolution: {integrity: sha512-RIRx9loxMgEAc/r1wPfnfShOuzn4RBi8pPPv6/jhhITEeMnJe6enAh2k5y9DdiVDDgCWZgVFSv0YkAIfzAFsnQ==}
- engines: {node: '>=14.0.0'}
-
- '@aws-sdk/signature-v4-multi-region@3.515.0':
- resolution: {integrity: sha512-5lrCn4DSE0zL41k0L6moqcdExZhWdAnV0/oMEagrISzQYoia+aNTEeyVD3xqJhRbEW4gCj3Uoyis6c8muf7b9g==}
- engines: {node: '>=14.0.0'}
-
- '@aws-sdk/token-providers@3.515.0':
- resolution: {integrity: sha512-MQuf04rIcTXqwDzmyHSpFPF1fKEzRl64oXtCRUF3ddxTdK6wxXkePfK6wNCuL+GEbEcJAoCtIGIRpzGPJvQjHA==}
- engines: {node: '>=14.0.0'}
-
- '@aws-sdk/types@3.515.0':
- resolution: {integrity: sha512-B3gUpiMlpT6ERaLvZZ61D0RyrQPsFYDkCncLPVkZOKkCOoFU46zi1o6T5JcYiz8vkx1q9RGloQ5exh79s5pU/w==}
- engines: {node: '>=14.0.0'}
-
- '@aws-sdk/util-arn-parser@3.495.0':
- resolution: {integrity: sha512-hwdA3XAippSEUxs7jpznwD63YYFR+LtQvlEcebPTgWR9oQgG9TfS+39PUfbnEeje1ICuOrN3lrFqFbmP9uzbMg==}
- engines: {node: '>=14.0.0'}
-
- '@aws-sdk/util-endpoints@3.515.0':
- resolution: {integrity: sha512-UJi+jdwcGFV/F7d3+e2aQn5yZOVpDiAgfgNhPnEtgV0WozJ5/ZUeZBgWvSc/K415N4A4D/9cbBc7+I+35qzcDQ==}
- engines: {node: '>=14.0.0'}
-
- '@aws-sdk/util-locate-window@3.568.0':
- resolution: {integrity: sha512-3nh4TINkXYr+H41QaPelCceEB2FXP3fxp93YZXB/kqJvX0U9j0N0Uk45gvsjmEPzG8XxkPEeLIfT2I1M7A6Lig==}
- engines: {node: '>=16.0.0'}
-
- '@aws-sdk/util-user-agent-browser@3.515.0':
- resolution: {integrity: sha512-pTWQb0JCafTmLHLDv3Qqs/nAAJghcPdGQIBpsCStb0YEzg3At/dOi2AIQ683yYnXmeOxLXJDzmlsovfVObJScw==}
-
- '@aws-sdk/util-user-agent-node@3.515.0':
- resolution: {integrity: sha512-A/KJ+/HTohHyVXLH+t/bO0Z2mPrQgELbQO8tX+B2nElo8uklj70r5cT7F8ETsI9oOy+HDVpiL5/v45ZgpUOiPg==}
- engines: {node: '>=14.0.0'}
- peerDependencies:
- aws-crt: '>=1.0.0'
- peerDependenciesMeta:
- aws-crt:
- optional: true
-
- '@aws-sdk/util-utf8-browser@3.259.0':
- resolution: {integrity: sha512-UvFa/vR+e19XookZF8RzFZBrw2EUkQWxiBW0yYQAhvk3C+QVGl0H3ouca8LDBlBfQKXwmW3huo/59H8rwb1wJw==}
-
- '@aws-sdk/xml-builder@3.496.0':
- resolution: {integrity: sha512-GvEjh537IIeOw1ZkZuB37sV12u+ipS5Z1dwjEC/HAvhl5ac23ULtTr1/n+U1gLNN+BAKSWjKiQ2ksj8DiUzeyw==}
- engines: {node: '>=14.0.0'}
-
'@babel/code-frame@7.24.7':
resolution: {integrity: sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==}
engines: {node: '>=6.9.0'}
@@ -3311,197 +3147,6 @@ packages:
resolution: {integrity: sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==}
engines: {node: '>=18'}
- '@smithy/abort-controller@2.2.0':
- resolution: {integrity: sha512-wRlta7GuLWpTqtFfGo+nZyOO1vEvewdNR1R4rTxpC8XU6vG/NDyrFBhwLZsqg1NUoR1noVaXJPC/7ZK47QCySw==}
- engines: {node: '>=14.0.0'}
-
- '@smithy/chunked-blob-reader-native@2.2.0':
- resolution: {integrity: sha512-VNB5+1oCgX3Fzs072yuRsUoC2N4Zg/LJ11DTxX3+Qu+Paa6AmbIF0E9sc2wthz9Psrk/zcOlTCyuposlIhPjZQ==}
-
- '@smithy/chunked-blob-reader@2.2.0':
- resolution: {integrity: sha512-3GJNvRwXBGdkDZZOGiziVYzDpn4j6zfyULHMDKAGIUo72yHALpE9CbhfQp/XcLNVoc1byfMpn6uW5H2BqPjgaQ==}
-
- '@smithy/config-resolver@2.2.0':
- resolution: {integrity: sha512-fsiMgd8toyUba6n1WRmr+qACzXltpdDkPTAaDqc8QqPBUzO+/JKwL6bUBseHVi8tu9l+3JOK+tSf7cay+4B3LA==}
- engines: {node: '>=14.0.0'}
-
- '@smithy/core@1.4.2':
- resolution: {integrity: sha512-2fek3I0KZHWJlRLvRTqxTEri+qV0GRHrJIoLFuBMZB4EMg4WgeBGfF0X6abnrNYpq55KJ6R4D6x4f0vLnhzinA==}
- engines: {node: '>=14.0.0'}
-
- '@smithy/credential-provider-imds@2.3.0':
- resolution: {integrity: sha512-BWB9mIukO1wjEOo1Ojgl6LrG4avcaC7T/ZP6ptmAaW4xluhSIPZhY+/PI5YKzlk+jsm+4sQZB45Bt1OfMeQa3w==}
- engines: {node: '>=14.0.0'}
-
- '@smithy/eventstream-codec@2.2.0':
- resolution: {integrity: sha512-8janZoJw85nJmQZc4L8TuePp2pk1nxLgkxIR0TUjKJ5Dkj5oelB9WtiSSGXCQvNsJl0VSTvK/2ueMXxvpa9GVw==}
-
- '@smithy/eventstream-serde-browser@2.2.0':
- resolution: {integrity: sha512-UaPf8jKbcP71BGiO0CdeLmlg+RhWnlN8ipsMSdwvqBFigl5nil3rHOI/5GE3tfiuX8LvY5Z9N0meuU7Rab7jWw==}
- engines: {node: '>=14.0.0'}
-
- '@smithy/eventstream-serde-config-resolver@2.2.0':
- resolution: {integrity: sha512-RHhbTw/JW3+r8QQH7PrganjNCiuiEZmpi6fYUAetFfPLfZ6EkiA08uN3EFfcyKubXQxOwTeJRZSQmDDCdUshaA==}
- engines: {node: '>=14.0.0'}
-
- '@smithy/eventstream-serde-node@2.2.0':
- resolution: {integrity: sha512-zpQMtJVqCUMn+pCSFcl9K/RPNtQE0NuMh8sKpCdEHafhwRsjP50Oq/4kMmvxSRy6d8Jslqd8BLvDngrUtmN9iA==}
- engines: {node: '>=14.0.0'}
-
- '@smithy/eventstream-serde-universal@2.2.0':
- resolution: {integrity: sha512-pvoe/vvJY0mOpuF84BEtyZoYfbehiFj8KKWk1ds2AT0mTLYFVs+7sBJZmioOFdBXKd48lfrx1vumdPdmGlCLxA==}
- engines: {node: '>=14.0.0'}
-
- '@smithy/fetch-http-handler@2.5.0':
- resolution: {integrity: sha512-BOWEBeppWhLn/no/JxUL/ghTfANTjT7kg3Ww2rPqTUY9R4yHPXxJ9JhMe3Z03LN3aPwiwlpDIUcVw1xDyHqEhw==}
-
- '@smithy/hash-blob-browser@2.2.0':
- resolution: {integrity: sha512-SGPoVH8mdXBqrkVCJ1Hd1X7vh1zDXojNN1yZyZTZsCno99hVue9+IYzWDjq/EQDDXxmITB0gBmuyPh8oAZSTcg==}
-
- '@smithy/hash-node@2.2.0':
- resolution: {integrity: sha512-zLWaC/5aWpMrHKpoDF6nqpNtBhlAYKF/7+9yMN7GpdR8CzohnWfGtMznPybnwSS8saaXBMxIGwJqR4HmRp6b3g==}
- engines: {node: '>=14.0.0'}
-
- '@smithy/hash-stream-node@2.2.0':
- resolution: {integrity: sha512-aT+HCATOSRMGpPI7bi7NSsTNVZE/La9IaxLXWoVAYMxHT5hGO3ZOGEMZQg8A6nNL+pdFGtZQtND1eoY084HgHQ==}
- engines: {node: '>=14.0.0'}
-
- '@smithy/invalid-dependency@2.2.0':
- resolution: {integrity: sha512-nEDASdbKFKPXN2O6lOlTgrEEOO9NHIeO+HVvZnkqc8h5U9g3BIhWsvzFo+UcUbliMHvKNPD/zVxDrkP1Sbgp8Q==}
-
- '@smithy/is-array-buffer@2.2.0':
- resolution: {integrity: sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==}
- engines: {node: '>=14.0.0'}
-
- '@smithy/md5-js@2.2.0':
- resolution: {integrity: sha512-M26XTtt9IIusVMOWEAhIvFIr9jYj4ISPPGJROqw6vXngO3IYJCnVVSMFn4Tx1rUTG5BiKJNg9u2nxmBiZC5IlQ==}
-
- '@smithy/middleware-content-length@2.2.0':
- resolution: {integrity: sha512-5bl2LG1Ah/7E5cMSC+q+h3IpVHMeOkG0yLRyQT1p2aMJkSrZG7RlXHPuAgb7EyaFeidKEnnd/fNaLLaKlHGzDQ==}
- engines: {node: '>=14.0.0'}
-
- '@smithy/middleware-endpoint@2.5.1':
- resolution: {integrity: sha512-1/8kFp6Fl4OsSIVTWHnNjLnTL8IqpIb/D3sTSczrKFnrE9VMNWxnrRKNvpUHOJ6zpGD5f62TPm7+17ilTJpiCQ==}
- engines: {node: '>=14.0.0'}
-
- '@smithy/middleware-retry@2.3.1':
- resolution: {integrity: sha512-P2bGufFpFdYcWvqpyqqmalRtwFUNUA8vHjJR5iGqbfR6mp65qKOLcUd6lTr4S9Gn/enynSrSf3p3FVgVAf6bXA==}
- engines: {node: '>=14.0.0'}
-
- '@smithy/middleware-serde@2.3.0':
- resolution: {integrity: sha512-sIADe7ojwqTyvEQBe1nc/GXB9wdHhi9UwyX0lTyttmUWDJLP655ZYE1WngnNyXREme8I27KCaUhyhZWRXL0q7Q==}
- engines: {node: '>=14.0.0'}
-
- '@smithy/middleware-stack@2.2.0':
- resolution: {integrity: sha512-Qntc3jrtwwrsAC+X8wms8zhrTr0sFXnyEGhZd9sLtsJ/6gGQKFzNB+wWbOcpJd7BR8ThNCoKt76BuQahfMvpeA==}
- engines: {node: '>=14.0.0'}
-
- '@smithy/node-config-provider@2.3.0':
- resolution: {integrity: sha512-0elK5/03a1JPWMDPaS726Iw6LpQg80gFut1tNpPfxFuChEEklo2yL823V94SpTZTxmKlXFtFgsP55uh3dErnIg==}
- engines: {node: '>=14.0.0'}
-
- '@smithy/node-http-handler@2.5.0':
- resolution: {integrity: sha512-mVGyPBzkkGQsPoxQUbxlEfRjrj6FPyA3u3u2VXGr9hT8wilsoQdZdvKpMBFMB8Crfhv5dNkKHIW0Yyuc7eABqA==}
- engines: {node: '>=14.0.0'}
-
- '@smithy/property-provider@2.2.0':
- resolution: {integrity: sha512-+xiil2lFhtTRzXkx8F053AV46QnIw6e7MV8od5Mi68E1ICOjCeCHw2XfLnDEUHnT9WGUIkwcqavXjfwuJbGlpg==}
- engines: {node: '>=14.0.0'}
-
- '@smithy/protocol-http@3.3.0':
- resolution: {integrity: sha512-Xy5XK1AFWW2nlY/biWZXu6/krgbaf2dg0q492D8M5qthsnU2H+UgFeZLbM76FnH7s6RO/xhQRkj+T6KBO3JzgQ==}
- engines: {node: '>=14.0.0'}
-
- '@smithy/querystring-builder@2.2.0':
- resolution: {integrity: sha512-L1kSeviUWL+emq3CUVSgdogoM/D9QMFaqxL/dd0X7PCNWmPXqt+ExtrBjqT0V7HLN03Vs9SuiLrG3zy3JGnE5A==}
- engines: {node: '>=14.0.0'}
-
- '@smithy/querystring-parser@2.2.0':
- resolution: {integrity: sha512-BvHCDrKfbG5Yhbpj4vsbuPV2GgcpHiAkLeIlcA1LtfpMz3jrqizP1+OguSNSj1MwBHEiN+jwNisXLGdajGDQJA==}
- engines: {node: '>=14.0.0'}
-
- '@smithy/service-error-classification@2.1.5':
- resolution: {integrity: sha512-uBDTIBBEdAQryvHdc5W8sS5YX7RQzF683XrHePVdFmAgKiMofU15FLSM0/HU03hKTnazdNRFa0YHS7+ArwoUSQ==}
- engines: {node: '>=14.0.0'}
-
- '@smithy/shared-ini-file-loader@2.4.0':
- resolution: {integrity: sha512-WyujUJL8e1B6Z4PBfAqC/aGY1+C7T0w20Gih3yrvJSk97gpiVfB+y7c46T4Nunk+ZngLq0rOIdeVeIklk0R3OA==}
- engines: {node: '>=14.0.0'}
-
- '@smithy/signature-v4@2.3.0':
- resolution: {integrity: sha512-ui/NlpILU+6HAQBfJX8BBsDXuKSNrjTSuOYArRblcrErwKFutjrCNb/OExfVRyj9+26F9J+ZmfWT+fKWuDrH3Q==}
- engines: {node: '>=14.0.0'}
-
- '@smithy/smithy-client@2.5.1':
- resolution: {integrity: sha512-jrbSQrYCho0yDaaf92qWgd+7nAeap5LtHTI51KXqmpIFCceKU3K9+vIVTUH72bOJngBMqa4kyu1VJhRcSrk/CQ==}
- engines: {node: '>=14.0.0'}
-
- '@smithy/types@2.12.0':
- resolution: {integrity: sha512-QwYgloJ0sVNBeBuBs65cIkTbfzV/Q6ZNPCJ99EICFEdJYG50nGIY/uYXp+TbsdJReIuPr0a0kXmCvren3MbRRw==}
- engines: {node: '>=14.0.0'}
-
- '@smithy/url-parser@2.2.0':
- resolution: {integrity: sha512-hoA4zm61q1mNTpksiSWp2nEl1dt3j726HdRhiNgVJQMj7mLp7dprtF57mOB6JvEk/x9d2bsuL5hlqZbBuHQylQ==}
-
- '@smithy/util-base64@2.3.0':
- resolution: {integrity: sha512-s3+eVwNeJuXUwuMbusncZNViuhv2LjVJ1nMwTqSA0XAC7gjKhqqxRdJPhR8+YrkoZ9IiIbFk/yK6ACe/xlF+hw==}
- engines: {node: '>=14.0.0'}
-
- '@smithy/util-body-length-browser@2.2.0':
- resolution: {integrity: sha512-dtpw9uQP7W+n3vOtx0CfBD5EWd7EPdIdsQnWTDoFf77e3VUf05uA7R7TGipIo8e4WL2kuPdnsr3hMQn9ziYj5w==}
-
- '@smithy/util-body-length-node@2.3.0':
- resolution: {integrity: sha512-ITWT1Wqjubf2CJthb0BuT9+bpzBfXeMokH/AAa5EJQgbv9aPMVfnM76iFIZVFf50hYXGbtiV71BHAthNWd6+dw==}
- engines: {node: '>=14.0.0'}
-
- '@smithy/util-buffer-from@2.2.0':
- resolution: {integrity: sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==}
- engines: {node: '>=14.0.0'}
-
- '@smithy/util-config-provider@2.3.0':
- resolution: {integrity: sha512-HZkzrRcuFN1k70RLqlNK4FnPXKOpkik1+4JaBoHNJn+RnJGYqaa3c5/+XtLOXhlKzlRgNvyaLieHTW2VwGN0VQ==}
- engines: {node: '>=14.0.0'}
-
- '@smithy/util-defaults-mode-browser@2.2.1':
- resolution: {integrity: sha512-RtKW+8j8skk17SYowucwRUjeh4mCtnm5odCL0Lm2NtHQBsYKrNW0od9Rhopu9wF1gHMfHeWF7i90NwBz/U22Kw==}
- engines: {node: '>= 10.0.0'}
-
- '@smithy/util-defaults-mode-node@2.3.1':
- resolution: {integrity: sha512-vkMXHQ0BcLFysBMWgSBLSk3+leMpFSyyFj8zQtv5ZyUBx8/owVh1/pPEkzmW/DR/Gy/5c8vjLDD9gZjXNKbrpA==}
- engines: {node: '>= 10.0.0'}
-
- '@smithy/util-endpoints@1.2.0':
- resolution: {integrity: sha512-BuDHv8zRjsE5zXd3PxFXFknzBG3owCpjq8G3FcsXW3CykYXuEqM3nTSsmLzw5q+T12ZYuDlVUZKBdpNbhVtlrQ==}
- engines: {node: '>= 14.0.0'}
-
- '@smithy/util-hex-encoding@2.2.0':
- resolution: {integrity: sha512-7iKXR+/4TpLK194pVjKiasIyqMtTYJsgKgM242Y9uzt5dhHnUDvMNb+3xIhRJ9QhvqGii/5cRUt4fJn3dtXNHQ==}
- engines: {node: '>=14.0.0'}
-
- '@smithy/util-middleware@2.2.0':
- resolution: {integrity: sha512-L1qpleXf9QD6LwLCJ5jddGkgWyuSvWBkJwWAZ6kFkdifdso+sk3L3O1HdmPvCdnCK3IS4qWyPxev01QMnfHSBw==}
- engines: {node: '>=14.0.0'}
-
- '@smithy/util-retry@2.2.0':
- resolution: {integrity: sha512-q9+pAFPTfftHXRytmZ7GzLFFrEGavqapFc06XxzZFcSIGERXMerXxCitjOG1prVDR9QdjqotF40SWvbqcCpf8g==}
- engines: {node: '>= 14.0.0'}
-
- '@smithy/util-stream@2.2.0':
- resolution: {integrity: sha512-17faEXbYWIRst1aU9SvPZyMdWmqIrduZjVOqCPMIsWFNxs5yQQgFrJL6b2SdiCzyW9mJoDjFtgi53xx7EH+BXA==}
- engines: {node: '>=14.0.0'}
-
- '@smithy/util-uri-escape@2.2.0':
- resolution: {integrity: sha512-jtmJMyt1xMD/d8OtbVJ2gFZOSKc+ueYJZPW20ULW1GOp/q/YIM0wNh+u8ZFao9UaIGz4WoPW8hC64qlWLIfoDA==}
- engines: {node: '>=14.0.0'}
-
- '@smithy/util-utf8@2.3.0':
- resolution: {integrity: sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==}
- engines: {node: '>=14.0.0'}
-
- '@smithy/util-waiter@2.2.0':
- resolution: {integrity: sha512-IHk53BVw6MPMi2Gsn+hCng8rFA3ZmR3Rk7GllxDUW9qFJl/hiSvskn7XldkECapQVkIg/1dHpMAxI9xSTaLLSA==}
- engines: {node: '>=14.0.0'}
-
'@swagger-api/apidom-ast@1.0.0-alpha.6':
resolution: {integrity: sha512-uzDNIeTLFeITzK7yX9PSsu3dl92rHP/gKMNAlJhmDRr7r+OLr5dCpAzyZ0WvONRxjxR8Otj5LX4AD12+EX32fg==}
@@ -3832,8 +3477,8 @@ packages:
'@types/serve-static@1.15.7':
resolution: {integrity: sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==}
- '@types/ssh2@1.15.0':
- resolution: {integrity: sha512-YcT8jP5F8NzWeevWvcyrrLB3zcneVjzYY9ZDSMAMboI+2zR1qYWFhwsyOFVzT7Jorn67vqxC0FRiw8YyG9P1ww==}
+ '@types/ssh2@1.15.1':
+ resolution: {integrity: sha512-ZIbEqKAsi5gj35y4P4vkJYly642wIbY6PqoN0xiyQGshKUGXR9WQjF/iF9mXBQ8uBKy3ezfsCkcoHKhd0BzuDA==}
'@types/swagger-ui-react@4.18.3':
resolution: {integrity: sha512-Mo/R7IjDVwtiFPs84pWvh5pI9iyNGBjmfielxqbOh2Jv+8WVSDVe8Nu25kb5BOuV2xmGS3o33jr6nwDJMBcX+Q==}
@@ -4271,9 +3916,6 @@ packages:
bottleneck@2.19.5:
resolution: {integrity: sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==}
- bowser@2.11.0:
- resolution: {integrity: sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==}
-
boxen@7.1.1:
resolution: {integrity: sha512-2hCgjEmP8YLWQ130n2FerGv7rYpfBmnmp9Uy2Le1vge6X3gZIfSmEzP5QTDElFxcvVcXlEn8Aq6MU/PZygIOog==}
engines: {node: '>=14.16'}
@@ -5365,10 +5007,6 @@ packages:
fast-uri@3.0.1:
resolution: {integrity: sha512-MWipKbbYiYI0UC7cl8m/i/IWTqfC8YXsqjzybjddLsFjStroQzsHXkc73JutMvBiXmOvapk+axIl79ig5t55Bw==}
- fast-xml-parser@4.2.5:
- resolution: {integrity: sha512-B9/wizE4WngqQftFPmdaMYlXoJlJOYxGQOanC77fq9k8+Z0v5dDSVh+3glErdIROP//s/jgb7ZuxKfB8nVyo0g==}
- hasBin: true
-
fastq@1.17.1:
resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==}
@@ -7825,9 +7463,6 @@ packages:
strip-literal@2.1.0:
resolution: {integrity: sha512-Op+UycaUt/8FbN/Z2TWPBLge3jWrP3xj10f3fnYxf052bKuS3EKs1ZQcVGjnEMdsNVAM+plXRdmjrZ/KgG3Skw==}
- strnum@1.0.5:
- resolution: {integrity: sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==}
-
style-mod@4.1.2:
resolution: {integrity: sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==}
@@ -8062,9 +7697,6 @@ packages:
resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==}
engines: {node: '>=6'}
- tslib@1.14.1:
- resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==}
-
tslib@2.6.3:
resolution: {integrity: sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==}
@@ -8534,512 +8166,6 @@ snapshots:
'@types/json-schema': 7.0.15
js-yaml: 4.1.0
- '@aws-crypto/crc32@3.0.0':
- dependencies:
- '@aws-crypto/util': 3.0.0
- '@aws-sdk/types': 3.515.0
- tslib: 1.14.1
-
- '@aws-crypto/crc32c@3.0.0':
- dependencies:
- '@aws-crypto/util': 3.0.0
- '@aws-sdk/types': 3.515.0
- tslib: 1.14.1
-
- '@aws-crypto/ie11-detection@3.0.0':
- dependencies:
- tslib: 1.14.1
-
- '@aws-crypto/sha1-browser@3.0.0':
- dependencies:
- '@aws-crypto/ie11-detection': 3.0.0
- '@aws-crypto/supports-web-crypto': 3.0.0
- '@aws-crypto/util': 3.0.0
- '@aws-sdk/types': 3.515.0
- '@aws-sdk/util-locate-window': 3.568.0
- '@aws-sdk/util-utf8-browser': 3.259.0
- tslib: 1.14.1
-
- '@aws-crypto/sha256-browser@3.0.0':
- dependencies:
- '@aws-crypto/ie11-detection': 3.0.0
- '@aws-crypto/sha256-js': 3.0.0
- '@aws-crypto/supports-web-crypto': 3.0.0
- '@aws-crypto/util': 3.0.0
- '@aws-sdk/types': 3.515.0
- '@aws-sdk/util-locate-window': 3.568.0
- '@aws-sdk/util-utf8-browser': 3.259.0
- tslib: 1.14.1
-
- '@aws-crypto/sha256-js@3.0.0':
- dependencies:
- '@aws-crypto/util': 3.0.0
- '@aws-sdk/types': 3.515.0
- tslib: 1.14.1
-
- '@aws-crypto/supports-web-crypto@3.0.0':
- dependencies:
- tslib: 1.14.1
-
- '@aws-crypto/util@3.0.0':
- dependencies:
- '@aws-sdk/types': 3.515.0
- '@aws-sdk/util-utf8-browser': 3.259.0
- tslib: 1.14.1
-
- '@aws-sdk/client-s3@3.515.0':
- dependencies:
- '@aws-crypto/sha1-browser': 3.0.0
- '@aws-crypto/sha256-browser': 3.0.0
- '@aws-crypto/sha256-js': 3.0.0
- '@aws-sdk/client-sts': 3.515.0(@aws-sdk/credential-provider-node@3.515.0)
- '@aws-sdk/core': 3.513.0
- '@aws-sdk/credential-provider-node': 3.515.0
- '@aws-sdk/middleware-bucket-endpoint': 3.515.0
- '@aws-sdk/middleware-expect-continue': 3.515.0
- '@aws-sdk/middleware-flexible-checksums': 3.515.0
- '@aws-sdk/middleware-host-header': 3.515.0
- '@aws-sdk/middleware-location-constraint': 3.515.0
- '@aws-sdk/middleware-logger': 3.515.0
- '@aws-sdk/middleware-recursion-detection': 3.515.0
- '@aws-sdk/middleware-sdk-s3': 3.515.0
- '@aws-sdk/middleware-signing': 3.515.0
- '@aws-sdk/middleware-ssec': 3.515.0
- '@aws-sdk/middleware-user-agent': 3.515.0
- '@aws-sdk/region-config-resolver': 3.515.0
- '@aws-sdk/signature-v4-multi-region': 3.515.0
- '@aws-sdk/types': 3.515.0
- '@aws-sdk/util-endpoints': 3.515.0
- '@aws-sdk/util-user-agent-browser': 3.515.0
- '@aws-sdk/util-user-agent-node': 3.515.0
- '@aws-sdk/xml-builder': 3.496.0
- '@smithy/config-resolver': 2.2.0
- '@smithy/core': 1.4.2
- '@smithy/eventstream-serde-browser': 2.2.0
- '@smithy/eventstream-serde-config-resolver': 2.2.0
- '@smithy/eventstream-serde-node': 2.2.0
- '@smithy/fetch-http-handler': 2.5.0
- '@smithy/hash-blob-browser': 2.2.0
- '@smithy/hash-node': 2.2.0
- '@smithy/hash-stream-node': 2.2.0
- '@smithy/invalid-dependency': 2.2.0
- '@smithy/md5-js': 2.2.0
- '@smithy/middleware-content-length': 2.2.0
- '@smithy/middleware-endpoint': 2.5.1
- '@smithy/middleware-retry': 2.3.1
- '@smithy/middleware-serde': 2.3.0
- '@smithy/middleware-stack': 2.2.0
- '@smithy/node-config-provider': 2.3.0
- '@smithy/node-http-handler': 2.5.0
- '@smithy/protocol-http': 3.3.0
- '@smithy/smithy-client': 2.5.1
- '@smithy/types': 2.12.0
- '@smithy/url-parser': 2.2.0
- '@smithy/util-base64': 2.3.0
- '@smithy/util-body-length-browser': 2.2.0
- '@smithy/util-body-length-node': 2.3.0
- '@smithy/util-defaults-mode-browser': 2.2.1
- '@smithy/util-defaults-mode-node': 2.3.1
- '@smithy/util-endpoints': 1.2.0
- '@smithy/util-retry': 2.2.0
- '@smithy/util-stream': 2.2.0
- '@smithy/util-utf8': 2.3.0
- '@smithy/util-waiter': 2.2.0
- fast-xml-parser: 4.2.5
- tslib: 2.6.3
- transitivePeerDependencies:
- - aws-crt
-
- '@aws-sdk/client-sso-oidc@3.515.0(@aws-sdk/credential-provider-node@3.515.0)':
- dependencies:
- '@aws-crypto/sha256-browser': 3.0.0
- '@aws-crypto/sha256-js': 3.0.0
- '@aws-sdk/client-sts': 3.515.0(@aws-sdk/credential-provider-node@3.515.0)
- '@aws-sdk/core': 3.513.0
- '@aws-sdk/credential-provider-node': 3.515.0
- '@aws-sdk/middleware-host-header': 3.515.0
- '@aws-sdk/middleware-logger': 3.515.0
- '@aws-sdk/middleware-recursion-detection': 3.515.0
- '@aws-sdk/middleware-user-agent': 3.515.0
- '@aws-sdk/region-config-resolver': 3.515.0
- '@aws-sdk/types': 3.515.0
- '@aws-sdk/util-endpoints': 3.515.0
- '@aws-sdk/util-user-agent-browser': 3.515.0
- '@aws-sdk/util-user-agent-node': 3.515.0
- '@smithy/config-resolver': 2.2.0
- '@smithy/core': 1.4.2
- '@smithy/fetch-http-handler': 2.5.0
- '@smithy/hash-node': 2.2.0
- '@smithy/invalid-dependency': 2.2.0
- '@smithy/middleware-content-length': 2.2.0
- '@smithy/middleware-endpoint': 2.5.1
- '@smithy/middleware-retry': 2.3.1
- '@smithy/middleware-serde': 2.3.0
- '@smithy/middleware-stack': 2.2.0
- '@smithy/node-config-provider': 2.3.0
- '@smithy/node-http-handler': 2.5.0
- '@smithy/protocol-http': 3.3.0
- '@smithy/smithy-client': 2.5.1
- '@smithy/types': 2.12.0
- '@smithy/url-parser': 2.2.0
- '@smithy/util-base64': 2.3.0
- '@smithy/util-body-length-browser': 2.2.0
- '@smithy/util-body-length-node': 2.3.0
- '@smithy/util-defaults-mode-browser': 2.2.1
- '@smithy/util-defaults-mode-node': 2.3.1
- '@smithy/util-endpoints': 1.2.0
- '@smithy/util-middleware': 2.2.0
- '@smithy/util-retry': 2.2.0
- '@smithy/util-utf8': 2.3.0
- tslib: 2.6.3
- transitivePeerDependencies:
- - aws-crt
-
- '@aws-sdk/client-sso@3.515.0':
- dependencies:
- '@aws-crypto/sha256-browser': 3.0.0
- '@aws-crypto/sha256-js': 3.0.0
- '@aws-sdk/core': 3.513.0
- '@aws-sdk/middleware-host-header': 3.515.0
- '@aws-sdk/middleware-logger': 3.515.0
- '@aws-sdk/middleware-recursion-detection': 3.515.0
- '@aws-sdk/middleware-user-agent': 3.515.0
- '@aws-sdk/region-config-resolver': 3.515.0
- '@aws-sdk/types': 3.515.0
- '@aws-sdk/util-endpoints': 3.515.0
- '@aws-sdk/util-user-agent-browser': 3.515.0
- '@aws-sdk/util-user-agent-node': 3.515.0
- '@smithy/config-resolver': 2.2.0
- '@smithy/core': 1.4.2
- '@smithy/fetch-http-handler': 2.5.0
- '@smithy/hash-node': 2.2.0
- '@smithy/invalid-dependency': 2.2.0
- '@smithy/middleware-content-length': 2.2.0
- '@smithy/middleware-endpoint': 2.5.1
- '@smithy/middleware-retry': 2.3.1
- '@smithy/middleware-serde': 2.3.0
- '@smithy/middleware-stack': 2.2.0
- '@smithy/node-config-provider': 2.3.0
- '@smithy/node-http-handler': 2.5.0
- '@smithy/protocol-http': 3.3.0
- '@smithy/smithy-client': 2.5.1
- '@smithy/types': 2.12.0
- '@smithy/url-parser': 2.2.0
- '@smithy/util-base64': 2.3.0
- '@smithy/util-body-length-browser': 2.2.0
- '@smithy/util-body-length-node': 2.3.0
- '@smithy/util-defaults-mode-browser': 2.2.1
- '@smithy/util-defaults-mode-node': 2.3.1
- '@smithy/util-endpoints': 1.2.0
- '@smithy/util-middleware': 2.2.0
- '@smithy/util-retry': 2.2.0
- '@smithy/util-utf8': 2.3.0
- tslib: 2.6.3
- transitivePeerDependencies:
- - aws-crt
-
- '@aws-sdk/client-sts@3.515.0(@aws-sdk/credential-provider-node@3.515.0)':
- dependencies:
- '@aws-crypto/sha256-browser': 3.0.0
- '@aws-crypto/sha256-js': 3.0.0
- '@aws-sdk/core': 3.513.0
- '@aws-sdk/credential-provider-node': 3.515.0
- '@aws-sdk/middleware-host-header': 3.515.0
- '@aws-sdk/middleware-logger': 3.515.0
- '@aws-sdk/middleware-recursion-detection': 3.515.0
- '@aws-sdk/middleware-user-agent': 3.515.0
- '@aws-sdk/region-config-resolver': 3.515.0
- '@aws-sdk/types': 3.515.0
- '@aws-sdk/util-endpoints': 3.515.0
- '@aws-sdk/util-user-agent-browser': 3.515.0
- '@aws-sdk/util-user-agent-node': 3.515.0
- '@smithy/config-resolver': 2.2.0
- '@smithy/core': 1.4.2
- '@smithy/fetch-http-handler': 2.5.0
- '@smithy/hash-node': 2.2.0
- '@smithy/invalid-dependency': 2.2.0
- '@smithy/middleware-content-length': 2.2.0
- '@smithy/middleware-endpoint': 2.5.1
- '@smithy/middleware-retry': 2.3.1
- '@smithy/middleware-serde': 2.3.0
- '@smithy/middleware-stack': 2.2.0
- '@smithy/node-config-provider': 2.3.0
- '@smithy/node-http-handler': 2.5.0
- '@smithy/protocol-http': 3.3.0
- '@smithy/smithy-client': 2.5.1
- '@smithy/types': 2.12.0
- '@smithy/url-parser': 2.2.0
- '@smithy/util-base64': 2.3.0
- '@smithy/util-body-length-browser': 2.2.0
- '@smithy/util-body-length-node': 2.3.0
- '@smithy/util-defaults-mode-browser': 2.2.1
- '@smithy/util-defaults-mode-node': 2.3.1
- '@smithy/util-endpoints': 1.2.0
- '@smithy/util-middleware': 2.2.0
- '@smithy/util-retry': 2.2.0
- '@smithy/util-utf8': 2.3.0
- fast-xml-parser: 4.2.5
- tslib: 2.6.3
- transitivePeerDependencies:
- - aws-crt
-
- '@aws-sdk/core@3.513.0':
- dependencies:
- '@smithy/core': 1.4.2
- '@smithy/protocol-http': 3.3.0
- '@smithy/signature-v4': 2.3.0
- '@smithy/smithy-client': 2.5.1
- '@smithy/types': 2.12.0
- tslib: 2.6.3
-
- '@aws-sdk/credential-provider-env@3.515.0':
- dependencies:
- '@aws-sdk/types': 3.515.0
- '@smithy/property-provider': 2.2.0
- '@smithy/types': 2.12.0
- tslib: 2.6.3
-
- '@aws-sdk/credential-provider-http@3.515.0':
- dependencies:
- '@aws-sdk/types': 3.515.0
- '@smithy/fetch-http-handler': 2.5.0
- '@smithy/node-http-handler': 2.5.0
- '@smithy/property-provider': 2.2.0
- '@smithy/protocol-http': 3.3.0
- '@smithy/smithy-client': 2.5.1
- '@smithy/types': 2.12.0
- '@smithy/util-stream': 2.2.0
- tslib: 2.6.3
-
- '@aws-sdk/credential-provider-ini@3.515.0(@aws-sdk/credential-provider-node@3.515.0)':
- dependencies:
- '@aws-sdk/client-sts': 3.515.0(@aws-sdk/credential-provider-node@3.515.0)
- '@aws-sdk/credential-provider-env': 3.515.0
- '@aws-sdk/credential-provider-process': 3.515.0
- '@aws-sdk/credential-provider-sso': 3.515.0(@aws-sdk/credential-provider-node@3.515.0)
- '@aws-sdk/credential-provider-web-identity': 3.515.0(@aws-sdk/credential-provider-node@3.515.0)
- '@aws-sdk/types': 3.515.0
- '@smithy/credential-provider-imds': 2.3.0
- '@smithy/property-provider': 2.2.0
- '@smithy/shared-ini-file-loader': 2.4.0
- '@smithy/types': 2.12.0
- tslib: 2.6.3
- transitivePeerDependencies:
- - '@aws-sdk/credential-provider-node'
- - aws-crt
-
- '@aws-sdk/credential-provider-node@3.515.0':
- dependencies:
- '@aws-sdk/credential-provider-env': 3.515.0
- '@aws-sdk/credential-provider-http': 3.515.0
- '@aws-sdk/credential-provider-ini': 3.515.0(@aws-sdk/credential-provider-node@3.515.0)
- '@aws-sdk/credential-provider-process': 3.515.0
- '@aws-sdk/credential-provider-sso': 3.515.0(@aws-sdk/credential-provider-node@3.515.0)
- '@aws-sdk/credential-provider-web-identity': 3.515.0(@aws-sdk/credential-provider-node@3.515.0)
- '@aws-sdk/types': 3.515.0
- '@smithy/credential-provider-imds': 2.3.0
- '@smithy/property-provider': 2.2.0
- '@smithy/shared-ini-file-loader': 2.4.0
- '@smithy/types': 2.12.0
- tslib: 2.6.3
- transitivePeerDependencies:
- - aws-crt
-
- '@aws-sdk/credential-provider-process@3.515.0':
- dependencies:
- '@aws-sdk/types': 3.515.0
- '@smithy/property-provider': 2.2.0
- '@smithy/shared-ini-file-loader': 2.4.0
- '@smithy/types': 2.12.0
- tslib: 2.6.3
-
- '@aws-sdk/credential-provider-sso@3.515.0(@aws-sdk/credential-provider-node@3.515.0)':
- dependencies:
- '@aws-sdk/client-sso': 3.515.0
- '@aws-sdk/token-providers': 3.515.0(@aws-sdk/credential-provider-node@3.515.0)
- '@aws-sdk/types': 3.515.0
- '@smithy/property-provider': 2.2.0
- '@smithy/shared-ini-file-loader': 2.4.0
- '@smithy/types': 2.12.0
- tslib: 2.6.3
- transitivePeerDependencies:
- - '@aws-sdk/credential-provider-node'
- - aws-crt
-
- '@aws-sdk/credential-provider-web-identity@3.515.0(@aws-sdk/credential-provider-node@3.515.0)':
- dependencies:
- '@aws-sdk/client-sts': 3.515.0(@aws-sdk/credential-provider-node@3.515.0)
- '@aws-sdk/types': 3.515.0
- '@smithy/property-provider': 2.2.0
- '@smithy/types': 2.12.0
- tslib: 2.6.3
- transitivePeerDependencies:
- - '@aws-sdk/credential-provider-node'
- - aws-crt
-
- '@aws-sdk/middleware-bucket-endpoint@3.515.0':
- dependencies:
- '@aws-sdk/types': 3.515.0
- '@aws-sdk/util-arn-parser': 3.495.0
- '@smithy/node-config-provider': 2.3.0
- '@smithy/protocol-http': 3.3.0
- '@smithy/types': 2.12.0
- '@smithy/util-config-provider': 2.3.0
- tslib: 2.6.3
-
- '@aws-sdk/middleware-expect-continue@3.515.0':
- dependencies:
- '@aws-sdk/types': 3.515.0
- '@smithy/protocol-http': 3.3.0
- '@smithy/types': 2.12.0
- tslib: 2.6.3
-
- '@aws-sdk/middleware-flexible-checksums@3.515.0':
- dependencies:
- '@aws-crypto/crc32': 3.0.0
- '@aws-crypto/crc32c': 3.0.0
- '@aws-sdk/types': 3.515.0
- '@smithy/is-array-buffer': 2.2.0
- '@smithy/protocol-http': 3.3.0
- '@smithy/types': 2.12.0
- '@smithy/util-utf8': 2.3.0
- tslib: 2.6.3
-
- '@aws-sdk/middleware-host-header@3.515.0':
- dependencies:
- '@aws-sdk/types': 3.515.0
- '@smithy/protocol-http': 3.3.0
- '@smithy/types': 2.12.0
- tslib: 2.6.3
-
- '@aws-sdk/middleware-location-constraint@3.515.0':
- dependencies:
- '@aws-sdk/types': 3.515.0
- '@smithy/types': 2.12.0
- tslib: 2.6.3
-
- '@aws-sdk/middleware-logger@3.515.0':
- dependencies:
- '@aws-sdk/types': 3.515.0
- '@smithy/types': 2.12.0
- tslib: 2.6.3
-
- '@aws-sdk/middleware-recursion-detection@3.515.0':
- dependencies:
- '@aws-sdk/types': 3.515.0
- '@smithy/protocol-http': 3.3.0
- '@smithy/types': 2.12.0
- tslib: 2.6.3
-
- '@aws-sdk/middleware-sdk-s3@3.515.0':
- dependencies:
- '@aws-sdk/types': 3.515.0
- '@aws-sdk/util-arn-parser': 3.495.0
- '@smithy/node-config-provider': 2.3.0
- '@smithy/protocol-http': 3.3.0
- '@smithy/signature-v4': 2.3.0
- '@smithy/smithy-client': 2.5.1
- '@smithy/types': 2.12.0
- '@smithy/util-config-provider': 2.3.0
- tslib: 2.6.3
-
- '@aws-sdk/middleware-signing@3.515.0':
- dependencies:
- '@aws-sdk/types': 3.515.0
- '@smithy/property-provider': 2.2.0
- '@smithy/protocol-http': 3.3.0
- '@smithy/signature-v4': 2.3.0
- '@smithy/types': 2.12.0
- '@smithy/util-middleware': 2.2.0
- tslib: 2.6.3
-
- '@aws-sdk/middleware-ssec@3.515.0':
- dependencies:
- '@aws-sdk/types': 3.515.0
- '@smithy/types': 2.12.0
- tslib: 2.6.3
-
- '@aws-sdk/middleware-user-agent@3.515.0':
- dependencies:
- '@aws-sdk/types': 3.515.0
- '@aws-sdk/util-endpoints': 3.515.0
- '@smithy/protocol-http': 3.3.0
- '@smithy/types': 2.12.0
- tslib: 2.6.3
-
- '@aws-sdk/region-config-resolver@3.515.0':
- dependencies:
- '@aws-sdk/types': 3.515.0
- '@smithy/node-config-provider': 2.3.0
- '@smithy/types': 2.12.0
- '@smithy/util-config-provider': 2.3.0
- '@smithy/util-middleware': 2.2.0
- tslib: 2.6.3
-
- '@aws-sdk/signature-v4-multi-region@3.515.0':
- dependencies:
- '@aws-sdk/middleware-sdk-s3': 3.515.0
- '@aws-sdk/types': 3.515.0
- '@smithy/protocol-http': 3.3.0
- '@smithy/signature-v4': 2.3.0
- '@smithy/types': 2.12.0
- tslib: 2.6.3
-
- '@aws-sdk/token-providers@3.515.0(@aws-sdk/credential-provider-node@3.515.0)':
- dependencies:
- '@aws-sdk/client-sso-oidc': 3.515.0(@aws-sdk/credential-provider-node@3.515.0)
- '@aws-sdk/types': 3.515.0
- '@smithy/property-provider': 2.2.0
- '@smithy/shared-ini-file-loader': 2.4.0
- '@smithy/types': 2.12.0
- tslib: 2.6.3
- transitivePeerDependencies:
- - '@aws-sdk/credential-provider-node'
- - aws-crt
-
- '@aws-sdk/types@3.515.0':
- dependencies:
- '@smithy/types': 2.12.0
- tslib: 2.6.3
-
- '@aws-sdk/util-arn-parser@3.495.0':
- dependencies:
- tslib: 2.6.3
-
- '@aws-sdk/util-endpoints@3.515.0':
- dependencies:
- '@aws-sdk/types': 3.515.0
- '@smithy/types': 2.12.0
- '@smithy/util-endpoints': 1.2.0
- tslib: 2.6.3
-
- '@aws-sdk/util-locate-window@3.568.0':
- dependencies:
- tslib: 2.6.3
-
- '@aws-sdk/util-user-agent-browser@3.515.0':
- dependencies:
- '@aws-sdk/types': 3.515.0
- '@smithy/types': 2.12.0
- bowser: 2.11.0
- tslib: 2.6.3
-
- '@aws-sdk/util-user-agent-node@3.515.0':
- dependencies:
- '@aws-sdk/types': 3.515.0
- '@smithy/node-config-provider': 2.3.0
- '@smithy/types': 2.12.0
- tslib: 2.6.3
-
- '@aws-sdk/util-utf8-browser@3.259.0':
- dependencies:
- tslib: 2.6.3
-
- '@aws-sdk/xml-builder@3.496.0':
- dependencies:
- '@smithy/types': 2.12.0
- tslib: 2.6.3
-
'@babel/code-frame@7.24.7':
dependencies:
'@babel/highlight': 7.24.7
@@ -11669,320 +10795,6 @@ snapshots:
'@sindresorhus/merge-streams@2.3.0': {}
- '@smithy/abort-controller@2.2.0':
- dependencies:
- '@smithy/types': 2.12.0
- tslib: 2.6.3
-
- '@smithy/chunked-blob-reader-native@2.2.0':
- dependencies:
- '@smithy/util-base64': 2.3.0
- tslib: 2.6.3
-
- '@smithy/chunked-blob-reader@2.2.0':
- dependencies:
- tslib: 2.6.3
-
- '@smithy/config-resolver@2.2.0':
- dependencies:
- '@smithy/node-config-provider': 2.3.0
- '@smithy/types': 2.12.0
- '@smithy/util-config-provider': 2.3.0
- '@smithy/util-middleware': 2.2.0
- tslib: 2.6.3
-
- '@smithy/core@1.4.2':
- dependencies:
- '@smithy/middleware-endpoint': 2.5.1
- '@smithy/middleware-retry': 2.3.1
- '@smithy/middleware-serde': 2.3.0
- '@smithy/protocol-http': 3.3.0
- '@smithy/smithy-client': 2.5.1
- '@smithy/types': 2.12.0
- '@smithy/util-middleware': 2.2.0
- tslib: 2.6.3
-
- '@smithy/credential-provider-imds@2.3.0':
- dependencies:
- '@smithy/node-config-provider': 2.3.0
- '@smithy/property-provider': 2.2.0
- '@smithy/types': 2.12.0
- '@smithy/url-parser': 2.2.0
- tslib: 2.6.3
-
- '@smithy/eventstream-codec@2.2.0':
- dependencies:
- '@aws-crypto/crc32': 3.0.0
- '@smithy/types': 2.12.0
- '@smithy/util-hex-encoding': 2.2.0
- tslib: 2.6.3
-
- '@smithy/eventstream-serde-browser@2.2.0':
- dependencies:
- '@smithy/eventstream-serde-universal': 2.2.0
- '@smithy/types': 2.12.0
- tslib: 2.6.3
-
- '@smithy/eventstream-serde-config-resolver@2.2.0':
- dependencies:
- '@smithy/types': 2.12.0
- tslib: 2.6.3
-
- '@smithy/eventstream-serde-node@2.2.0':
- dependencies:
- '@smithy/eventstream-serde-universal': 2.2.0
- '@smithy/types': 2.12.0
- tslib: 2.6.3
-
- '@smithy/eventstream-serde-universal@2.2.0':
- dependencies:
- '@smithy/eventstream-codec': 2.2.0
- '@smithy/types': 2.12.0
- tslib: 2.6.3
-
- '@smithy/fetch-http-handler@2.5.0':
- dependencies:
- '@smithy/protocol-http': 3.3.0
- '@smithy/querystring-builder': 2.2.0
- '@smithy/types': 2.12.0
- '@smithy/util-base64': 2.3.0
- tslib: 2.6.3
-
- '@smithy/hash-blob-browser@2.2.0':
- dependencies:
- '@smithy/chunked-blob-reader': 2.2.0
- '@smithy/chunked-blob-reader-native': 2.2.0
- '@smithy/types': 2.12.0
- tslib: 2.6.3
-
- '@smithy/hash-node@2.2.0':
- dependencies:
- '@smithy/types': 2.12.0
- '@smithy/util-buffer-from': 2.2.0
- '@smithy/util-utf8': 2.3.0
- tslib: 2.6.3
-
- '@smithy/hash-stream-node@2.2.0':
- dependencies:
- '@smithy/types': 2.12.0
- '@smithy/util-utf8': 2.3.0
- tslib: 2.6.3
-
- '@smithy/invalid-dependency@2.2.0':
- dependencies:
- '@smithy/types': 2.12.0
- tslib: 2.6.3
-
- '@smithy/is-array-buffer@2.2.0':
- dependencies:
- tslib: 2.6.3
-
- '@smithy/md5-js@2.2.0':
- dependencies:
- '@smithy/types': 2.12.0
- '@smithy/util-utf8': 2.3.0
- tslib: 2.6.3
-
- '@smithy/middleware-content-length@2.2.0':
- dependencies:
- '@smithy/protocol-http': 3.3.0
- '@smithy/types': 2.12.0
- tslib: 2.6.3
-
- '@smithy/middleware-endpoint@2.5.1':
- dependencies:
- '@smithy/middleware-serde': 2.3.0
- '@smithy/node-config-provider': 2.3.0
- '@smithy/shared-ini-file-loader': 2.4.0
- '@smithy/types': 2.12.0
- '@smithy/url-parser': 2.2.0
- '@smithy/util-middleware': 2.2.0
- tslib: 2.6.3
-
- '@smithy/middleware-retry@2.3.1':
- dependencies:
- '@smithy/node-config-provider': 2.3.0
- '@smithy/protocol-http': 3.3.0
- '@smithy/service-error-classification': 2.1.5
- '@smithy/smithy-client': 2.5.1
- '@smithy/types': 2.12.0
- '@smithy/util-middleware': 2.2.0
- '@smithy/util-retry': 2.2.0
- tslib: 2.6.3
- uuid: 9.0.1
-
- '@smithy/middleware-serde@2.3.0':
- dependencies:
- '@smithy/types': 2.12.0
- tslib: 2.6.3
-
- '@smithy/middleware-stack@2.2.0':
- dependencies:
- '@smithy/types': 2.12.0
- tslib: 2.6.3
-
- '@smithy/node-config-provider@2.3.0':
- dependencies:
- '@smithy/property-provider': 2.2.0
- '@smithy/shared-ini-file-loader': 2.4.0
- '@smithy/types': 2.12.0
- tslib: 2.6.3
-
- '@smithy/node-http-handler@2.5.0':
- dependencies:
- '@smithy/abort-controller': 2.2.0
- '@smithy/protocol-http': 3.3.0
- '@smithy/querystring-builder': 2.2.0
- '@smithy/types': 2.12.0
- tslib: 2.6.3
-
- '@smithy/property-provider@2.2.0':
- dependencies:
- '@smithy/types': 2.12.0
- tslib: 2.6.3
-
- '@smithy/protocol-http@3.3.0':
- dependencies:
- '@smithy/types': 2.12.0
- tslib: 2.6.3
-
- '@smithy/querystring-builder@2.2.0':
- dependencies:
- '@smithy/types': 2.12.0
- '@smithy/util-uri-escape': 2.2.0
- tslib: 2.6.3
-
- '@smithy/querystring-parser@2.2.0':
- dependencies:
- '@smithy/types': 2.12.0
- tslib: 2.6.3
-
- '@smithy/service-error-classification@2.1.5':
- dependencies:
- '@smithy/types': 2.12.0
-
- '@smithy/shared-ini-file-loader@2.4.0':
- dependencies:
- '@smithy/types': 2.12.0
- tslib: 2.6.3
-
- '@smithy/signature-v4@2.3.0':
- dependencies:
- '@smithy/is-array-buffer': 2.2.0
- '@smithy/types': 2.12.0
- '@smithy/util-hex-encoding': 2.2.0
- '@smithy/util-middleware': 2.2.0
- '@smithy/util-uri-escape': 2.2.0
- '@smithy/util-utf8': 2.3.0
- tslib: 2.6.3
-
- '@smithy/smithy-client@2.5.1':
- dependencies:
- '@smithy/middleware-endpoint': 2.5.1
- '@smithy/middleware-stack': 2.2.0
- '@smithy/protocol-http': 3.3.0
- '@smithy/types': 2.12.0
- '@smithy/util-stream': 2.2.0
- tslib: 2.6.3
-
- '@smithy/types@2.12.0':
- dependencies:
- tslib: 2.6.3
-
- '@smithy/url-parser@2.2.0':
- dependencies:
- '@smithy/querystring-parser': 2.2.0
- '@smithy/types': 2.12.0
- tslib: 2.6.3
-
- '@smithy/util-base64@2.3.0':
- dependencies:
- '@smithy/util-buffer-from': 2.2.0
- '@smithy/util-utf8': 2.3.0
- tslib: 2.6.3
-
- '@smithy/util-body-length-browser@2.2.0':
- dependencies:
- tslib: 2.6.3
-
- '@smithy/util-body-length-node@2.3.0':
- dependencies:
- tslib: 2.6.3
-
- '@smithy/util-buffer-from@2.2.0':
- dependencies:
- '@smithy/is-array-buffer': 2.2.0
- tslib: 2.6.3
-
- '@smithy/util-config-provider@2.3.0':
- dependencies:
- tslib: 2.6.3
-
- '@smithy/util-defaults-mode-browser@2.2.1':
- dependencies:
- '@smithy/property-provider': 2.2.0
- '@smithy/smithy-client': 2.5.1
- '@smithy/types': 2.12.0
- bowser: 2.11.0
- tslib: 2.6.3
-
- '@smithy/util-defaults-mode-node@2.3.1':
- dependencies:
- '@smithy/config-resolver': 2.2.0
- '@smithy/credential-provider-imds': 2.3.0
- '@smithy/node-config-provider': 2.3.0
- '@smithy/property-provider': 2.2.0
- '@smithy/smithy-client': 2.5.1
- '@smithy/types': 2.12.0
- tslib: 2.6.3
-
- '@smithy/util-endpoints@1.2.0':
- dependencies:
- '@smithy/node-config-provider': 2.3.0
- '@smithy/types': 2.12.0
- tslib: 2.6.3
-
- '@smithy/util-hex-encoding@2.2.0':
- dependencies:
- tslib: 2.6.3
-
- '@smithy/util-middleware@2.2.0':
- dependencies:
- '@smithy/types': 2.12.0
- tslib: 2.6.3
-
- '@smithy/util-retry@2.2.0':
- dependencies:
- '@smithy/service-error-classification': 2.1.5
- '@smithy/types': 2.12.0
- tslib: 2.6.3
-
- '@smithy/util-stream@2.2.0':
- dependencies:
- '@smithy/fetch-http-handler': 2.5.0
- '@smithy/node-http-handler': 2.5.0
- '@smithy/types': 2.12.0
- '@smithy/util-base64': 2.3.0
- '@smithy/util-buffer-from': 2.2.0
- '@smithy/util-hex-encoding': 2.2.0
- '@smithy/util-utf8': 2.3.0
- tslib: 2.6.3
-
- '@smithy/util-uri-escape@2.2.0':
- dependencies:
- tslib: 2.6.3
-
- '@smithy/util-utf8@2.3.0':
- dependencies:
- '@smithy/util-buffer-from': 2.2.0
- tslib: 2.6.3
-
- '@smithy/util-waiter@2.2.0':
- dependencies:
- '@smithy/abort-controller': 2.2.0
- '@smithy/types': 2.12.0
- tslib: 2.6.3
-
'@swagger-api/apidom-ast@1.0.0-alpha.6':
dependencies:
'@babel/runtime-corejs3': 7.25.0
@@ -12449,7 +11261,7 @@ snapshots:
'@types/docker-modem@3.0.6':
dependencies:
'@types/node': 20.14.10
- '@types/ssh2': 1.15.0
+ '@types/ssh2': 1.15.1
'@types/dockerode@3.3.23':
dependencies:
@@ -12579,7 +11391,7 @@ snapshots:
'@types/node': 20.14.10
'@types/send': 0.17.4
- '@types/ssh2@1.15.0':
+ '@types/ssh2@1.15.1':
dependencies:
'@types/node': 18.19.42
@@ -13118,8 +11930,6 @@ snapshots:
bottleneck@2.19.5: {}
- bowser@2.11.0: {}
-
boxen@7.1.1:
dependencies:
ansi-align: 3.0.1
@@ -14400,10 +13210,6 @@ snapshots:
fast-uri@3.0.1: {}
- fast-xml-parser@4.2.5:
- dependencies:
- strnum: 1.0.5
-
fastq@1.17.1:
dependencies:
reusify: 1.0.4
@@ -17328,8 +16134,6 @@ snapshots:
dependencies:
js-tokens: 9.0.0
- strnum@1.0.5: {}
-
style-mod@4.1.2: {}
style-to-object@0.4.4:
@@ -17645,8 +16449,6 @@ snapshots:
minimist: 1.2.8
strip-bom: 3.0.0
- tslib@1.14.1: {}
-
tslib@2.6.3: {}
tsx@4.15.7: