Vicens Juan Tomas Monserratfix: Use the same traefik version everywhere21b16529 months agoHistory
433 lines · 10.2 KB · TypeScript
RawDownload
import {	chmodSync,	existsSync,	mkdirSync,	rmSync,	statSync,	writeFileSync,} from "node:fs";import path from "node:path";import type { ContainerCreateOptions, CreateServiceOptions } from "dockerode";import { stringify } from "yaml";import { paths } from "../constants";import { getRemoteDocker } from "../utils/servers/remote-docker";import type { FileConfig } from "../utils/traefik/file-types";import type { MainTraefikConfig } from "../utils/traefik/types"; export const TRAEFIK_SSL_PORT =	Number.parseInt(process.env.TRAEFIK_SSL_PORT!, 10) || 443;export const TRAEFIK_PORT =	Number.parseInt(process.env.TRAEFIK_PORT!, 10) || 80;export const TRAEFIK_HTTP3_PORT =	Number.parseInt(process.env.TRAEFIK_HTTP3_PORT!, 10) || 443;export const TRAEFIK_VERSION = process.env.TRAEFIK_VERSION || "3.6.7"; export interface TraefikOptions {	env?: string[];	serverId?: string;	additionalPorts?: {		targetPort: number;		publishedPort: number;		protocol?: string;	}[];} export const initializeStandaloneTraefik = async ({	env,	serverId,	additionalPorts = [],}: TraefikOptions = {}) => {	const { MAIN_TRAEFIK_PATH, DYNAMIC_TRAEFIK_PATH } = paths(!!serverId);	const imageName = `traefik:v${TRAEFIK_VERSION}`;	const containerName = "dokploy-traefik"; 	const exposedPorts: Record<string, {}> = {		[`${TRAEFIK_PORT}/tcp`]: {},		[`${TRAEFIK_SSL_PORT}/tcp`]: {},		[`${TRAEFIK_HTTP3_PORT}/udp`]: {},	}; 	const portBindings: Record<string, Array<{ HostPort: string }>> = {		[`${TRAEFIK_PORT}/tcp`]: [{ HostPort: TRAEFIK_PORT.toString() }],		[`${TRAEFIK_SSL_PORT}/tcp`]: [{ HostPort: TRAEFIK_SSL_PORT.toString() }],		[`${TRAEFIK_HTTP3_PORT}/udp`]: [			{ HostPort: TRAEFIK_HTTP3_PORT.toString() },		],	}; 	const enableDashboard = additionalPorts.some(		(port) => port.targetPort === 8080,	); 	if (enableDashboard) {		exposedPorts["8080/tcp"] = {};		portBindings["8080/tcp"] = [{ HostPort: "8080" }];	} 	for (const port of additionalPorts) {		const portKey = `${port.targetPort}/${port.protocol ?? "tcp"}`;		exposedPorts[portKey] = {};		portBindings[portKey] = [{ HostPort: port.publishedPort.toString() }];	} 	const settings: ContainerCreateOptions = {		name: containerName,		Image: imageName,		NetworkingConfig: {			EndpointsConfig: {				"dokploy-network": {},			},		},		ExposedPorts: exposedPorts,		HostConfig: {			RestartPolicy: {				Name: "always",			},			Binds: [				`${MAIN_TRAEFIK_PATH}/traefik.yml:/etc/traefik/traefik.yml`,				`${DYNAMIC_TRAEFIK_PATH}:/etc/dokploy/traefik/dynamic`,				"/var/run/docker.sock:/var/run/docker.sock",			],			PortBindings: portBindings,		},		Env: env,	}; 	const docker = await getRemoteDocker(serverId);	try {		await docker.pull(imageName);		await new Promise((resolve) => setTimeout(resolve, 3000));		console.log("Traefik Image Pulled ✅");	} catch (error) {		console.log("Traefik Image Not Found: Pulling ", error);	}	try {		const container = docker.getContainer(containerName);		await container.remove({ force: true });		await new Promise((resolve) => setTimeout(resolve, 5000));	} catch {} 	try {		await docker.createContainer(settings);		const newContainer = docker.getContainer(containerName);		await newContainer.start();		console.log("Traefik Started ✅");	} catch (error) {		console.log("Traefik Not Found: Starting ", error);	}}; export const initializeTraefikService = async ({	env,	additionalPorts = [],	serverId,}: TraefikOptions) => {	const { MAIN_TRAEFIK_PATH, DYNAMIC_TRAEFIK_PATH } = paths(!!serverId);	const imageName = `traefik:v${TRAEFIK_VERSION}`;	const appName = "dokploy-traefik"; 	const settings: CreateServiceOptions = {		Name: appName,		TaskTemplate: {			ContainerSpec: {				Image: imageName,				Env: env,				Mounts: [					{						Type: "bind",						Source: `${MAIN_TRAEFIK_PATH}/traefik.yml`,						Target: "/etc/traefik/traefik.yml",					},					{						Type: "bind",						Source: DYNAMIC_TRAEFIK_PATH,						Target: "/etc/dokploy/traefik/dynamic",					},					{						Type: "bind",						Source: "/var/run/docker.sock",						Target: "/var/run/docker.sock",					},				],			},			Networks: [{ Target: "dokploy-network" }],			Placement: {				Constraints: ["node.role==manager"],			},		},		Mode: {			Replicated: {				Replicas: 1,			},		},		EndpointSpec: {			Ports: [				{					TargetPort: 443,					PublishedPort: TRAEFIK_SSL_PORT,					PublishMode: "host",					Protocol: "tcp",				},				{					TargetPort: 443,					PublishedPort: TRAEFIK_SSL_PORT,					PublishMode: "host",					Protocol: "udp",				},				{					TargetPort: 80,					PublishedPort: TRAEFIK_PORT,					PublishMode: "host",					Protocol: "tcp",				}, 				...additionalPorts.map((port) => ({					TargetPort: port.targetPort,					PublishedPort: port.publishedPort,					Protocol: port.protocol as "tcp" | "udp" | "sctp" | undefined,					PublishMode: "host" as const,				})),			],		},	};	const docker = await getRemoteDocker(serverId);	try {		const service = docker.getService(appName);		const inspect = await service.inspect(); 		await service.update({			version: Number.parseInt(inspect.Version.Index),			...settings,			TaskTemplate: {				...settings.TaskTemplate,				ForceUpdate: inspect.Spec.TaskTemplate.ForceUpdate + 1,			},		});		console.log("Traefik Updated ✅");	} catch {		await docker.createService(settings);		console.log("Traefik Started ✅");	}}; export const createDefaultServerTraefikConfig = () => {	const { DYNAMIC_TRAEFIK_PATH } = paths();	const configFilePath = path.join(DYNAMIC_TRAEFIK_PATH, "dokploy.yml"); 	if (existsSync(configFilePath)) {		console.log("Default traefik config already exists");		return;	} 	const appName = "dokploy";	const serviceURLDefault = `http://${appName}:${process.env.PORT || 3000}`;	const config: FileConfig = {		http: {			routers: {				[`${appName}-router-app`]: {					rule: `Host(\`${appName}.docker.localhost\`) && PathPrefix(\`/\`)`,					service: `${appName}-service-app`,					entryPoints: ["web"],				},			},			services: {				[`${appName}-service-app`]: {					loadBalancer: {						servers: [{ url: serviceURLDefault }],						passHostHeader: true,					},				},			},		},	}; 	const yamlStr = stringify(config);	mkdirSync(DYNAMIC_TRAEFIK_PATH, { recursive: true });	writeFileSync(		path.join(DYNAMIC_TRAEFIK_PATH, `${appName}.yml`),		yamlStr,		"utf8",	);}; export const getDefaultTraefikConfig = () => {	const configObject: MainTraefikConfig = {		global: {			sendAnonymousUsage: false,		},		providers: {			...(process.env.NODE_ENV === "development"				? {						docker: {							defaultRule:								"Host(`{{ trimPrefix `/` .Name }}.docker.localhost`)",						},					}				: {						swarm: {							exposedByDefault: false,							watch: true,						},						docker: {							exposedByDefault: false,							watch: true,							network: "dokploy-network",						},					}),			file: {				directory: "/etc/dokploy/traefik/dynamic",				watch: true,			},		},		entryPoints: {			web: {				address: `:${TRAEFIK_PORT}`,			},			websecure: {				address: `:${TRAEFIK_SSL_PORT}`,				http3: {					advertisedPort: TRAEFIK_HTTP3_PORT,				},				...(process.env.NODE_ENV === "production" && {					http: {						tls: {							certResolver: "letsencrypt",						},					},				}),			},		},		api: {			insecure: true,		},		...(process.env.NODE_ENV === "production" && {			certificatesResolvers: {				letsencrypt: {					acme: {						email: "test@localhost.com",						storage: "/etc/dokploy/traefik/dynamic/acme.json",						httpChallenge: {							entryPoint: "web",						},					},				},			},		}),	}; 	const yamlStr = stringify(configObject); 	return yamlStr;}; export const getDefaultServerTraefikConfig = () => {	const configObject: MainTraefikConfig = {		providers: {			swarm: {				exposedByDefault: false,				watch: true,			},			docker: {				exposedByDefault: false,				watch: true,				network: "dokploy-network",			},			file: {				directory: "/etc/dokploy/traefik/dynamic",				watch: true,			},		},		entryPoints: {			web: {				address: `:${TRAEFIK_PORT}`,			},			websecure: {				address: `:${TRAEFIK_SSL_PORT}`,				http3: {					advertisedPort: TRAEFIK_HTTP3_PORT,				},				http: {					tls: {						certResolver: "letsencrypt",					},				},			},		},		api: {			insecure: true,		},		certificatesResolvers: {			letsencrypt: {				acme: {					email: "test@localhost.com",					storage: "/etc/dokploy/traefik/dynamic/acme.json",					httpChallenge: {						entryPoint: "web",					},				},			},		},	}; 	const yamlStr = stringify(configObject); 	return yamlStr;}; export const createDefaultTraefikConfig = () => {	const { MAIN_TRAEFIK_PATH, DYNAMIC_TRAEFIK_PATH } = paths();	const mainConfig = path.join(MAIN_TRAEFIK_PATH, "traefik.yml");	const acmeJsonPath = path.join(DYNAMIC_TRAEFIK_PATH, "acme.json"); 	if (existsSync(acmeJsonPath)) {		chmodSync(acmeJsonPath, "600");	} 	// Create the traefik directory first	mkdirSync(MAIN_TRAEFIK_PATH, { recursive: true }); 	// Check if traefik.yml exists and handle the case where it might be a directory	if (existsSync(mainConfig)) {		const stats = statSync(mainConfig);		if (stats.isDirectory()) {			// If traefik.yml is a directory, remove it			console.log("Found traefik.yml as directory, removing it...");			rmSync(mainConfig, { recursive: true, force: true });		} else if (stats.isFile()) {			console.log("Main config already exists");			return;		}	} 	const yamlStr = getDefaultTraefikConfig();	writeFileSync(mainConfig, yamlStr, "utf8");	console.log("Traefik config created successfully");}; export const getDefaultMiddlewares = () => {	const defaultMiddlewares = {		http: {			middlewares: {				"redirect-to-https": {					redirectScheme: {						scheme: "https",						permanent: true,					},				},			},		},	};	const yamlStr = stringify(defaultMiddlewares);	return yamlStr;};export const createDefaultMiddlewares = () => {	const { DYNAMIC_TRAEFIK_PATH } = paths();	const middlewaresPath = path.join(DYNAMIC_TRAEFIK_PATH, "middlewares.yml");	if (existsSync(middlewaresPath)) {		console.log("Default middlewares already exists");		return;	}	const yamlStr = getDefaultMiddlewares();	mkdirSync(DYNAMIC_TRAEFIK_PATH, { recursive: true });	writeFileSync(middlewaresPath, yamlStr, "utf8");};