From 30b3e1fe48aea3bddc28914ab142c4c1c20c7840 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 30 May 2026 16:01:52 -0600 Subject: [PATCH] =?UTF-8?q?=F0=9F=9A=80=20Release=20v0.29.6=20(#4514)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(migrate-auth-secret): exit cleanly when there are no 2FA records The empty-records branch of `main()` returned without calling `process.exit(0)`, leaving the Drizzle Postgres connection pool holding the event loop open. The `migrate-auth-secret` process then hangs indefinitely after printing "No 2FA records found, nothing to migrate." causing the upstream `0.29.3.sh` security migration script (which calls this via `docker exec`) to never reach its final `docker service update` step that mounts the new Docker Secret. Operators end up with the new secret created but the dokploy service still configured with the hardcoded `BETTER_AUTH_SECRET`, while believing the migration completed. Match the success branch a few lines below which already does `process.exit(0)`, and the pattern used in sibling scripts `reset-password.ts` and `reset-2fa.ts`. Closes #4392 * feat(compose): add import from base64 in create service dropdown Adds an "Import" option to the Create Service dropdown that lets users paste a base64-encoded compose export, preview the template (compose YAML, domains, envs, mounts) before confirming, and create the service only on confirm. Adds a `previewTemplate` tRPC procedure that processes the base64 without touching the DB, with server access validation via session. * [autofix.ci] apply automated fixes * Enhance version synchronization workflow to include SDK repository - Updated the GitHub Actions workflow to sync versioning across MCP, CLI, and SDK repositories. - Added steps to bump the version in the SDK repository and regenerate tools from the latest OpenAPI spec. - Improved commit message formatting to include source and release information for all repositories. - Ensured successful synchronization messages for each repository after the version update. * feat(deployment): add readLogs procedure to fetch deployment logs - Introduced a new `readLogs` procedure that allows users to retrieve logs for a specific deployment by providing the deployment ID and an optional tail parameter. - Implemented permission checks to ensure users have access to the requested logs. - Enhanced log retrieval for both cloud and non-cloud environments, utilizing appropriate commands based on the server context. Resolve https://github.com/Dokploy/mcp/issues/14 * feat(deployment): add server access validation for deployment actions - Implemented server access validation in deployment procedures to ensure users can only access deployments associated with their active organization. - Added checks to throw an UNAUTHORIZED error if a user attempts to access a deployment linked to a server outside their organization. This enhancement improves security and access control within the deployment management system. * feat(organization): prevent inviting users with owner role - Added validation to prevent users from being invited with the owner role in the organization and user routers. - Implemented TRPCError responses to ensure proper error handling when attempting to assign the owner role. This change enhances role management and security within the organization structure. https://github.com/Dokploy/dokploy/security/advisories/GHSA-fm9p-wmpw-gxjh * feat(user): implement session cleanup on user update - Added functionality to delete old sessions when a user updates their password, ensuring that only the current session remains active. - This change enhances security by preventing unauthorized access from previous sessions after a password change. Close here https://github.com/Dokploy/dokploy/security/advisories/GHSA-rr9m-w87g-46f3 * feat(settings): add copy button to server IP in web server settings (#4397) * fix: copy Dokploy server IP when clicking server badge (#4390) * fix: copy Dokploy server IP when clicking server badge When a service runs on the local Dokploy server (no remote server), clicking the server badge did nothing because `data.server` is null. Now falls back to the server IP from settings so the badge always copies an IP address. Co-Authored-By: Claude Opus 4.6 (1M context) * feat(copy-ip): implement IP address copying functionality across database service components - Added the ability to copy the server IP address to the clipboard when clicking the server badge in various database service components (Libsql, MariaDB, MongoDB, MySQL, PostgreSQL, Redis). - Integrated the `copy-to-clipboard` library and `sonner` for user feedback upon successful copy action. - Ensured fallback to the server IP from settings when the service data is not available, enhancing user experience and functionality. --------- Co-authored-by: Claude Opus 4.6 (1M context) Co-authored-by: Mauricio Siu * fix: responsive layout (#4391) Signed-off-by: Nahidujjaman Hridoy * fix: automatically converting username to lowercase both in creation of register, and build for extra. (#4382) * fix: allow square brackets in zip path validation for Next.js dynamic routes (#4468) * fix: allow square brackets in zip drop path validation for Next.js dynamic routes ZIP uploads containing Next.js dynamic route files (e.g. app/api/[id]/route.ts, pages/[slug].tsx) were rejected by readValidDirectory because the path regex did not include square bracket characters. * [autofix.ci] apply automated fixes --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> * fix: prevent webhook deploy crash when commit data lacks modified files (#4470) shouldDeploy passed undefined/null entries from commit.modified straight into micromatch, which throws "Expected input to be a string" and fails every webhook deployment when watch paths are configured. Filter out non-string values before matching. * fix: add type="button" to TooltipTrigger in form components to prevent accidental submission (#4422) Co-authored-by: Maks Pikov * fix: enable comment toggle shortcut in env variable editor (#4402) (#4473) * fix: add tls=true label for domains when certificateType is none (#4018) (#4474) * fix: add tls=true label for compose domains when certificateType is none (#4018) * test: cover tls=true label for certificateType none, require https * fix: scope tls fix to compose labels, leave traefik file config unchanged (#4018) * chore: update version to v0.29.5 in package.json * chore(deps): upgrade next to 16.2.6 (#4477) Upgraded next dependency in apps/dokploy to 16.2.6 exactly. Verified typescript typecheck passes successfully. * feat: add self-hosted enterprise restrictions (remote-servers-only, enforce-sso) (#4511) * feat: add self-hosted enterprise restrictions (remote-servers-only, enforce-sso) - Add `remoteServersOnly` field to webServerSettings: prevents creating services on the local Dokploy VM, forcing all deployments to remote servers. Validated in all 8 service routers (application, compose, postgres, mysql, mongo, redis, mariadb, libsql). - Add `enforceSSO` field to webServerSettings: hides the email/password login form and shows only the SSO button on the login page. - Both settings are enterprise-only (enterpriseProcedure) and self-hosted-only (blocked at the API level when IS_CLOUD=true). - UI toggles added to the SSO settings page under a new "Self-hosted Restrictions" card (hidden in cloud). Login page reads enforceSSO from getServerSideProps to avoid client-side flash. - Migrations: 0167_fresh_goliath.sql, 0168_long_justice.sql * fix: add missing final newlines to migration files * refactor: improve code formatting for better readability in multiple components - Adjusted formatting in `add-application.tsx`, `add-compose.tsx`, and `add-database.tsx` to enhance readability by adding line breaks and consistent indentation. - Updated `toggle-enforce-sso.tsx` to simplify the Switch component's props. - Reformatted imports in `index.tsx` and `sso.tsx` for consistency. - Cleaned up conditional statements in various router files for improved clarity. * fix: add enforceSSO to test mock * fix: grant create and delete SSH key permissions when canAccessToSSHKeys is enabled for members (#4512) * fix: use create permission for basic auth delete instead of delete (#4513) * fix: wrap long server names and keep actions menu visible (#4434) On settings/servers, a long server name in the card title (h3) did not wrap and overflowed its container, overlapping nearby content and squeezing the three-dots actions menu until it disappeared. Allow the title block to shrink and wrap (min-w-0 + break-words), keep the server icon and the actions trigger from being crushed (shrink-0), and add gap between the title and the actions button. * chore: update version to v0.29.6 in package.json * fix: preserve HOME in compose deploy so --with-registry-auth can read docker config (#4485) The compose/stack deploy command runs under `env -i PATH="$PATH"`, which clears the environment except for PATH. That strips HOME, so when the generated command is `docker stack deploy --prune --with-registry-auth` the docker CLI cannot resolve `~/.docker/config.json` (e.g. `/root/.docker/config.json`) and ships no registry credentials to the swarm. Private-registry images then fail to pull on the nodes: image registry.example.com/... could not be accessed on a registry to record its digest. Each node will access ... independently while the deploy still logs "Docker Compose Deployed: ✅". Keep PATH isolation but preserve HOME so docker can read its config for both `stack deploy --with-registry-auth` and `compose up -d --build`. Add a regression test asserting the generated command preserves `HOME="$HOME"` for both stack and docker-compose deploys. Fixes #4401 Co-authored-by: Claude Opus 4.7 (1M context) --------- Signed-off-by: Nahidujjaman Hridoy Co-authored-by: ngenohkevin Co-authored-by: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Co-authored-by: Mauricio Siu Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Volodymyr Kravchuk Co-authored-by: Claude Opus 4.6 (1M context) Co-authored-by: Nahidujjaman Hridoy <75487507+nhridoy@users.noreply.github.com> Co-authored-by: Francis <9560564+Baker@users.noreply.github.com> Co-authored-by: mixelburg <52622705+mixelburg@users.noreply.github.com> Co-authored-by: Maks Pikov Co-authored-by: Jasael <67719321+jasael@users.noreply.github.com> Co-authored-by: Philippe Parage <69145356+pparage@users.noreply.github.com> Co-authored-by: youcef zr <93142224+youcefzemmar@users.noreply.github.com> --- .../compose/build-compose-command.test.ts | 52 + .../server/update-server-config.test.ts | 2 + .../dashboard/project/add-application.tsx | 17 +- .../dashboard/project/add-compose.tsx | 17 +- .../dashboard/project/add-database.tsx | 13 +- .../servers/actions/toggle-enforce-sso.tsx | 48 + .../actions/toggle-remote-servers-only.tsx | 53 + .../settings/servers/show-servers.tsx | 10 +- .../proprietary/sso/sign-in-with-sso.tsx | 27 +- apps/dokploy/drizzle/0167_fresh_goliath.sql | 1 + apps/dokploy/drizzle/0168_long_justice.sql | 1 + apps/dokploy/drizzle/meta/0167_snapshot.json | 8325 ++++++++++++++++ apps/dokploy/drizzle/meta/0168_snapshot.json | 8332 +++++++++++++++++ apps/dokploy/drizzle/meta/_journal.json | 16 +- apps/dokploy/package.json | 4 +- apps/dokploy/pages/dashboard/settings/sso.tsx | 46 +- apps/dokploy/pages/index.tsx | 17 +- .../dokploy/server/api/routers/application.ts | 7 +- apps/dokploy/server/api/routers/compose.ts | 12 +- apps/dokploy/server/api/routers/libsql.ts | 7 +- apps/dokploy/server/api/routers/mariadb.ts | 7 +- apps/dokploy/server/api/routers/mongo.ts | 7 +- apps/dokploy/server/api/routers/mysql.ts | 7 +- apps/dokploy/server/api/routers/postgres.ts | 7 +- .../server/api/routers/proprietary/sso.ts | 8 + apps/dokploy/server/api/routers/redis.ts | 7 +- apps/dokploy/server/api/routers/security.ts | 2 +- apps/dokploy/server/api/routers/settings.ts | 45 + .../src/db/schema/web-server-settings.ts | 6 + packages/server/src/services/permission.ts | 2 + packages/server/src/utils/builders/compose.ts | 2 +- pnpm-lock.yaml | 219 +- 32 files changed, 17168 insertions(+), 158 deletions(-) create mode 100644 apps/dokploy/__test__/compose/build-compose-command.test.ts create mode 100644 apps/dokploy/components/dashboard/settings/servers/actions/toggle-enforce-sso.tsx create mode 100644 apps/dokploy/components/dashboard/settings/servers/actions/toggle-remote-servers-only.tsx create mode 100644 apps/dokploy/drizzle/0167_fresh_goliath.sql create mode 100644 apps/dokploy/drizzle/0168_long_justice.sql create mode 100644 apps/dokploy/drizzle/meta/0167_snapshot.json create mode 100644 apps/dokploy/drizzle/meta/0168_snapshot.json diff --git a/apps/dokploy/__test__/compose/build-compose-command.test.ts b/apps/dokploy/__test__/compose/build-compose-command.test.ts new file mode 100644 index 000000000..0aa2bc2d9 --- /dev/null +++ b/apps/dokploy/__test__/compose/build-compose-command.test.ts @@ -0,0 +1,52 @@ +import { getBuildComposeCommand } from "@dokploy/server/utils/builders/compose"; +import { describe, expect, it, vi } from "vitest"; + +// Isolate the command builder from the compose-file I/O performed by +// writeDomainsToCompose; we only care about the docker invocation it emits. +vi.mock("@dokploy/server/utils/docker/domain", () => ({ + writeDomainsToCompose: vi.fn().mockResolvedValue(""), +})); + +const baseCompose = { + appName: "my-app", + sourceType: "raw", + command: "", + composePath: "docker-compose.yml", + composeType: "stack", + isolatedDeployment: false, + randomize: false, + suffix: "", + serverId: null, + env: "", + mounts: [], + domains: [], + environment: { project: { env: "" }, env: "" }, +} as unknown as Parameters[0]; + +// Regression coverage for #4401: the deploy command runs under `env -i`, which +// clears the environment except for the vars listed explicitly. HOME must be +// preserved so docker can resolve ~/.docker/config.json — otherwise +// `docker stack deploy --with-registry-auth` ships no credentials to the swarm +// and private-registry images fail to pull. +describe("getBuildComposeCommand registry auth (#4401)", () => { + it("preserves HOME for swarm stack deploys", async () => { + const command = await getBuildComposeCommand({ + ...baseCompose, + composeType: "stack", + }); + + expect(command).toContain("stack deploy"); + expect(command).toContain("--with-registry-auth"); + expect(command).toContain('env -i PATH="$PATH" HOME="$HOME"'); + }); + + it("preserves HOME for docker compose deploys", async () => { + const command = await getBuildComposeCommand({ + ...baseCompose, + composeType: "docker-compose", + }); + + expect(command).toContain("compose -p my-app"); + expect(command).toContain('env -i PATH="$PATH" HOME="$HOME"'); + }); +}); diff --git a/apps/dokploy/__test__/traefik/server/update-server-config.test.ts b/apps/dokploy/__test__/traefik/server/update-server-config.test.ts index e07f34ade..eda4dace5 100644 --- a/apps/dokploy/__test__/traefik/server/update-server-config.test.ts +++ b/apps/dokploy/__test__/traefik/server/update-server-config.test.ts @@ -65,6 +65,8 @@ const baseSettings: WebServerSettings = { cleanupCacheApplications: false, cleanupCacheOnCompose: false, cleanupCacheOnPreviews: false, + remoteServersOnly: false, + enforceSSO: false, createdAt: null, updatedAt: new Date(), }; diff --git a/apps/dokploy/components/dashboard/project/add-application.tsx b/apps/dokploy/components/dashboard/project/add-application.tsx index 16fac353d..0c50cc41f 100644 --- a/apps/dokploy/components/dashboard/project/add-application.tsx +++ b/apps/dokploy/components/dashboard/project/add-application.tsx @@ -71,6 +71,9 @@ interface Props { export const AddApplication = ({ environmentId, projectName }: Props) => { const utils = api.useUtils(); const { data: isCloud } = api.settings.isCloud.useQuery(); + const { data: webServerSettings } = + api.settings.getWebServerSettings.useQuery(); + const showLocalOption = !isCloud && !webServerSettings?.remoteServersOnly; const [visible, setVisible] = useState(false); const slug = slugify(projectName); const { data: servers } = api.server.withSSHKey.useQuery(); @@ -171,7 +174,8 @@ export const AddApplication = ({ environmentId, projectName }: Props) => { - Select a Server {!isCloud ? "(Optional)" : ""} + Select a Server{" "} + {showLocalOption ? "(Optional)" : ""} @@ -191,17 +195,19 @@ export const AddApplication = ({ environmentId, projectName }: Props) => { - {!isCloud && ( + {showLocalOption && ( Dokploy @@ -236,7 +242,8 @@ export const AddCompose = ({ environmentId, projectName }: Props) => { ))} - Servers ({servers?.length + (!isCloud ? 1 : 0)}) + Servers ( + {servers?.length + (showLocalOption ? 1 : 0)}) diff --git a/apps/dokploy/components/dashboard/project/add-database.tsx b/apps/dokploy/components/dashboard/project/add-database.tsx index 966fe0013..a76de2775 100644 --- a/apps/dokploy/components/dashboard/project/add-database.tsx +++ b/apps/dokploy/components/dashboard/project/add-database.tsx @@ -219,6 +219,9 @@ export const AddDatabase = ({ environmentId, projectName }: Props) => { const [visible, setVisible] = useState(false); const slug = slugify(projectName); const { data: isCloud } = api.settings.isCloud.useQuery(); + const { data: webServerSettings } = + api.settings.getWebServerSettings.useQuery(); + const showLocalOption = !isCloud && !webServerSettings?.remoteServersOnly; const { data: servers } = api.server.withSSHKey.useQuery(); const libsqlMutation = api.libsql.create.useMutation(); const mariadbMutation = api.mariadb.create.useMutation(); @@ -470,19 +473,20 @@ export const AddDatabase = ({ environmentId, projectName }: Props) => {