mirror of
https://github.com/Dokploy/dokploy
synced 2025-06-26 18:27:59 +00:00
Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
47db6831b4 | ||
|
|
56cbd1abb3 | ||
|
|
cb40ac5c6b | ||
|
|
7218b3f79b | ||
|
|
19ea4d3fcd | ||
|
|
6edfd1e547 | ||
|
|
666a8ede97 | ||
|
|
08e4b8fe33 | ||
|
|
5fc265d14f | ||
|
|
c3887af5d1 | ||
|
|
a6684af57e | ||
|
|
8df2b20c3b | ||
|
|
f159dc11eb | ||
|
|
fce22ec1d0 | ||
|
|
e63eed57dd | ||
|
|
acc8ce80ad | ||
|
|
e317772367 |
@@ -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="<INSERT 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="<INSERT 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}/);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -186,7 +186,7 @@ export const ShowProjects = () => {
|
||||
target="_blank"
|
||||
href={`${domain.https ? "https" : "http"}://${domain.host}${domain.path}`}
|
||||
>
|
||||
<span>{domain.host}</span>
|
||||
<span className="truncate">{domain.host}</span>
|
||||
<ExternalLinkIcon className="size-4 shrink-0" />
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
@@ -222,7 +222,7 @@ export const ShowProjects = () => {
|
||||
target="_blank"
|
||||
href={`${domain.https ? "https" : "http"}://${domain.host}${domain.path}`}
|
||||
>
|
||||
<span>{domain.host}</span>
|
||||
<span className="truncate">{domain.host}</span>
|
||||
<ExternalLinkIcon className="size-4 shrink-0" />
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "dokploy",
|
||||
"version": "v0.20.0",
|
||||
"version": "v0.20.2",
|
||||
"private": true,
|
||||
"license": "Apache-2.0",
|
||||
"type": "module",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -546,7 +546,7 @@ export const createTraefikInstance = () => {
|
||||
if docker service inspect dokploy-traefik > /dev/null 2>&1; then
|
||||
echo "Migrating Traefik to Standalone..."
|
||||
docker service rm dokploy-traefik
|
||||
sleep 7
|
||||
sleep 8
|
||||
echo "Traefik migrated to Standalone ✅"
|
||||
fi
|
||||
|
||||
|
||||
@@ -91,9 +91,26 @@ export const initializeTraefik = async ({
|
||||
try {
|
||||
const service = docker.getService("dokploy-traefik");
|
||||
await service?.remove({ force: true });
|
||||
await new Promise((resolve) => setTimeout(resolve, 5000));
|
||||
} catch (_) {}
|
||||
|
||||
let attempts = 0;
|
||||
const maxAttempts = 5;
|
||||
while (attempts < maxAttempts) {
|
||||
try {
|
||||
await docker.listServices({
|
||||
filters: { name: ["dokploy-traefik"] },
|
||||
});
|
||||
console.log("Waiting for service cleanup...");
|
||||
await new Promise((resolve) => setTimeout(resolve, 5000));
|
||||
attempts++;
|
||||
} catch (e) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("No existing service to remove");
|
||||
}
|
||||
|
||||
// Then try to remove any existing container
|
||||
const container = docker.getContainer(containerName);
|
||||
try {
|
||||
const inspect = await container.inspect();
|
||||
@@ -103,15 +120,31 @@ export const initializeTraefik = async ({
|
||||
}
|
||||
|
||||
await container.remove({ force: true });
|
||||
await new Promise((resolve) => setTimeout(resolve, 5000));
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
console.log("No existing container to remove");
|
||||
}
|
||||
|
||||
await docker.createContainer(settings);
|
||||
const newContainer = docker.getContainer(containerName);
|
||||
await newContainer.start();
|
||||
// Create and start the new container
|
||||
try {
|
||||
await docker.createContainer(settings);
|
||||
const newContainer = docker.getContainer(containerName);
|
||||
await newContainer.start();
|
||||
console.log("Traefik container started successfully");
|
||||
} catch (error: any) {
|
||||
if (error?.json?.message?.includes("port is already allocated")) {
|
||||
console.log("Ports still in use, waiting longer for cleanup...");
|
||||
await new Promise((resolve) => setTimeout(resolve, 10000));
|
||||
// Try one more time
|
||||
await docker.createContainer(settings);
|
||||
const newContainer = docker.getContainer(containerName);
|
||||
await newContainer.start();
|
||||
console.log("Traefik container started successfully after retry");
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
console.error("Failed to initialize Traefik:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -45,7 +45,7 @@ export interface CompleteTemplate {
|
||||
variables: Record<string, string>;
|
||||
config: {
|
||||
domains: DomainConfig[];
|
||||
env: Record<string, string>;
|
||||
env: Record<string, string> | string[];
|
||||
mounts?: MountConfig[];
|
||||
};
|
||||
}
|
||||
@@ -175,7 +175,8 @@ export function processDomains(
|
||||
variables: Record<string, string>,
|
||||
schema: Schema,
|
||||
): Template["domains"] {
|
||||
return template.config.domains.map((domain: DomainConfig) => ({
|
||||
if (!template?.config?.domains) return [];
|
||||
return template?.config?.domains?.map((domain: DomainConfig) => ({
|
||||
...domain,
|
||||
host: domain.host
|
||||
? processValue(domain.host, variables, schema)
|
||||
@@ -191,6 +192,19 @@ export function processEnvVars(
|
||||
variables: Record<string, string>,
|
||||
schema: Schema,
|
||||
): Template["envs"] {
|
||||
if (!template?.config?.env) return [];
|
||||
|
||||
// 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);
|
||||
@@ -207,9 +221,9 @@ export function processMounts(
|
||||
variables: Record<string, string>,
|
||||
schema: Schema,
|
||||
): Template["mounts"] {
|
||||
if (!template.config.mounts) return [];
|
||||
if (!template?.config?.mounts) return [];
|
||||
|
||||
return template.config.mounts.map((mount: MountConfig) => ({
|
||||
return template?.config?.mounts?.map((mount: MountConfig) => ({
|
||||
filePath: processValue(mount.filePath, variables, schema),
|
||||
content: processValue(mount.content, variables, schema),
|
||||
}));
|
||||
|
||||
@@ -212,6 +212,5 @@ export const keepLatestNBackups = async (
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user