diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 000000000..16e8e6664 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["biomejs.biome"] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..99357f236 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "editor.formatOnSave": true, + "editor.defaultFormatter": "biomejs.biome", + "editor.codeActionsOnSave": { + "source.fixAll.biome": "explicit", + "source.organizeImports.biome": "explicit" + } +} diff --git a/Dockerfile b/Dockerfile index 4d18a99ab..11310b18e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -58,7 +58,7 @@ RUN curl -sSL https://nixpacks.com/install.sh -o install.sh \ && pnpm install -g tsx # Install Railpack -ARG RAILPACK_VERSION=0.0.64 +ARG RAILPACK_VERSION=0.2.2 RUN curl -sSL https://railpack.com/install.sh | bash # Install buildpacks diff --git a/README.md b/README.md index bd27474e0..fb2ee82a5 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@
- Dokploy - Open Source Alternative to Vercel, Heroku and Netlify. + Dokploy - Open Source Alternative to Vercel, Heroku and Netlify.

@@ -13,7 +13,7 @@ Dokploy is a free, self-hostable Platform as a Service (PaaS) that simplifies the deployment and management of applications and databases. -### Features +## ✨ Features Dokploy includes multiple features to make your life easier. @@ -43,7 +43,7 @@ curl -sSL https://dokploy.com/install.sh | sh For detailed documentation, visit [docs.dokploy.com](https://docs.dokploy.com). -## Sponsors +## ♥️ Sponsors 🙏 We're deeply grateful to all our sponsors who make Dokploy possible! Your support helps cover the costs of hosting, testing, and developing new features. @@ -95,7 +95,6 @@ For detailed documentation, visit [docs.dokploy.com](https://docs.dokploy.com). ### Community Backers 🤝 - #### Organizations: [Sponsors on Open Collective](https://opencollective.com/dokploy) @@ -107,15 +106,15 @@ For detailed documentation, visit [docs.dokploy.com](https://docs.dokploy.com). ### Contributors 🤝 - + Contributors -## Video Tutorial +## 📺 Video Tutorial Watch the video -## Contributing +## 🤝 Contributing Check out the [Contributing Guide](CONTRIBUTING.md) for more information. diff --git a/apps/dokploy/__test__/compose/compose.test.ts b/apps/dokploy/__test__/compose/compose.test.ts index 9d4ba20f5..69d3a5212 100644 --- a/apps/dokploy/__test__/compose/compose.test.ts +++ b/apps/dokploy/__test__/compose/compose.test.ts @@ -1,5 +1,5 @@ -import { addSuffixToAllProperties } from "@dokploy/server"; import type { ComposeSpecification } from "@dokploy/server"; +import { addSuffixToAllProperties } from "@dokploy/server"; import { load } from "js-yaml"; import { expect, test } from "vitest"; diff --git a/apps/dokploy/__test__/compose/config/config-root.test.ts b/apps/dokploy/__test__/compose/config/config-root.test.ts index 4b40c073e..668e17902 100644 --- a/apps/dokploy/__test__/compose/config/config-root.test.ts +++ b/apps/dokploy/__test__/compose/config/config-root.test.ts @@ -1,6 +1,5 @@ -import { generateRandomHash } from "@dokploy/server"; -import { addSuffixToConfigsRoot } from "@dokploy/server"; import type { ComposeSpecification } from "@dokploy/server"; +import { addSuffixToConfigsRoot, generateRandomHash } from "@dokploy/server"; import { load } from "js-yaml"; import { expect, test } from "vitest"; diff --git a/apps/dokploy/__test__/compose/config/config-service.test.ts b/apps/dokploy/__test__/compose/config/config-service.test.ts index de014eb5e..246872f09 100644 --- a/apps/dokploy/__test__/compose/config/config-service.test.ts +++ b/apps/dokploy/__test__/compose/config/config-service.test.ts @@ -1,6 +1,8 @@ -import { generateRandomHash } from "@dokploy/server"; -import { addSuffixToConfigsInServices } from "@dokploy/server"; import type { ComposeSpecification } from "@dokploy/server"; +import { + addSuffixToConfigsInServices, + generateRandomHash, +} from "@dokploy/server"; import { load } from "js-yaml"; import { expect, test } from "vitest"; diff --git a/apps/dokploy/__test__/compose/config/config.test.ts b/apps/dokploy/__test__/compose/config/config.test.ts index aed3350f5..2d5feeb9a 100644 --- a/apps/dokploy/__test__/compose/config/config.test.ts +++ b/apps/dokploy/__test__/compose/config/config.test.ts @@ -1,6 +1,5 @@ -import { generateRandomHash } from "@dokploy/server"; -import { addSuffixToAllConfigs } from "@dokploy/server"; import type { ComposeSpecification } from "@dokploy/server"; +import { addSuffixToAllConfigs, generateRandomHash } from "@dokploy/server"; import { load } from "js-yaml"; import { expect, test } from "vitest"; diff --git a/apps/dokploy/__test__/compose/domain/labels.test.ts b/apps/dokploy/__test__/compose/domain/labels.test.ts index 172bff2af..9a75e0a84 100644 --- a/apps/dokploy/__test__/compose/domain/labels.test.ts +++ b/apps/dokploy/__test__/compose/domain/labels.test.ts @@ -108,4 +108,136 @@ describe("createDomainLabels", () => { "traefik.http.services.test-app-1-web.loadbalancer.server.port=3000", ); }); + + it("should add stripPath middleware when stripPath is enabled", async () => { + const stripPathDomain = { + ...baseDomain, + path: "/api", + stripPath: true, + }; + const labels = await createDomainLabels(appName, stripPathDomain, "web"); + + expect(labels).toContain( + "traefik.http.middlewares.stripprefix-test-app-1.stripprefix.prefixes=/api", + ); + expect(labels).toContain( + "traefik.http.routers.test-app-1-web.middlewares=stripprefix-test-app-1", + ); + }); + + it("should add internalPath middleware when internalPath is set", async () => { + const internalPathDomain = { + ...baseDomain, + internalPath: "/hello", + }; + const webLabels = await createDomainLabels( + appName, + internalPathDomain, + "web", + ); + const websecureLabels = await createDomainLabels( + appName, + internalPathDomain, + "websecure", + ); + + // Middleware definition should only appear in web entrypoint + expect(webLabels).toContain( + "traefik.http.middlewares.addprefix-test-app-1.addprefix.prefix=/hello", + ); + expect(websecureLabels).not.toContain( + "traefik.http.middlewares.addprefix-test-app-1.addprefix.prefix=/hello", + ); + + // Both routers should reference the middleware + expect(webLabels).toContain( + "traefik.http.routers.test-app-1-web.middlewares=addprefix-test-app-1", + ); + expect(websecureLabels).toContain( + "traefik.http.routers.test-app-1-websecure.middlewares=addprefix-test-app-1", + ); + }); + + it("should combine HTTPS redirect with internalPath middleware in correct order", async () => { + const combinedDomain = { + ...baseDomain, + https: true, + internalPath: "/hello", + }; + const webLabels = await createDomainLabels(appName, combinedDomain, "web"); + const websecureLabels = await createDomainLabels( + appName, + combinedDomain, + "websecure", + ); + + // Web entrypoint should have both middlewares with redirect first + expect(webLabels).toContain( + "traefik.http.routers.test-app-1-web.middlewares=redirect-to-https@file,addprefix-test-app-1", + ); + + // Websecure should only have the addprefix middleware + expect(websecureLabels).toContain( + "traefik.http.routers.test-app-1-websecure.middlewares=addprefix-test-app-1", + ); + + // Middleware definition should only appear once (in web) + expect(webLabels).toContain( + "traefik.http.middlewares.addprefix-test-app-1.addprefix.prefix=/hello", + ); + expect(websecureLabels).not.toContain( + "traefik.http.middlewares.addprefix-test-app-1.addprefix.prefix=/hello", + ); + }); + + it("should combine all middlewares in correct order", async () => { + const fullDomain = { + ...baseDomain, + https: true, + path: "/api", + stripPath: true, + internalPath: "/hello", + }; + const webLabels = await createDomainLabels(appName, fullDomain, "web"); + + // Should have all middleware definitions (only in web) + expect(webLabels).toContain( + "traefik.http.middlewares.stripprefix-test-app-1.stripprefix.prefixes=/api", + ); + expect(webLabels).toContain( + "traefik.http.middlewares.addprefix-test-app-1.addprefix.prefix=/hello", + ); + + // Should have middlewares in correct order: redirect, stripprefix, addprefix + expect(webLabels).toContain( + "traefik.http.routers.test-app-1-web.middlewares=redirect-to-https@file,stripprefix-test-app-1,addprefix-test-app-1", + ); + }); + + it("should not add middleware definitions for websecure entrypoint", async () => { + const internalPathDomain = { + ...baseDomain, + path: "/api", + stripPath: true, + internalPath: "/hello", + }; + const websecureLabels = await createDomainLabels( + appName, + internalPathDomain, + "websecure", + ); + + // Should not contain any middleware definitions + expect(websecureLabels).not.toContain( + "traefik.http.middlewares.stripprefix-test-app-1.stripprefix.prefixes=/api", + ); + expect(websecureLabels).not.toContain( + "traefik.http.middlewares.addprefix-test-app-1.addprefix.prefix=/hello", + ); + + // But should reference the middlewares + expect(websecureLabels).toContain( + "traefik.http.routers.test-app-1-websecure.middlewares=stripprefix-test-app-1,addprefix-test-app-1", + ); + }); }); diff --git a/apps/dokploy/__test__/compose/network/network-root.test.ts b/apps/dokploy/__test__/compose/network/network-root.test.ts index 980502fff..c55f6fa86 100644 --- a/apps/dokploy/__test__/compose/network/network-root.test.ts +++ b/apps/dokploy/__test__/compose/network/network-root.test.ts @@ -1,6 +1,5 @@ -import { generateRandomHash } from "@dokploy/server"; -import { addSuffixToNetworksRoot } from "@dokploy/server"; import type { ComposeSpecification } from "@dokploy/server"; +import { addSuffixToNetworksRoot, generateRandomHash } from "@dokploy/server"; import { load } from "js-yaml"; import { expect, test } from "vitest"; diff --git a/apps/dokploy/__test__/compose/network/network-service.test.ts b/apps/dokploy/__test__/compose/network/network-service.test.ts index ee07d9de9..3cf46d4ab 100644 --- a/apps/dokploy/__test__/compose/network/network-service.test.ts +++ b/apps/dokploy/__test__/compose/network/network-service.test.ts @@ -1,6 +1,8 @@ -import { generateRandomHash } from "@dokploy/server"; -import { addSuffixToServiceNetworks } from "@dokploy/server"; import type { ComposeSpecification } from "@dokploy/server"; +import { + addSuffixToServiceNetworks, + generateRandomHash, +} from "@dokploy/server"; import { load } from "js-yaml"; import { expect, test } from "vitest"; diff --git a/apps/dokploy/__test__/compose/network/network.test.ts b/apps/dokploy/__test__/compose/network/network.test.ts index 39cf03958..7ba1c6a83 100644 --- a/apps/dokploy/__test__/compose/network/network.test.ts +++ b/apps/dokploy/__test__/compose/network/network.test.ts @@ -1,10 +1,10 @@ -import { generateRandomHash } from "@dokploy/server"; +import type { ComposeSpecification } from "@dokploy/server"; import { addSuffixToAllNetworks, + addSuffixToNetworksRoot, addSuffixToServiceNetworks, + generateRandomHash, } from "@dokploy/server"; -import { addSuffixToNetworksRoot } from "@dokploy/server"; -import type { ComposeSpecification } from "@dokploy/server"; import { load } from "js-yaml"; import { expect, test } from "vitest"; diff --git a/apps/dokploy/__test__/compose/secrets/secret-root.test.ts b/apps/dokploy/__test__/compose/secrets/secret-root.test.ts index 1b1898c59..b8cef56e4 100644 --- a/apps/dokploy/__test__/compose/secrets/secret-root.test.ts +++ b/apps/dokploy/__test__/compose/secrets/secret-root.test.ts @@ -1,6 +1,5 @@ -import { generateRandomHash } from "@dokploy/server"; -import { addSuffixToSecretsRoot } from "@dokploy/server"; import type { ComposeSpecification } from "@dokploy/server"; +import { addSuffixToSecretsRoot, generateRandomHash } from "@dokploy/server"; import { load } from "js-yaml"; import { expect, test } from "vitest"; diff --git a/apps/dokploy/__test__/compose/secrets/secret-services.test.ts b/apps/dokploy/__test__/compose/secrets/secret-services.test.ts index 5206bbbaf..e12f611d0 100644 --- a/apps/dokploy/__test__/compose/secrets/secret-services.test.ts +++ b/apps/dokploy/__test__/compose/secrets/secret-services.test.ts @@ -1,6 +1,8 @@ -import { generateRandomHash } from "@dokploy/server"; -import { addSuffixToSecretsInServices } from "@dokploy/server"; import type { ComposeSpecification } from "@dokploy/server"; +import { + addSuffixToSecretsInServices, + generateRandomHash, +} from "@dokploy/server"; import { load } from "js-yaml"; import { expect, test } from "vitest"; diff --git a/apps/dokploy/__test__/compose/secrets/secret.test.ts b/apps/dokploy/__test__/compose/secrets/secret.test.ts index d874dc5e7..3ff524ad7 100644 --- a/apps/dokploy/__test__/compose/secrets/secret.test.ts +++ b/apps/dokploy/__test__/compose/secrets/secret.test.ts @@ -1,5 +1,5 @@ -import { addSuffixToAllSecrets } from "@dokploy/server"; import type { ComposeSpecification } from "@dokploy/server"; +import { addSuffixToAllSecrets } from "@dokploy/server"; import { load } from "js-yaml"; import { expect, test } from "vitest"; diff --git a/apps/dokploy/__test__/compose/service/service-container-name.test.ts b/apps/dokploy/__test__/compose/service/service-container-name.test.ts index bcb51fd04..6ad45c588 100644 --- a/apps/dokploy/__test__/compose/service/service-container-name.test.ts +++ b/apps/dokploy/__test__/compose/service/service-container-name.test.ts @@ -1,6 +1,5 @@ -import { generateRandomHash } from "@dokploy/server"; -import { addSuffixToServiceNames } from "@dokploy/server"; import type { ComposeSpecification } from "@dokploy/server"; +import { addSuffixToServiceNames, generateRandomHash } from "@dokploy/server"; import { load } from "js-yaml"; import { expect, test } from "vitest"; diff --git a/apps/dokploy/__test__/compose/service/service-depends-on.test.ts b/apps/dokploy/__test__/compose/service/service-depends-on.test.ts index b27414be5..14a5789c4 100644 --- a/apps/dokploy/__test__/compose/service/service-depends-on.test.ts +++ b/apps/dokploy/__test__/compose/service/service-depends-on.test.ts @@ -1,6 +1,5 @@ -import { generateRandomHash } from "@dokploy/server"; -import { addSuffixToServiceNames } from "@dokploy/server"; import type { ComposeSpecification } from "@dokploy/server"; +import { addSuffixToServiceNames, generateRandomHash } from "@dokploy/server"; import { load } from "js-yaml"; import { expect, test } from "vitest"; diff --git a/apps/dokploy/__test__/compose/service/service-extends.test.ts b/apps/dokploy/__test__/compose/service/service-extends.test.ts index 8309a32fd..0b7e92c53 100644 --- a/apps/dokploy/__test__/compose/service/service-extends.test.ts +++ b/apps/dokploy/__test__/compose/service/service-extends.test.ts @@ -1,6 +1,5 @@ -import { generateRandomHash } from "@dokploy/server"; -import { addSuffixToServiceNames } from "@dokploy/server"; import type { ComposeSpecification } from "@dokploy/server"; +import { addSuffixToServiceNames, generateRandomHash } from "@dokploy/server"; import { load } from "js-yaml"; import { expect, test } from "vitest"; diff --git a/apps/dokploy/__test__/compose/service/service-links.test.ts b/apps/dokploy/__test__/compose/service/service-links.test.ts index 5f9b01ab2..6c8cde39e 100644 --- a/apps/dokploy/__test__/compose/service/service-links.test.ts +++ b/apps/dokploy/__test__/compose/service/service-links.test.ts @@ -1,6 +1,5 @@ -import { generateRandomHash } from "@dokploy/server"; -import { addSuffixToServiceNames } from "@dokploy/server"; import type { ComposeSpecification } from "@dokploy/server"; +import { addSuffixToServiceNames, generateRandomHash } from "@dokploy/server"; import { load } from "js-yaml"; import { expect, test } from "vitest"; diff --git a/apps/dokploy/__test__/compose/service/service-names.test.ts b/apps/dokploy/__test__/compose/service/service-names.test.ts index 936a32ecc..c65299b03 100644 --- a/apps/dokploy/__test__/compose/service/service-names.test.ts +++ b/apps/dokploy/__test__/compose/service/service-names.test.ts @@ -1,6 +1,5 @@ -import { generateRandomHash } from "@dokploy/server"; -import { addSuffixToServiceNames } from "@dokploy/server"; import type { ComposeSpecification } from "@dokploy/server"; +import { addSuffixToServiceNames, generateRandomHash } from "@dokploy/server"; import { load } from "js-yaml"; import { expect, test } from "vitest"; diff --git a/apps/dokploy/__test__/compose/service/service.test.ts b/apps/dokploy/__test__/compose/service/service.test.ts index c6050f75a..38895e073 100644 --- a/apps/dokploy/__test__/compose/service/service.test.ts +++ b/apps/dokploy/__test__/compose/service/service.test.ts @@ -1,8 +1,8 @@ +import type { ComposeSpecification } from "@dokploy/server"; import { addSuffixToAllServiceNames, addSuffixToServiceNames, } from "@dokploy/server"; -import type { ComposeSpecification } from "@dokploy/server"; import { load } from "js-yaml"; import { expect, test } from "vitest"; diff --git a/apps/dokploy/__test__/compose/service/sevice-volumes-from.test.ts b/apps/dokploy/__test__/compose/service/sevice-volumes-from.test.ts index 8066a6dd7..8aa8296e8 100644 --- a/apps/dokploy/__test__/compose/service/sevice-volumes-from.test.ts +++ b/apps/dokploy/__test__/compose/service/sevice-volumes-from.test.ts @@ -1,6 +1,5 @@ -import { generateRandomHash } from "@dokploy/server"; -import { addSuffixToServiceNames } from "@dokploy/server"; import type { ComposeSpecification } from "@dokploy/server"; +import { addSuffixToServiceNames, generateRandomHash } from "@dokploy/server"; import { load } from "js-yaml"; import { expect, test } from "vitest"; diff --git a/apps/dokploy/__test__/compose/volume/volume-2.test.ts b/apps/dokploy/__test__/compose/volume/volume-2.test.ts index 61cba82d3..6aa9d01d3 100644 --- a/apps/dokploy/__test__/compose/volume/volume-2.test.ts +++ b/apps/dokploy/__test__/compose/volume/volume-2.test.ts @@ -1,6 +1,9 @@ -import { generateRandomHash } from "@dokploy/server"; -import { addSuffixToAllVolumes, addSuffixToVolumesRoot } from "@dokploy/server"; import type { ComposeSpecification } from "@dokploy/server"; +import { + addSuffixToAllVolumes, + addSuffixToVolumesRoot, + generateRandomHash, +} from "@dokploy/server"; import { load } from "js-yaml"; import { expect, test } from "vitest"; diff --git a/apps/dokploy/__test__/compose/volume/volume-root.test.ts b/apps/dokploy/__test__/compose/volume/volume-root.test.ts index d91cb64d3..80db1f0cc 100644 --- a/apps/dokploy/__test__/compose/volume/volume-root.test.ts +++ b/apps/dokploy/__test__/compose/volume/volume-root.test.ts @@ -1,6 +1,5 @@ -import { generateRandomHash } from "@dokploy/server"; -import { addSuffixToVolumesRoot } from "@dokploy/server"; import type { ComposeSpecification } from "@dokploy/server"; +import { addSuffixToVolumesRoot, generateRandomHash } from "@dokploy/server"; import { load } from "js-yaml"; import { expect, test } from "vitest"; diff --git a/apps/dokploy/__test__/compose/volume/volume-services.test.ts b/apps/dokploy/__test__/compose/volume/volume-services.test.ts index 04a1a45ae..0e9cb018f 100644 --- a/apps/dokploy/__test__/compose/volume/volume-services.test.ts +++ b/apps/dokploy/__test__/compose/volume/volume-services.test.ts @@ -1,6 +1,8 @@ -import { generateRandomHash } from "@dokploy/server"; -import { addSuffixToVolumesInServices } from "@dokploy/server"; import type { ComposeSpecification } from "@dokploy/server"; +import { + addSuffixToVolumesInServices, + generateRandomHash, +} from "@dokploy/server"; import { load } from "js-yaml"; import { expect, test } from "vitest"; diff --git a/apps/dokploy/__test__/compose/volume/volume.test.ts b/apps/dokploy/__test__/compose/volume/volume.test.ts index 6c4344762..6f8e76708 100644 --- a/apps/dokploy/__test__/compose/volume/volume.test.ts +++ b/apps/dokploy/__test__/compose/volume/volume.test.ts @@ -1,5 +1,5 @@ -import { addSuffixToAllVolumes } from "@dokploy/server"; import type { ComposeSpecification } from "@dokploy/server"; +import { addSuffixToAllVolumes } from "@dokploy/server"; import { load } from "js-yaml"; import { expect, test } from "vitest"; diff --git a/apps/dokploy/__test__/deploy/github.test.ts b/apps/dokploy/__test__/deploy/github.test.ts index 18d7619ab..03805b08d 100644 --- a/apps/dokploy/__test__/deploy/github.test.ts +++ b/apps/dokploy/__test__/deploy/github.test.ts @@ -1,5 +1,5 @@ -import { extractCommitMessage } from "@/pages/api/deploy/[refreshToken]"; import { describe, expect, it } from "vitest"; +import { extractCommitMessage } from "@/pages/api/deploy/[refreshToken]"; describe("GitHub Webhook Skip CI", () => { const mockGithubHeaders = { diff --git a/apps/dokploy/__test__/drop/drop.test.test.ts b/apps/dokploy/__test__/drop/drop.test.ts similarity index 99% rename from apps/dokploy/__test__/drop/drop.test.test.ts rename to apps/dokploy/__test__/drop/drop.test.ts index 37a337adc..496949481 100644 --- a/apps/dokploy/__test__/drop/drop.test.test.ts +++ b/apps/dokploy/__test__/drop/drop.test.ts @@ -1,12 +1,12 @@ import fs from "node:fs/promises"; import path from "node:path"; -import { paths } from "@dokploy/server/constants"; -const { APPLICATIONS_PATH } = paths(); import type { ApplicationNested } from "@dokploy/server"; import { unzipDrop } from "@dokploy/server"; +import { paths } from "@dokploy/server/constants"; import AdmZip from "adm-zip"; import { afterAll, beforeAll, describe, expect, it, vi } from "vitest"; +const { APPLICATIONS_PATH } = paths(); vi.mock("@dokploy/server/constants", async (importOriginal) => { const actual = await importOriginal(); return { @@ -143,7 +143,7 @@ describe("unzipDrop using real zip files", () => { const outputPath = path.join(APPLICATIONS_PATH, baseApp.appName, "code"); const zip = new AdmZip("./__test__/drop/zips/single-file.zip"); console.log(`Output Path: ${outputPath}`); - const zipBuffer = zip.toBuffer(); + const zipBuffer = zip.toBuffer() as Buffer; const file = new File([zipBuffer], "single.zip"); await unzipDrop(file, baseApp); const files = await fs.readdir(outputPath, { withFileTypes: true }); diff --git a/apps/dokploy/__test__/requests/request.test.ts b/apps/dokploy/__test__/requests/request.test.ts index 997bd9ec5..53ca8d777 100644 --- a/apps/dokploy/__test__/requests/request.test.ts +++ b/apps/dokploy/__test__/requests/request.test.ts @@ -1,5 +1,6 @@ import { parseRawConfig, processLogs } from "@dokploy/server"; import { describe, expect, it } from "vitest"; + const sampleLogEntry = `{"ClientAddr":"172.19.0.1:56732","ClientHost":"172.19.0.1","ClientPort":"56732","ClientUsername":"-","DownstreamContentSize":0,"DownstreamStatus":304,"Duration":14729375,"OriginContentSize":0,"OriginDuration":14051833,"OriginStatus":304,"Overhead":677542,"RequestAddr":"s222-umami-c381af.traefik.me","RequestContentSize":0,"RequestCount":122,"RequestHost":"s222-umami-c381af.traefik.me","RequestMethod":"GET","RequestPath":"/dashboard?_rsc=1rugv","RequestPort":"-","RequestProtocol":"HTTP/1.1","RequestScheme":"http","RetryAttempts":0,"RouterName":"s222-umami-60e104-47-web@docker","ServiceAddr":"10.0.1.15:3000","ServiceName":"s222-umami-60e104-47-web@docker","ServiceURL":{"Scheme":"http","Opaque":"","User":null,"Host":"10.0.1.15:3000","Path":"","RawPath":"","ForceQuery":false,"RawQuery":"","Fragment":"","RawFragment":""},"StartLocal":"2024-08-25T04:34:37.306691884Z","StartUTC":"2024-08-25T04:34:37.306691884Z","entryPointName":"web","level":"info","msg":"","time":"2024-08-25T04:34:37Z"}`; describe("processLogs", () => { diff --git a/apps/dokploy/__test__/traefik/traefik.test.ts b/apps/dokploy/__test__/traefik/traefik.test.ts index bf7beb03d..bcf42ad54 100644 --- a/apps/dokploy/__test__/traefik/traefik.test.ts +++ b/apps/dokploy/__test__/traefik/traefik.test.ts @@ -1,6 +1,4 @@ -import type { Domain } from "@dokploy/server"; -import type { Redirect } from "@dokploy/server"; -import type { ApplicationNested } from "@dokploy/server"; +import type { ApplicationNested, Domain, Redirect } from "@dokploy/server"; import { createRouterConfig } from "@dokploy/server"; import { expect, test } from "vitest"; diff --git a/apps/dokploy/components/dashboard/application/advanced/security/handle-security.tsx b/apps/dokploy/components/dashboard/application/advanced/security/handle-security.tsx index 1808f7873..2fc50c7c7 100644 --- a/apps/dokploy/components/dashboard/application/advanced/security/handle-security.tsx +++ b/apps/dokploy/components/dashboard/application/advanced/security/handle-security.tsx @@ -151,7 +151,7 @@ export const HandleSecurity = ({ Password - + diff --git a/apps/dokploy/components/dashboard/application/advanced/security/show-security.tsx b/apps/dokploy/components/dashboard/application/advanced/security/show-security.tsx index 92439f511..552a186a1 100644 --- a/apps/dokploy/components/dashboard/application/advanced/security/show-security.tsx +++ b/apps/dokploy/components/dashboard/application/advanced/security/show-security.tsx @@ -7,6 +7,9 @@ import { CardHeader, CardTitle, } from "@/components/ui/card"; +import { Label } from "@/components/ui/label"; +import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input"; +import { Input } from "@/components/ui/input"; import { api } from "@/utils/api"; import { LockKeyhole, Trash2 } from "lucide-react"; import { toast } from "sonner"; @@ -58,19 +61,18 @@ export const ShowSecurity = ({ applicationId }: Props) => {
{data?.security.map((security) => (
-
-
-
- Username - - {security.username} - +
+
+
+ +
-
- Password - - {security.password} - +
+ +
diff --git a/apps/dokploy/components/dashboard/application/advanced/volumes/show-volumes.tsx b/apps/dokploy/components/dashboard/application/advanced/volumes/show-volumes.tsx index 2a2d2c032..d3803c42a 100644 --- a/apps/dokploy/components/dashboard/application/advanced/volumes/show-volumes.tsx +++ b/apps/dokploy/components/dashboard/application/advanced/volumes/show-volumes.tsx @@ -1,3 +1,5 @@ +import { Package, Trash2 } from "lucide-react"; +import { toast } from "sonner"; import { AlertBlock } from "@/components/shared/alert-block"; import { DialogAction } from "@/components/shared/dialog-action"; import { Button } from "@/components/ui/button"; @@ -9,11 +11,10 @@ import { CardTitle, } from "@/components/ui/card"; import { api } from "@/utils/api"; -import { Package, Trash2 } from "lucide-react"; -import { toast } from "sonner"; import type { ServiceType } from "../show-resources"; import { AddVolumes } from "./add-volumes"; import { UpdateVolume } from "./update-volume"; + interface Props { id: string; type: ServiceType | "compose"; @@ -80,7 +81,7 @@ export const ShowVolumes = ({ id, type }: Props) => { className="flex w-full flex-col sm:flex-row sm:items-center justify-between gap-4 sm:gap-10 border rounded-lg p-4" > {/* */} -
+
Mount Type @@ -112,21 +113,21 @@ export const ShowVolumes = ({ id, type }: Props) => {
)} - {mount.type === "file" ? ( + {mount.type === "file" && (
File Path {mount.filePath}
- ) : ( -
- Mount Path - - {mount.mountPath} - -
)} + +
+ Mount Path + + {mount.mountPath} + +
{ const [isOpen, setIsOpen] = useState(false); const [cacheType, setCacheType] = useState("cache"); + const [isManualInput, setIsManualInput] = useState(false); const utils = api.useUtils(); const { data, refetch } = api.domain.one.useQuery( @@ -325,46 +325,126 @@ export const AddDomain = ({ id, type, domainId = "", children }: Props) => { Service Name
- + ) : ( + + + + )} + {!isManualInput && ( + <> + + + + + + +

+ Fetch: Will clone the repository and + load the services +

+
+
+
+ + + + + + +

+ Cache: If you previously deployed this + compose, it will read the services + from the last deployment/fetch from + the repository +

+
+
+
+ + )} { className="max-w-[10rem]" >

- Fetch: Will clone the repository and load - the services -

-
-
-
- - - - - - -

- Cache: If you previously deployed this - compose, it will read the services from - the last deployment/fetch from the - repository + {isManualInput + ? "Switch to service selection" + : "Enter service name manually"}

diff --git a/apps/dokploy/components/dashboard/project/add-application.tsx b/apps/dokploy/components/dashboard/project/add-application.tsx index 9ed31464a..137f75a51 100644 --- a/apps/dokploy/components/dashboard/project/add-application.tsx +++ b/apps/dokploy/components/dashboard/project/add-application.tsx @@ -1,3 +1,9 @@ +import { zodResolver } from "@hookform/resolvers/zod"; +import { Folder, HelpCircle } from "lucide-react"; +import { useState } from "react"; +import { useForm } from "react-hook-form"; +import { toast } from "sonner"; +import { z } from "zod"; import { AlertBlock } from "@/components/shared/alert-block"; import { Button } from "@/components/ui/button"; import { @@ -37,12 +43,6 @@ import { } from "@/components/ui/tooltip"; import { slugify } from "@/lib/slug"; import { api } from "@/utils/api"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { Folder, HelpCircle } from "lucide-react"; -import { useState } from "react"; -import { useForm } from "react-hook-form"; -import { toast } from "sonner"; -import { z } from "zod"; const AddTemplateSchema = z.object({ name: z.string().min(1, { @@ -75,6 +75,8 @@ export const AddApplication = ({ projectId, projectName }: Props) => { const slug = slugify(projectName); const { data: servers } = api.server.withSSHKey.useQuery(); + const hasServers = servers && servers.length > 0; + const { mutateAsync, isLoading, error, isError } = api.application.create.useMutation(); @@ -155,68 +157,84 @@ export const AddApplication = ({ projectId, projectName }: Props) => { )} /> - ( - - - - - - Select a Server {!isCloud ? "(Optional)" : ""} - - - - - - If no server is selected, the application will be - deployed on the server where the user is logged in. - - - - + {hasServers && ( + ( + + + + + + Select a Server {!isCloud ? "(Optional)" : ""} + + + + + + If no server is selected, the application will be + deployed on the server where the user is logged in. + + + + - + + + + + + {servers?.map((server) => ( + + + {server.name} + + {server.ipAddress} + - - - ))} - Servers ({servers?.length}) - - - - - - )} - /> + + ))} + Servers ({servers?.length}) + + + + + + )} + /> + )} ( - App Name + + App Name + + + + + + +

+ This will be the name of the Docker Swarm service +

+
+
+
+
diff --git a/apps/dokploy/components/dashboard/project/add-compose.tsx b/apps/dokploy/components/dashboard/project/add-compose.tsx index a60bfdd70..c32e55c16 100644 --- a/apps/dokploy/components/dashboard/project/add-compose.tsx +++ b/apps/dokploy/components/dashboard/project/add-compose.tsx @@ -1,3 +1,9 @@ +import { zodResolver } from "@hookform/resolvers/zod"; +import { CircuitBoard, HelpCircle } from "lucide-react"; +import { useEffect, useState } from "react"; +import { useForm } from "react-hook-form"; +import { toast } from "sonner"; +import { z } from "zod"; import { AlertBlock } from "@/components/shared/alert-block"; import { Button } from "@/components/ui/button"; import { @@ -37,12 +43,6 @@ import { } from "@/components/ui/tooltip"; import { slugify } from "@/lib/slug"; import { api } from "@/utils/api"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { CircuitBoard, HelpCircle } from "lucide-react"; -import { useEffect, useState } from "react"; -import { useForm } from "react-hook-form"; -import { toast } from "sonner"; -import { z } from "zod"; const AddComposeSchema = z.object({ composeType: z.enum(["docker-compose", "stack"]).optional(), @@ -78,6 +78,8 @@ export const AddCompose = ({ projectId, projectName }: Props) => { const { mutateAsync, isLoading, error, isError } = api.compose.create.useMutation(); + const hasServers = servers && servers.length > 0; + const form = useForm({ defaultValues: { name: "", @@ -163,62 +165,64 @@ export const AddCompose = ({ projectId, projectName }: Props) => { )} />
- ( - - - - - - Select a Server {!isCloud ? "(Optional)" : ""} - - - - - - If no server is selected, the application will be - deployed on the server where the user is logged in. - - - - + {hasServers && ( + ( + + + + + + Select a Server {!isCloud ? "(Optional)" : ""} + + + + + + If no server is selected, the application will be + deployed on the server where the user is logged in. + + + + - + + + + + + {servers?.map((server) => ( + + + {server.name} + + {server.ipAddress} + - - - ))} - Servers ({servers?.length}) - - - - - - )} - /> + + ))} + Servers ({servers?.length}) + + + + + + )} + /> + )} { const mariadbMutation = api.mariadb.create.useMutation(); const mysqlMutation = api.mysql.create.useMutation(); + const hasServers = servers && servers.length > 0; + const form = useForm({ defaultValues: { type: "postgres", @@ -374,45 +382,62 @@ export const AddDatabase = ({ projectId, projectName }: Props) => {
)} /> - ( - - Select a Server - - - - )} - /> + {hasServers && ( + ( + + Select a Server + + + + )} + /> + )} ( - App Name + + App Name + + + + + + +

+ This will be the name of the Docker Swarm + service +

+
+
+
+
diff --git a/apps/dokploy/components/dashboard/project/add-template.tsx b/apps/dokploy/components/dashboard/project/add-template.tsx index 53ae30141..5eb994fc4 100644 --- a/apps/dokploy/components/dashboard/project/add-template.tsx +++ b/apps/dokploy/components/dashboard/project/add-template.tsx @@ -1,3 +1,18 @@ +import { + BookText, + CheckIcon, + ChevronsUpDown, + Globe, + HelpCircle, + LayoutGrid, + List, + Loader2, + PuzzleIcon, + SearchIcon, +} from "lucide-react"; +import Link from "next/link"; +import { useEffect, useState } from "react"; +import { toast } from "sonner"; import { GithubIcon } from "@/components/icons/data-tools-icons"; import { AlertBlock } from "@/components/shared/alert-block"; import { @@ -54,21 +69,6 @@ import { } from "@/components/ui/tooltip"; import { cn } from "@/lib/utils"; import { api } from "@/utils/api"; -import { - BookText, - CheckIcon, - ChevronsUpDown, - Globe, - HelpCircle, - LayoutGrid, - List, - Loader2, - PuzzleIcon, - SearchIcon, -} from "lucide-react"; -import Link from "next/link"; -import { useEffect, useState } from "react"; -import { toast } from "sonner"; const TEMPLATE_BASE_URL_KEY = "dokploy_template_base_url"; @@ -137,6 +137,8 @@ export const AddTemplate = ({ projectId, baseUrl }: Props) => { return matchesTags && matchesQuery; }) || []; + const hasServers = servers && servers.length > 0; + return ( @@ -425,60 +427,62 @@ export const AddTemplate = ({ projectId, baseUrl }: Props) => { project. -
- - - - - - - - If no server is selected, the application - will be deployed on the server where the - user is logged in. - - - - + {hasServers && ( +
+ + + + + + + + If no server is selected, the + application will be deployed on the + server where the user is logged in. + + + + - { + setServerId(e); + }} + > + + + + + + {servers?.map((server) => ( + + + {server.name} + + {server.ipAddress} + - - - ))} - - Servers ({servers?.length}) - - - - -
+ + ))} + + Servers ({servers?.length}) + + + + +
+ )} Cancel diff --git a/apps/dokploy/components/dashboard/project/ai/step-one.tsx b/apps/dokploy/components/dashboard/project/ai/step-one.tsx index e2a6795fe..29cf90305 100644 --- a/apps/dokploy/components/dashboard/project/ai/step-one.tsx +++ b/apps/dokploy/components/dashboard/project/ai/step-one.tsx @@ -25,6 +25,7 @@ const examples = [ export const StepOne = ({ setTemplateInfo, templateInfo }: any) => { // Get servers from the API const { data: servers } = api.server.withSSHKey.useQuery(); + const hasServers = servers && servers.length > 0; const handleExampleClick = (example: string) => { setTemplateInfo({ ...templateInfo, userInput: example }); @@ -47,37 +48,39 @@ export const StepOne = ({ setTemplateInfo, templateInfo }: any) => { />
-
- - -
+ {hasServers && ( +
+ + +
+ )}
diff --git a/apps/dokploy/components/dashboard/project/ai/step-two.tsx b/apps/dokploy/components/dashboard/project/ai/step-two.tsx index 7b4deb30e..fc64638f1 100644 --- a/apps/dokploy/components/dashboard/project/ai/step-two.tsx +++ b/apps/dokploy/components/dashboard/project/ai/step-two.tsx @@ -199,7 +199,7 @@ export const StepTwo = ({ templateInfo, setTemplateInfo }: StepProps) => {

Generating template suggestions based on your input...

-
{templateInfo.userInput}
+
{templateInfo.userInput}
); } diff --git a/apps/dokploy/components/dashboard/settings/git/github/add-github-provider.tsx b/apps/dokploy/components/dashboard/settings/git/github/add-github-provider.tsx index af7d58544..83a4c8b42 100644 --- a/apps/dokploy/components/dashboard/settings/git/github/add-github-provider.tsx +++ b/apps/dokploy/components/dashboard/settings/git/github/add-github-provider.tsx @@ -24,12 +24,14 @@ export const AddGithubProvider = () => { const [isOrganization, setIsOrganization] = useState(false); const [organizationName, setOrganization] = useState(""); + const randomString = () => Math.random().toString(36).slice(2, 8); + useEffect(() => { const url = document.location.origin; const manifest = JSON.stringify( { redirect_url: `${origin}/api/providers/github/setup?organizationId=${activeOrganization?.id}&userId=${session?.user?.id}`, - name: `Dokploy-${format(new Date(), "yyyy-MM-dd")}`, + name: `Dokploy-${format(new Date(), "yyyy-MM-dd")}-${randomString()}`, url: origin, hook_attributes: { url: `${url}/api/deploy/github`, diff --git a/apps/dokploy/components/dashboard/settings/git/show-git-providers.tsx b/apps/dokploy/components/dashboard/settings/git/show-git-providers.tsx index 05273ca2a..eda9d8afb 100644 --- a/apps/dokploy/components/dashboard/settings/git/show-git-providers.tsx +++ b/apps/dokploy/components/dashboard/settings/git/show-git-providers.tsx @@ -33,6 +33,7 @@ import { AddGithubProvider } from "./github/add-github-provider"; import { EditGithubProvider } from "./github/edit-github-provider"; import { AddGitlabProvider } from "./gitlab/add-gitlab-provider"; import { EditGitlabProvider } from "./gitlab/edit-gitlab-provider"; +import { Badge } from "@/components/ui/badge"; export const ShowGitProviders = () => { const { data, isLoading, refetch } = api.gitProvider.getAll.useQuery(); @@ -158,7 +159,13 @@ export const ShowGitProviders = () => {
{!haveGithubRequirements && isGithub && ( -
+
+ + Action Required + {
)} {!haveGitlabRequirements && isGitlab && ( -
+
+ + Action Required + { image: data?.user?.image || "", currentPassword: "", allowImpersonation: data?.user?.allowImpersonation || false, + name: data?.user?.name || "", }, resolver: zodResolver(profileSchema), }); @@ -97,6 +100,7 @@ export const ProfileForm = () => { image: data?.user?.image || "", currentPassword: form.getValues("currentPassword") || "", allowImpersonation: data?.user?.allowImpersonation, + name: data?.user?.name || "", }, { keepValues: true, @@ -119,6 +123,7 @@ export const ProfileForm = () => { image: values.image, currentPassword: values.currentPassword || undefined, allowImpersonation: values.allowImpersonation, + name: values.name || undefined, }) .then(async () => { await refetch(); @@ -128,6 +133,7 @@ export const ProfileForm = () => { password: "", image: values.image, currentPassword: "", + name: values.name || "", }); }) .catch(() => { @@ -167,6 +173,19 @@ export const ProfileForm = () => { className="grid gap-4" >
+ ( + + Name + + + + + + )} + /> { value={field.value} className="flex flex-row flex-wrap gap-2 max-xl:justify-center" > + + + + + + + + + {getFallbackAvatarInitials( + data?.user?.name, + )} + + + + {availableAvatars.map((image) => ( diff --git a/apps/dokploy/components/dashboard/settings/servers/handle-servers.tsx b/apps/dokploy/components/dashboard/settings/servers/handle-servers.tsx index a2c9b50a6..1ff2f09dc 100644 --- a/apps/dokploy/components/dashboard/settings/servers/handle-servers.tsx +++ b/apps/dokploy/components/dashboard/settings/servers/handle-servers.tsx @@ -1,3 +1,11 @@ +import { zodResolver } from "@hookform/resolvers/zod"; +import { PlusIcon } from "lucide-react"; +import Link from "next/link"; +import { useTranslation } from "next-i18next"; +import { useEffect, useState } from "react"; +import { useForm } from "react-hook-form"; +import { toast } from "sonner"; +import { z } from "zod"; import { AlertBlock } from "@/components/shared/alert-block"; import { Button } from "@/components/ui/button"; import { @@ -30,14 +38,6 @@ import { } 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 { useTranslation } from "next-i18next"; -import Link from "next/link"; -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, { @@ -218,7 +218,7 @@ export const HandleServers = ({ serverId }: Props) => {
{!canCreateMoreServers && ( - + You cannot create more servers,{" "} Please upgrade your plan diff --git a/apps/dokploy/components/dashboard/settings/servers/show-servers.tsx b/apps/dokploy/components/dashboard/settings/servers/show-servers.tsx index d6465cf09..191aab9ce 100644 --- a/apps/dokploy/components/dashboard/settings/servers/show-servers.tsx +++ b/apps/dokploy/components/dashboard/settings/servers/show-servers.tsx @@ -1,3 +1,9 @@ +import { format } from "date-fns"; +import { KeyIcon, Loader2, MoreHorizontal, ServerIcon } from "lucide-react"; +import Link from "next/link"; +import { useRouter } from "next/router"; +import { useTranslation } from "next-i18next"; +import { toast } from "sonner"; import { AlertBlock } from "@/components/shared/alert-block"; import { DialogAction } from "@/components/shared/dialog-action"; import { Badge } from "@/components/ui/badge"; @@ -27,12 +33,6 @@ import { TableRow, } from "@/components/ui/table"; import { api } from "@/utils/api"; -import { format } from "date-fns"; -import { KeyIcon, Loader2, MoreHorizontal, ServerIcon } from "lucide-react"; -import { useTranslation } from "next-i18next"; -import Link from "next/link"; -import { useRouter } from "next/router"; -import { toast } from "sonner"; import { ShowNodesModal } from "../cluster/nodes/show-nodes-modal"; import { TerminalModal } from "../web-server/terminal-modal"; import { ShowServerActions } from "./actions/show-server-actions"; @@ -115,24 +115,6 @@ export const ShowServers = () => {
) : (
- {!canCreateMoreServers && ( - -
- -
- You cannot create more servers,{" "} - - Please upgrade your plan - -
-
-
-
- )} -
diff --git a/apps/dokploy/components/dashboard/settings/servers/welcome-stripe/create-server.tsx b/apps/dokploy/components/dashboard/settings/servers/welcome-stripe/create-server.tsx index 3a112af20..b895f385b 100644 --- a/apps/dokploy/components/dashboard/settings/servers/welcome-stripe/create-server.tsx +++ b/apps/dokploy/components/dashboard/settings/servers/welcome-stripe/create-server.tsx @@ -1,3 +1,9 @@ +import { zodResolver } from "@hookform/resolvers/zod"; +import Link from "next/link"; +import { useEffect, useState } from "react"; +import { useForm } from "react-hook-form"; +import { toast } from "sonner"; +import { z } from "zod"; import { AlertBlock } from "@/components/shared/alert-block"; import { Button } from "@/components/ui/button"; import { Card, CardContent } from "@/components/ui/card"; @@ -22,12 +28,6 @@ import { } from "@/components/ui/select"; import { Textarea } from "@/components/ui/textarea"; import { api } from "@/utils/api"; -import { zodResolver } from "@hookform/resolvers/zod"; -import Link from "next/link"; -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, { @@ -108,7 +108,7 @@ export const CreateServer = ({ stepper }: Props) => {
{!canCreateMoreServers && ( - + You cannot create more servers,{" "} Please upgrade your plan diff --git a/apps/dokploy/components/dashboard/settings/servers/welcome-stripe/create-ssh-key.tsx b/apps/dokploy/components/dashboard/settings/servers/welcome-stripe/create-ssh-key.tsx index d7ab5e329..ad386cc49 100644 --- a/apps/dokploy/components/dashboard/settings/servers/welcome-stripe/create-ssh-key.tsx +++ b/apps/dokploy/components/dashboard/settings/servers/welcome-stripe/create-ssh-key.tsx @@ -1,18 +1,22 @@ +import copy from "copy-to-clipboard"; +import { CopyIcon, ExternalLinkIcon, Loader2 } from "lucide-react"; +import Link from "next/link"; +import { useEffect, useRef, useState } from "react"; +import { toast } from "sonner"; import { CodeEditor } from "@/components/shared/code-editor"; import { Card, CardContent } from "@/components/ui/card"; +import { Label } from "@/components/ui/label"; +import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; import { api } from "@/utils/api"; -import copy from "copy-to-clipboard"; -import { ExternalLinkIcon, Loader2 } from "lucide-react"; -import { CopyIcon } from "lucide-react"; -import Link from "next/link"; -import { useEffect, useRef } from "react"; -import { toast } from "sonner"; export const CreateSSHKey = () => { const { data, refetch } = api.sshKey.all.useQuery(); const generateMutation = api.sshKey.generate.useMutation(); const { mutateAsync, isLoading } = api.sshKey.create.useMutation(); const hasCreatedKey = useRef(false); + const [selectedOption, setSelectedOption] = useState<"manual" | "provider">( + "manual", + ); const cloudSSHKey = data?.find( (sshKey) => sshKey.name === "dokploy-cloud-ssh-key", @@ -60,89 +64,122 @@ export const CreateSSHKey = () => {
) : ( <> -
+

- You have two options to add SSH Keys to your server: + Choose how to add SSH Keys to your server:

-
    -
  • 1. Add The SSH Key to Server Manually
  • + {/* Radio button options */} +
    + { + setSelectedOption(value as "manual" | "provider"); + }} + className="grid gap-3" + > +
    + + +
    -
  • - 2. Add the public SSH Key when you create a server in your - preffered provider (Hostinger, Digital Ocean, Hetzner, etc){" "} -
  • -
- -
- - Option 1 - -
    -
  • - 1. Login to your server{" "} -
  • -
  • - 2. When you are logged in run the following command -
    - > ~/.ssh/authorized_keys`} - readOnly - className="font-mono opacity-60" - /> - -
    -
  • -
  • - 3. You're done, follow the next step to insert the details - of your server. -
  • -
+
+ + +
+
-
- - Option 2 - -
-
-
- Copy Public Key - + + {/* Content based on selected option */} + {selectedOption === "manual" && ( +
+ + Manual Setup Instructions + +
    +
  • + 1. Login to your server +
  • +
  • + 2. When you are logged in run the following command +
    + > ~/.ssh/authorized_keys`} + readOnly + className="font-mono opacity-60" + /> + +
    +
  • +
  • + 3. You're done, follow the next step to insert the + details of your server. +
  • +
+
+ )} + + {selectedOption === "provider" && ( +
+ + Provider Setup Instructions + +
+
+
+ Copy Public Key + +
+

+ Use this public key when creating a server in your + preferred provider (Hostinger, Digital Ocean, Hetzner, + etc.) +

+ + View Tutorial +
- - View Tutorial - -
+ )}
)} diff --git a/apps/dokploy/components/dashboard/swarm/applications/show-applications.tsx b/apps/dokploy/components/dashboard/swarm/applications/show-applications.tsx index 626e2a282..6734991f1 100644 --- a/apps/dokploy/components/dashboard/swarm/applications/show-applications.tsx +++ b/apps/dokploy/components/dashboard/swarm/applications/show-applications.tsx @@ -1,3 +1,4 @@ +import { Layers, Loader2 } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Dialog, @@ -8,7 +9,6 @@ import { DialogTrigger, } from "@/components/ui/dialog"; import { api } from "@/utils/api"; -import { Layers, Loader2 } from "lucide-react"; import { type ApplicationList, columns } from "./columns"; import { DataTable } from "./data-table"; @@ -20,10 +20,10 @@ export const ShowNodeApplications = ({ serverId }: Props) => { const { data: NodeApps, isLoading: NodeAppsLoading } = api.swarm.getNodeApps.useQuery({ serverId }); - let applicationList = ""; + let applicationList: string[] = []; if (NodeApps && NodeApps.length > 0) { - applicationList = NodeApps.map((app) => app.Name).join(" "); + applicationList = NodeApps.map((app) => app.Name); } const { data: NodeAppDetails, isLoading: NodeAppDetailsLoading } = diff --git a/apps/dokploy/components/layouts/onboarding-layout.tsx b/apps/dokploy/components/layouts/onboarding-layout.tsx index 270c906c4..fff5413e0 100644 --- a/apps/dokploy/components/layouts/onboarding-layout.tsx +++ b/apps/dokploy/components/layouts/onboarding-layout.tsx @@ -1,6 +1,6 @@ -import { cn } from "@/lib/utils"; import Link from "next/link"; import type React from "react"; +import { cn } from "@/lib/utils"; import { GithubIcon } from "../icons/data-tools-icons"; import { Logo } from "../shared/logo"; import { Button } from "../ui/button"; diff --git a/apps/dokploy/components/layouts/side.tsx b/apps/dokploy/components/layouts/side.tsx index 15390baf9..d1d4ae273 100644 --- a/apps/dokploy/components/layouts/side.tsx +++ b/apps/dokploy/components/layouts/side.tsx @@ -1,4 +1,5 @@ "use client"; +import type { inferRouterOutputs } from "@trpc/server"; import { Activity, BarChartHorizontalBigIcon, @@ -29,10 +30,10 @@ import { User, Users, } from "lucide-react"; +import Link from "next/link"; import { usePathname } from "next/navigation"; -import type * as React from "react"; import { useEffect, useState } from "react"; - +import { toast } from "sonner"; import { Breadcrumb, BreadcrumbItem, @@ -77,10 +78,6 @@ import { authClient } from "@/lib/auth-client"; import { cn } from "@/lib/utils"; import type { AppRouter } from "@/server/api/root"; import { api } from "@/utils/api"; -import type { inferRouterOutputs } from "@trpc/server"; -import Link from "next/link"; -import { useRouter } from "next/router"; -import { toast } from "sonner"; import { AddOrganization } from "../dashboard/organization/handle-organization"; import { DialogAction } from "../shared/dialog-action"; import { Logo } from "../shared/logo"; @@ -770,9 +767,7 @@ export default function Page({ children }: Props) { setIsLoaded(true); }, []); - const router = useRouter(); const pathname = usePathname(); - const _currentPath = router.pathname; const { data: auth } = api.user.get.useQuery(); const { data: dokployVersion } = api.settings.getDokployVersion.useQuery(); diff --git a/apps/dokploy/components/layouts/update-server.tsx b/apps/dokploy/components/layouts/update-server.tsx index 42cac69f4..6f01682c0 100644 --- a/apps/dokploy/components/layouts/update-server.tsx +++ b/apps/dokploy/components/layouts/update-server.tsx @@ -1,8 +1,7 @@ -import { api } from "@/utils/api"; import type { IUpdateData } from "@dokploy/server/index"; import { Download } from "lucide-react"; -import { useRouter } from "next/router"; import { useEffect, useRef, useState } from "react"; +import { api } from "@/utils/api"; import UpdateServer from "../dashboard/settings/web-server/update-server"; import { Button } from "../ui/button"; import { @@ -11,6 +10,7 @@ import { TooltipProvider, TooltipTrigger, } from "../ui/tooltip"; + const AUTO_CHECK_UPDATES_INTERVAL_MINUTES = 7; export const UpdateServerButton = () => { @@ -18,7 +18,6 @@ export const UpdateServerButton = () => { latestVersion: null, updateAvailable: false, }); - const _router = useRouter(); const { data: isCloud } = api.settings.isCloud.useQuery(); const { mutateAsync: getUpdateData } = api.settings.getUpdateData.useMutation(); @@ -26,9 +25,6 @@ export const UpdateServerButton = () => { const checkUpdatesIntervalRef = useRef(null); - if (isCloud) { - return null; - } useEffect(() => { // Handling of automatic check for server updates if (isCloud) { @@ -77,7 +73,7 @@ export const UpdateServerButton = () => { }; }, []); - return updateData.updateAvailable ? ( + return !isCloud && updateData.updateAvailable ? (
{ src={data?.user?.image || ""} alt={data?.user?.image || ""} /> - CN + + {getFallbackAvatarInitials(data?.user?.name)} +
Account @@ -122,18 +125,16 @@ export const UserNav = () => { )} ) : ( - <> - {data?.role === "owner" && ( - { - router.push("/dashboard/settings/servers"); - }} - > - Servers - - )} - + data?.role === "owner" && ( + { + router.push("/dashboard/settings/servers"); + }} + > + Servers + + ) )} {isCloud && data?.role === "owner" && ( diff --git a/apps/dokploy/lib/utils.ts b/apps/dokploy/lib/utils.ts index b763e9ee9..f01faa4ec 100644 --- a/apps/dokploy/lib/utils.ts +++ b/apps/dokploy/lib/utils.ts @@ -27,3 +27,14 @@ export function formatTimestamp(timestamp: string | number) { return "Fecha inválida"; } } + +export function getFallbackAvatarInitials( + fullName: string | undefined, +): string { + if (typeof fullName === "undefined" || fullName === "") return "CN"; + const [name = "", surname = ""] = fullName.split(" "); + if (surname === "") { + return name.substring(0, 2).toUpperCase(); + } + return (name.charAt(0) + surname.charAt(0)).toUpperCase(); +} diff --git a/apps/dokploy/package.json b/apps/dokploy/package.json index ec1219500..2f2fc43d2 100644 --- a/apps/dokploy/package.json +++ b/apps/dokploy/package.json @@ -1,6 +1,6 @@ { "name": "dokploy", - "version": "v0.24.4", + "version": "v0.24.6", "private": true, "license": "Apache-2.0", "type": "module", diff --git a/apps/dokploy/server/api/routers/swarm.ts b/apps/dokploy/server/api/routers/swarm.ts index 997eba310..1c3abdf37 100644 --- a/apps/dokploy/server/api/routers/swarm.ts +++ b/apps/dokploy/server/api/routers/swarm.ts @@ -1,10 +1,10 @@ import { + findServerById, getApplicationInfo, getNodeApplications, getNodeInfo, getSwarmNodes, } from "@dokploy/server"; -import { findServerById } from "@dokploy/server"; import { TRPCError } from "@trpc/server"; import { z } from "zod"; import { createTRPCRouter, protectedProcedure } from "../trpc"; @@ -55,7 +55,12 @@ export const swarmRouter = createTRPCRouter({ getAppInfos: protectedProcedure .input( z.object({ - appName: z.string().min(1).regex(containerIdRegex, "Invalid app name."), + appName: z + .string() + .min(1) + .regex(containerIdRegex, "Invalid app name.") + .array() + .min(1), serverId: z.string().optional(), }), ) diff --git a/biome.json b/biome.json index 3309d3a86..519deba7b 100644 --- a/biome.json +++ b/biome.json @@ -19,14 +19,16 @@ }, "complexity": { "noUselessCatch": "off", - "noBannedTypes": "off" + "noBannedTypes": "off", + "noUselessFragments": "off" }, "correctness": { "useExhaustiveDependencies": "off", "noUnsafeOptionalChaining": "off", "noUnusedImports": "error", "noUnusedFunctionParameters": "error", - "noUnusedVariables": "error" + "noUnusedVariables": "error", + "useHookAtTopLevel": "off" }, "style": { "noNonNullAssertion": "off", diff --git a/packages/server/src/db/schema/user.ts b/packages/server/src/db/schema/user.ts index d1620ef16..e7fb4cbf4 100644 --- a/packages/server/src/db/schema/user.ts +++ b/packages/server/src/db/schema/user.ts @@ -323,6 +323,7 @@ export const apiUpdateWebServerMonitoring = z.object({ export const apiUpdateUser = createSchema.partial().extend({ password: z.string().optional(), currentPassword: z.string().optional(), + name: z.string().optional(), metricsConfig: z .object({ server: z.object({ diff --git a/packages/server/src/services/application.ts b/packages/server/src/services/application.ts index 179820b8f..a8effba98 100644 --- a/packages/server/src/services/application.ts +++ b/packages/server/src/services/application.ts @@ -237,6 +237,7 @@ export const deployApplication = async ({ } catch (error) { await updateDeploymentStatus(deployment.deploymentId, "error"); await updateApplicationStatus(applicationId, "error"); + await sendBuildErrorNotifications({ projectName: application.project.name, applicationName: application.name, @@ -370,8 +371,9 @@ export const deployRemoteApplication = async ({ domains: application.domains, }); } catch (error) { - // @ts-ignore - const encodedContent = encodeBase64(error?.message); + const errorMessage = error instanceof Error ? error.message : String(error); + + const encodedContent = encodeBase64(errorMessage); await execAsyncRemote( application.serverId, @@ -383,12 +385,12 @@ export const deployRemoteApplication = async ({ 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 building", + errorMessage: `Please check the logs for details: ${errorMessage}`, buildLink, organizationId: application.project.organizationId, }); diff --git a/packages/server/src/services/compose.ts b/packages/server/src/services/compose.ts index 8b3bd3b0f..bb1b2e8a0 100644 --- a/packages/server/src/services/compose.ts +++ b/packages/server/src/services/compose.ts @@ -1,8 +1,12 @@ import { join } from "node:path"; import { paths } from "@dokploy/server/constants"; import { db } from "@dokploy/server/db"; -import { type apiCreateCompose, compose } from "@dokploy/server/db/schema"; -import { buildAppName, cleanAppName } from "@dokploy/server/db/schema"; +import { + type apiCreateCompose, + buildAppName, + cleanAppName, + compose, +} from "@dokploy/server/db/schema"; import { buildCompose, getBuildComposeCommand, @@ -516,19 +520,20 @@ export const startCompose = async (composeId: string) => { const compose = await findComposeById(composeId); try { const { COMPOSE_PATH } = paths(!!compose.serverId); + + const projectPath = join(COMPOSE_PATH, compose.appName, "code"); + const path = + compose.sourceType === "raw" ? "docker-compose.yml" : compose.composePath; + const baseCommand = `docker compose -p ${compose.appName} -f ${path} up -d`; if (compose.composeType === "docker-compose") { if (compose.serverId) { await execAsyncRemote( compose.serverId, - `cd ${join( - COMPOSE_PATH, - compose.appName, - "code", - )} && docker compose -p ${compose.appName} up -d`, + `cd ${projectPath} && ${baseCommand}`, ); } else { - await execAsync(`docker compose -p ${compose.appName} up -d`, { - cwd: join(COMPOSE_PATH, compose.appName, "code"), + await execAsync(baseCommand, { + cwd: projectPath, }); } } diff --git a/packages/server/src/services/docker.ts b/packages/server/src/services/docker.ts index 2e315d008..2194c89c6 100644 --- a/packages/server/src/services/docker.ts +++ b/packages/server/src/services/docker.ts @@ -441,13 +441,13 @@ export const getNodeApplications = async (serverId?: string) => { }; export const getApplicationInfo = async ( - appName: string, + appNames: string[], serverId?: string, ) => { try { let stdout = ""; let stderr = ""; - const command = `docker service ps ${appName} --format '{{json .}}' --no-trunc`; + const command = `docker service ps ${appNames.join(" ")} --format '{{json .}}' --no-trunc`; if (serverId) { const result = await execAsyncRemote(serverId, command); diff --git a/packages/server/src/services/mount.ts b/packages/server/src/services/mount.ts index 91d67a211..d64fef6f1 100644 --- a/packages/server/src/services/mount.ts +++ b/packages/server/src/services/mount.ts @@ -2,18 +2,22 @@ import path from "node:path"; import { paths } from "@dokploy/server/constants"; import { db } from "@dokploy/server/db"; import { - type ServiceType, type apiCreateMount, mounts, + type ServiceType, } from "@dokploy/server/db/schema"; import { createFile, + encodeBase64, getCreateFileCommand, } from "@dokploy/server/utils/docker/utils"; import { removeFileOrDirectory } from "@dokploy/server/utils/filesystem/directory"; -import { execAsyncRemote } from "@dokploy/server/utils/process/execAsync"; +import { + execAsync, + execAsyncRemote, +} from "@dokploy/server/utils/process/execAsync"; import { TRPCError } from "@trpc/server"; -import { type SQL, eq, sql } from "drizzle-orm"; +import { eq, type SQL, sql } from "drizzle-orm"; export type Mount = typeof mounts.$inferSelect; @@ -123,7 +127,7 @@ export const updateMount = async ( mountId: string, mountData: Partial, ) => { - return await db.transaction(async (tx) => { + const mount = await db.transaction(async (tx) => { const mount = await tx .update(mounts) .set({ @@ -140,13 +144,13 @@ export const updateMount = async ( }); } - if (mount.type === "file") { - await deleteFileMount(mountId); - await createFileMount(mountId); - } - return await findMountById(mountId); }); + + if (mount.type === "file") { + await updateFileMount(mountId); + } + return mount; }; export const findMountsByApplicationId = async ( @@ -198,6 +202,26 @@ export const deleteMount = async (mountId: string) => { return deletedMount[0]; }; +export const updateFileMount = async (mountId: string) => { + const mount = await findMountById(mountId); + if (!mount || !mount.filePath) return; + const basePath = await getBaseFilesPath(mountId); + const fullPath = path.join(basePath, mount.filePath); + + try { + const serverId = await getServerId(mount); + const encodedContent = encodeBase64(mount.content || ""); + const command = `echo "${encodedContent}" | base64 -d > ${fullPath}`; + if (serverId) { + await execAsyncRemote(serverId, command); + } else { + await execAsync(command); + } + } catch { + console.log("Error updating file mount"); + } +}; + export const deleteFileMount = async (mountId: string) => { const mount = await findMountById(mountId); if (!mount.filePath) return; diff --git a/packages/server/src/setup/server-setup.ts b/packages/server/src/setup/server-setup.ts index 1a4554084..a9ca1c371 100644 --- a/packages/server/src/setup/server-setup.ts +++ b/packages/server/src/setup/server-setup.ts @@ -6,18 +6,17 @@ import { } from "@dokploy/server/services/deployment"; import { findServerById } from "@dokploy/server/services/server"; import { + getDefaultMiddlewares, + getDefaultServerTraefikConfig, TRAEFIK_HTTP3_PORT, TRAEFIK_PORT, TRAEFIK_SSL_PORT, TRAEFIK_VERSION, - getDefaultMiddlewares, - getDefaultServerTraefikConfig, } from "@dokploy/server/setup/traefik-setup"; +import slug from "slugify"; import { Client } from "ssh2"; import { recreateDirectory } from "../utils/filesystem/directory"; -import slug from "slugify"; - export const slugify = (text: string | undefined) => { if (!text) { return ""; @@ -609,7 +608,7 @@ const installRailpack = () => ` if command_exists railpack; then echo "Railpack already installed ✅" else - export RAILPACK_VERSION=0.0.64 + export RAILPACK_VERSION=0.2.2 bash -c "$(curl -fsSL https://railpack.com/install.sh)" echo "Railpack version $RAILPACK_VERSION installed ✅" fi diff --git a/packages/server/src/utils/backups/compose.ts b/packages/server/src/utils/backups/compose.ts index 2fb808198..f260ffa08 100644 --- a/packages/server/src/utils/backups/compose.ts +++ b/packages/server/src/utils/backups/compose.ts @@ -17,7 +17,7 @@ export const runComposeBackup = async ( const project = await findProjectById(projectId); const { prefix, databaseType } = backup; const destination = backup.destination; - const backupFileName = `${new Date().toISOString()}.dump.gz`; + const backupFileName = `${new Date().toISOString()}.sql.gz`; const bucketDestination = `${normalizeS3Path(prefix)}${backupFileName}`; const deployment = await createDeploymentBackup({ backupId: backup.backupId, diff --git a/packages/server/src/utils/backups/index.ts b/packages/server/src/utils/backups/index.ts index 5b74d92fc..6ce8f9e55 100644 --- a/packages/server/src/utils/backups/index.ts +++ b/packages/server/src/utils/backups/index.ts @@ -1,7 +1,11 @@ import path from "node:path"; +import { member } from "@dokploy/server/db/schema"; +import type { BackupSchedule } from "@dokploy/server/services/backup"; import { getAllServers } from "@dokploy/server/services/server"; +import { eq } from "drizzle-orm"; import { scheduleJob } from "node-schedule"; import { db } from "../../db/index"; +import { startLogCleanup } from "../access-log/handler"; import { cleanUpDockerBuilder, cleanUpSystemPrune, @@ -11,11 +15,6 @@ import { sendDockerCleanupNotifications } from "../notifications/docker-cleanup" import { execAsync, execAsyncRemote } from "../process/execAsync"; import { getS3Credentials, scheduleBackup } from "./utils"; -import { member } from "@dokploy/server/db/schema"; -import type { BackupSchedule } from "@dokploy/server/services/backup"; -import { eq } from "drizzle-orm"; -import { startLogCleanup } from "../access-log/handler"; - export const initCronJobs = async () => { console.log("Setting up cron jobs...."); diff --git a/packages/server/src/utils/backups/mongo.ts b/packages/server/src/utils/backups/mongo.ts index e626efa01..6a74f1d10 100644 --- a/packages/server/src/utils/backups/mongo.ts +++ b/packages/server/src/utils/backups/mongo.ts @@ -14,7 +14,7 @@ export const runMongoBackup = async (mongo: Mongo, backup: BackupSchedule) => { const project = await findProjectById(projectId); const { prefix } = backup; const destination = backup.destination; - const backupFileName = `${new Date().toISOString()}.dump.gz`; + const backupFileName = `${new Date().toISOString()}.sql.gz`; const bucketDestination = `${normalizeS3Path(prefix)}${backupFileName}`; const deployment = await createDeploymentBackup({ backupId: backup.backupId, diff --git a/packages/server/src/utils/builders/railpack.ts b/packages/server/src/utils/builders/railpack.ts index 991720f3a..acbf7b97c 100644 --- a/packages/server/src/utils/builders/railpack.ts +++ b/packages/server/src/utils/builders/railpack.ts @@ -1,7 +1,6 @@ import { createHash } from "node:crypto"; import type { WriteStream } from "node:fs"; import { nanoid } from "nanoid"; -import type { ApplicationNested } from "."; import { parseEnvironmentKeyValuePair, prepareEnvironmentVariables, @@ -9,6 +8,7 @@ import { import { getBuildAppDirectory } from "../filesystem/directory"; import { execAsync } from "../process/execAsync"; import { spawnAsync } from "../process/spawnAsync"; +import type { ApplicationNested } from "."; const calculateSecretsHash = (envVariables: string[]): string => { const hash = createHash("sha256"); @@ -75,7 +75,7 @@ export const buildRailpack = async ( ] : []), "--build-arg", - "BUILDKIT_SYNTAX=ghcr.io/railwayapp/railpack-frontend:v0.0.64", + "BUILDKIT_SYNTAX=ghcr.io/railwayapp/railpack-frontend:v0.2.2", "-f", `${buildAppDirectory}/railpack-plan.json`, "--output", diff --git a/packages/server/src/utils/docker/domain.ts b/packages/server/src/utils/docker/domain.ts index c12083345..0ce138d70 100644 --- a/packages/server/src/utils/docker/domain.ts +++ b/packages/server/src/utils/docker/domain.ts @@ -313,40 +313,46 @@ export const createDomainLabels = ( `traefik.http.routers.${routerName}.service=${routerName}`, ]; - // Validate stripPath - it should only be used when path is defined and not "/" - if (stripPath) { - if (!path || path === "/") { - console.warn( - `stripPath is enabled but path is not defined or is "/" for domain ${host}`, - ); - } else { - const middlewareName = `stripprefix-${appName}-${uniqueConfigKey}`; + // Collect middlewares for this router + const middlewares: string[] = []; + + // Add HTTPS redirect for web entrypoint (must be first) + if (entrypoint === "web" && https) { + middlewares.push("redirect-to-https@file"); + } + + // Add stripPath middleware if needed + if (stripPath && path && path !== "/") { + const middlewareName = `stripprefix-${appName}-${uniqueConfigKey}`; + // Only define middleware once (on web entrypoint) + if (entrypoint === "web") { labels.push( `traefik.http.middlewares.${middlewareName}.stripprefix.prefixes=${path}`, ); } + middlewares.push(middlewareName); } - // Validate internalPath - ensure it's a valid path format - if (internalPath && internalPath !== "/") { - if (!internalPath.startsWith("/")) { - console.warn( - `internalPath "${internalPath}" should start with "/" and not be empty for domain ${host}`, - ); - } else { - const middlewareName = `addprefix-${appName}-${uniqueConfigKey}`; + // Add internalPath middleware if needed + if (internalPath && internalPath !== "/" && internalPath.startsWith("/")) { + const middlewareName = `addprefix-${appName}-${uniqueConfigKey}`; + // Only define middleware once (on web entrypoint) + if (entrypoint === "web") { labels.push( `traefik.http.middlewares.${middlewareName}.addprefix.prefix=${internalPath}`, ); } + middlewares.push(middlewareName); } - if (entrypoint === "web" && https) { + // Apply middlewares to router if any exist + if (middlewares.length > 0) { labels.push( - `traefik.http.routers.${routerName}.middlewares=redirect-to-https@file`, + `traefik.http.routers.${routerName}.middlewares=${middlewares.join(",")}`, ); } + // Add TLS configuration for websecure if (entrypoint === "websecure") { if (certificateType === "letsencrypt") { labels.push( diff --git a/packages/server/src/utils/notifications/build-error.ts b/packages/server/src/utils/notifications/build-error.ts index c873c9ab5..47fa4de77 100644 --- a/packages/server/src/utils/notifications/build-error.ts +++ b/packages/server/src/utils/notifications/build-error.ts @@ -65,6 +65,8 @@ export const sendBuildErrorNotifications = async ({ const decorate = (decoration: string, text: string) => `${discord.decoration ? decoration : ""} ${text}`.trim(); + const limitCharacter = 800; + const truncatedErrorMessage = errorMessage.substring(0, limitCharacter); await sendDiscordNotification(discord, { title: decorate(">", "`⚠️` Build Failed"), color: 0xed4245, @@ -101,7 +103,7 @@ export const sendBuildErrorNotifications = async ({ }, { name: decorate("`⚠️`", "Error Message"), - value: `\`\`\`${errorMessage}\`\`\``, + value: `\`\`\`${truncatedErrorMessage}\`\`\``, }, { name: decorate("`🧷`", "Build Link"), diff --git a/packages/server/src/utils/providers/gitlab.ts b/packages/server/src/utils/providers/gitlab.ts index 369951699..774cd818f 100644 --- a/packages/server/src/utils/providers/gitlab.ts +++ b/packages/server/src/utils/providers/gitlab.ts @@ -4,8 +4,8 @@ import { paths } from "@dokploy/server/constants"; import type { apiGitlabTestConnection } from "@dokploy/server/db/schema"; import type { Compose } from "@dokploy/server/services/compose"; import { - type Gitlab, findGitlabById, + type Gitlab, updateGitlab, } from "@dokploy/server/services/gitlab"; import type { InferResultType } from "@dokploy/server/types/with"; @@ -310,22 +310,43 @@ export const getGitlabBranches = async (input: { const gitlabProvider = await findGitlabById(input.gitlabId); - const branchesResponse = await fetch( - `${gitlabProvider.gitlabUrl}/api/v4/projects/${input.id}/repository/branches`, - { - headers: { - Authorization: `Bearer ${gitlabProvider.accessToken}`, - }, - }, - ); + const allBranches = []; + let page = 1; + const perPage = 100; // GitLab's max per page is 100 - if (!branchesResponse.ok) { - throw new Error(`Failed to fetch branches: ${branchesResponse.statusText}`); + while (true) { + const branchesResponse = await fetch( + `https://gitlab.com/api/v4/projects/${input.id}/repository/branches?page=${page}&per_page=${perPage}`, + { + headers: { + Authorization: `Bearer ${gitlabProvider.accessToken}`, + }, + }, + ); + + if (!branchesResponse.ok) { + throw new Error( + `Failed to fetch branches: ${branchesResponse.statusText}`, + ); + } + + const branches = await branchesResponse.json(); + + if (branches.length === 0) { + break; + } + + allBranches.push(...branches); + page++; + + // Check if we've reached the total using headers (optional optimization) + const total = branchesResponse.headers.get("x-total"); + if (total && allBranches.length >= Number.parseInt(total)) { + break; + } } - const branches = await branchesResponse.json(); - - return branches as { + return allBranches as { id: string; name: string; commit: { diff --git a/packages/server/src/utils/restore/utils.ts b/packages/server/src/utils/restore/utils.ts index 17be49c97..c46077238 100644 --- a/packages/server/src/utils/restore/utils.ts +++ b/packages/server/src/utils/restore/utils.ts @@ -81,7 +81,7 @@ const getMongoSpecificCommand = ( backupFile: string, ): string => { const tempDir = "/tmp/dokploy-restore"; - const fileName = backupFile.split("/").pop() || "backup.dump.gz"; + const fileName = backupFile.split("/").pop() || "backup.sql.gz"; const decompressedName = fileName.replace(".gz", ""); return ` rm -rf ${tempDir} && \ diff --git a/packages/server/src/utils/volume-backups/backup.ts b/packages/server/src/utils/volume-backups/backup.ts index 3b074f998..cc613ffa9 100644 --- a/packages/server/src/utils/volume-backups/backup.ts +++ b/packages/server/src/utils/volume-backups/backup.ts @@ -2,8 +2,7 @@ import path from "node:path"; import { paths } from "@dokploy/server/constants"; import { findComposeById } from "@dokploy/server/services/compose"; import type { findVolumeBackupById } from "@dokploy/server/services/volume-backups"; -import { normalizeS3Path } from "../backups/utils"; -import { getS3Credentials } from "../backups/utils"; +import { getS3Credentials, normalizeS3Path } from "../backups/utils"; export const backupVolume = async ( volumeBackup: Awaited>, @@ -37,6 +36,9 @@ export const backupVolume = async ( echo "Starting upload to S3..." ${rcloneCommand} echo "Upload to S3 done ✅" + echo "Cleaning up local backup file..." + rm "${volumeBackupPath}/${backupFileName}" + echo "Local backup file cleaned up ✅" `; if (!turnOff) {