diff --git a/apps/dokploy/__test__/templates/config.template.test.ts b/apps/dokploy/__test__/templates/config.template.test.ts index 145e1372..902e3163 100644 --- a/apps/dokploy/__test__/templates/config.template.test.ts +++ b/apps/dokploy/__test__/templates/config.template.test.ts @@ -166,7 +166,38 @@ describe("processTemplate", () => { if (!baseUrl || !secretKey) return; expect(baseUrl).toContain(mockSchema.projectName); - expect(secretKey.split("=")[1]).toHaveLength(64); + const base64Value = secretKey.split("=")[1]; + expect(base64Value).toBeDefined(); + if (!base64Value) return; + expect(base64Value).toMatch(/^[A-Za-z0-9+/]+={0,2}$/); + expect(base64Value.length).toBeGreaterThanOrEqual(86); + expect(base64Value.length).toBeLessThanOrEqual(88); + }); + + it("should process env vars when provided as an array", () => { + const template: CompleteTemplate = { + metadata: {} as any, + variables: {}, + config: { + domains: [], + env: [ + 'CLOUDFLARE_TUNNEL_TOKEN=""', + 'ANOTHER_VAR="some value"', + "DOMAIN=${domain}", + ], + mounts: [], + }, + }; + + const result = processTemplate(template, mockSchema); + expect(result.envs).toHaveLength(3); + + // Should preserve exact format for static values + expect(result.envs[0]).toBe('CLOUDFLARE_TUNNEL_TOKEN=""'); + expect(result.envs[1]).toBe('ANOTHER_VAR="some value"'); + + // Should process variables in array items + expect(result.envs[2]).toContain(mockSchema.projectName); }); it("should allow using utility functions directly in env vars", () => { @@ -195,7 +226,12 @@ describe("processTemplate", () => { if (!randomDomainEnv || !secretKeyEnv) return; expect(randomDomainEnv).toContain(mockSchema.projectName); - expect(secretKeyEnv.split("=")[1]).toHaveLength(32); + const base64Value = secretKeyEnv.split("=")[1]; + expect(base64Value).toBeDefined(); + if (!base64Value) return; + expect(base64Value).toMatch(/^[A-Za-z0-9+/]+={0,2}$/); + expect(base64Value.length).toBeGreaterThanOrEqual(42); + expect(base64Value.length).toBeLessThanOrEqual(44); }); }); @@ -325,8 +361,22 @@ describe("processTemplate", () => { if (!baseUrl || !secretKey || !totpKey) return; expect(baseUrl).toContain(mockSchema.projectName); - expect(secretKey.split("=")[1]).toHaveLength(64); - expect(totpKey.split("=")[1]).toHaveLength(32); + + // Check base64 lengths and format + const secretKeyValue = secretKey.split("=")[1]; + const totpKeyValue = totpKey.split("=")[1]; + + expect(secretKeyValue).toBeDefined(); + expect(totpKeyValue).toBeDefined(); + if (!secretKeyValue || !totpKeyValue) return; + + expect(secretKeyValue).toMatch(/^[A-Za-z0-9+/]+={0,2}$/); + expect(secretKeyValue.length).toBeGreaterThanOrEqual(86); + expect(secretKeyValue.length).toBeLessThanOrEqual(88); + + expect(totpKeyValue).toMatch(/^[A-Za-z0-9+/]+={0,2}$/); + expect(totpKeyValue.length).toBeGreaterThanOrEqual(42); + expect(totpKeyValue.length).toBeLessThanOrEqual(44); // Check mounts expect(result.mounts).toHaveLength(1); @@ -334,8 +384,8 @@ describe("processTemplate", () => { expect(mount).toBeDefined(); if (!mount) return; expect(mount.content).toContain(mockSchema.projectName); - expect(mount.content).toMatch(/secret=[A-Za-z0-9+/]{64}/); - expect(mount.content).toMatch(/totp=[A-Za-z0-9+/]{32}/); + expect(mount.content).toMatch(/secret=[A-Za-z0-9+/]{86,88}/); + expect(mount.content).toMatch(/totp=[A-Za-z0-9+/]{42,44}/); }); }); diff --git a/apps/dokploy/components/dashboard/projects/show.tsx b/apps/dokploy/components/dashboard/projects/show.tsx index 188ee60d..b3caef7d 100644 --- a/apps/dokploy/components/dashboard/projects/show.tsx +++ b/apps/dokploy/components/dashboard/projects/show.tsx @@ -186,7 +186,7 @@ export const ShowProjects = () => { target="_blank" href={`${domain.https ? "https" : "http"}://${domain.host}${domain.path}`} > - {domain.host} + {domain.host} @@ -222,7 +222,7 @@ export const ShowProjects = () => { target="_blank" href={`${domain.https ? "https" : "http"}://${domain.host}${domain.path}`} > - {domain.host} + {domain.host} diff --git a/apps/dokploy/package.json b/apps/dokploy/package.json index ad2e494f..22fe71be 100644 --- a/apps/dokploy/package.json +++ b/apps/dokploy/package.json @@ -1,6 +1,6 @@ { "name": "dokploy", - "version": "v0.20.1", + "version": "v0.20.2", "private": true, "license": "Apache-2.0", "type": "module", diff --git a/apps/dokploy/server/api/routers/compose.ts b/apps/dokploy/server/api/routers/compose.ts index 57f04559..acb8e172 100644 --- a/apps/dokploy/server/api/routers/compose.ts +++ b/apps/dokploy/server/api/routers/compose.ts @@ -607,7 +607,7 @@ export const composeRouter = createTRPCRouter({ const processedTemplate = processTemplate(config, { serverIp: serverIp, - projectName: compose.project.name, + projectName: compose.appName, }); return { @@ -676,7 +676,7 @@ export const composeRouter = createTRPCRouter({ const processedTemplate = processTemplate(config, { serverIp: serverIp, - projectName: compose.project.name, + projectName: compose.appName, }); // Update compose file diff --git a/packages/server/src/templates/index.ts b/packages/server/src/templates/index.ts index 14ff0ac0..6ae26418 100644 --- a/packages/server/src/templates/index.ts +++ b/packages/server/src/templates/index.ts @@ -53,14 +53,12 @@ export const generatePassword = (quantity = 16): string => { }; /** - * Generate a random base64 string of specified length + * Generate a random base64 string from N random bytes + * @param bytes Number of random bytes to generate before base64 encoding (default: 32) + * @returns base64 encoded string of the random bytes */ -export function generateBase64(length: number): string { - // To get N characters in base64, we need to generate N * 3/4 bytes - const bytesNeeded = Math.ceil((length * 3) / 4); - return Buffer.from(randomBytes(bytesNeeded)) - .toString("base64") - .substring(0, length); +export function generateBase64(bytes = 32): string { + return randomBytes(bytes).toString("base64"); } export function generateJwt(length = 256): string { diff --git a/packages/server/src/templates/processors.ts b/packages/server/src/templates/processors.ts index 4320b4d0..4cf48f1d 100644 --- a/packages/server/src/templates/processors.ts +++ b/packages/server/src/templates/processors.ts @@ -45,7 +45,7 @@ export interface CompleteTemplate { variables: Record; config: { domains: DomainConfig[]; - env: Record; + env: Record | string[]; mounts?: MountConfig[]; }; } @@ -176,7 +176,6 @@ export function processDomains( schema: Schema, ): Template["domains"] { if (!template?.config?.domains) return []; - return template?.config?.domains?.map((domain: DomainConfig) => ({ ...domain, host: domain.host @@ -195,7 +194,18 @@ export function processEnvVars( ): Template["envs"] { if (!template?.config?.env) return []; - return Object.entries(template?.config?.env).map( + // Handle array of env vars + if (Array.isArray(template.config.env)) { + return template.config.env.map((env) => { + if (typeof env === "string") { + return processValue(env, variables, schema); + } + return env; + }); + } + + // Handle object of env vars + return Object.entries(template.config.env).map( ([key, value]: [string, string]) => { const processedValue = processValue(value, variables, schema); return `${key}=${processedValue}`;