369 lines · 9.0 KB · TypeScript
RawDownload
import { db } from "@dokploy/server/db";import { type apiCreateNetwork, network } from "@dokploy/server/db/schema";import { TRPCError } from "@trpc/server";import { and, eq, inArray, isNull } from "drizzle-orm";import type { z } from "zod";import { IS_CLOUD } from "../constants";import type { ApplicationNested } from "../utils/builders";import { getRemoteDocker } from "../utils/servers/remote-docker"; const RESERVED_NETWORKS = [	"bridge",	"host",	"none",	"ingress",	"docker_gwbridge",	"dokploy-network",]; type DockerNetworkInfo = {	Name: string;	Driver: string;	Internal?: boolean;	Attachable?: boolean;	Ingress?: boolean;	EnableIPv4?: boolean;	EnableIPv6?: boolean;	Options?: Record<string, string> | null;	IPAM?: {		Driver?: string;		Config?: Array<{			Subnet?: string;			Gateway?: string;			IPRange?: string;		}> | null;	};}; const parseMtu = (value: string | undefined) => {	const mtu = Number.parseInt(value ?? "", 10);	return Number.isNaN(mtu) ? null : mtu;}; const isImportableDockerNetwork = (dockerNetwork: DockerNetworkInfo) =>	!RESERVED_NETWORKS.includes(dockerNetwork.Name) &&	!dockerNetwork.Ingress &&	(dockerNetwork.Driver === "bridge" || dockerNetwork.Driver === "overlay"); const mapDockerNetworkToRow = (	dockerNetwork: DockerNetworkInfo,	organizationId: string,	serverId: string | null,) => ({	name: dockerNetwork.Name,	driver: dockerNetwork.Driver as "bridge" | "overlay",	internal: dockerNetwork.Internal ?? false,	attachable: dockerNetwork.Attachable ?? false,	// Older daemons don't report EnableIPv4; IPv4 is always on there	enableIPv4: dockerNetwork.EnableIPv4 ?? true,	enableIPv6: dockerNetwork.EnableIPv6 ?? false,	mtu: parseMtu(dockerNetwork.Options?.["com.docker.network.driver.mtu"]),	ipam: {		driver: dockerNetwork.IPAM?.Driver,		config: (dockerNetwork.IPAM?.Config ?? []).map((c) => ({			subnet: c.Subnet,			gateway: c.Gateway,			ipRange: c.IPRange,		})),	},	organizationId,	serverId,}); const findNetworksByServer = async (	organizationId: string,	serverId: string | null,) => {	return await db.query.network.findMany({		where: and(			eq(network.organizationId, organizationId),			serverId ? eq(network.serverId, serverId) : isNull(network.serverId),		),	});}; export const findNetworksToSync = async (	organizationId: string,	serverId: string | null,) => {	if (IS_CLOUD && !serverId) {		throw new TRPCError({			code: "BAD_REQUEST",			message: "Server is required",		});	} 	const docker = await getRemoteDocker(serverId);	let dockerNetworks: DockerNetworkInfo[] = [];	try {		dockerNetworks = (await docker.listNetworks()) as DockerNetworkInfo[];	} catch (error) {		throw new TRPCError({			code: "BAD_REQUEST",			message:				error instanceof Error					? error.message					: "Failed to list Docker networks",			cause: error,		});	} 	const existing = await findNetworksByServer(organizationId, serverId);	const existingNames = new Set(existing.map((row) => row.name));	const dockerNames = new Set(dockerNetworks.map((d) => d.Name)); 	const importable = dockerNetworks		.filter(			(dockerNetwork) =>				isImportableDockerNetwork(dockerNetwork) &&				!existingNames.has(dockerNetwork.Name),		)		.map((dockerNetwork) => ({			name: dockerNetwork.Name,			driver: dockerNetwork.Driver,			internal: dockerNetwork.Internal ?? false,			attachable: dockerNetwork.Attachable ?? false,			subnets: (dockerNetwork.IPAM?.Config ?? [])				.map((c) => c.Subnet)				.filter((s): s is string => !!s),		})); 	// Rows in Dokploy whose network no longer exists in Docker	const missing = existing		.filter((row) => !dockerNames.has(row.name))		.map((row) => ({ networkId: row.networkId, name: row.name })); 	return { importable, missing };}; export const importDockerNetworks = async (	organizationId: string,	serverId: string | null,	names: string[],) => {	if (IS_CLOUD && !serverId) {		throw new TRPCError({			code: "BAD_REQUEST",			message: "Server is required",		});	} 	const docker = await getRemoteDocker(serverId);	const existing = await findNetworksByServer(organizationId, serverId);	const existingNames = new Set(existing.map((row) => row.name)); 	const imported: string[] = [];	const errors: { name: string; error: string }[] = []; 	for (const name of names) {		if (existingNames.has(name)) {			errors.push({ name, error: "Already imported" });			continue;		}		try {			const info = (await docker				.getNetwork(name)				.inspect()) as DockerNetworkInfo;			if (!isImportableDockerNetwork(info)) {				errors.push({ name, error: "Network is reserved or not supported" });				continue;			}			await db				.insert(network)				.values(mapDockerNetworkToRow(info, organizationId, serverId));			imported.push(name);		} catch (error) {			errors.push({				name,				error:					error instanceof Error ? error.message : "Failed to inspect network",			});		}	} 	return { imported, errors };}; export const findNetworkById = async (networkId: string) => {	const [row] = await db		.select()		.from(network)		.where(eq(network.networkId, networkId))		.limit(1); 	if (!row) {		throw new TRPCError({			code: "NOT_FOUND",			message: "Network not found",		});	} 	return row;}; export const createNetwork = async (	input: z.infer<typeof apiCreateNetwork>,	organizationId: string,) => {	if (IS_CLOUD) {		if (!input.serverId) {			throw new TRPCError({				code: "BAD_REQUEST",				message: "Server is required",			});		}	} 	const created = await db.transaction(async (tx) => {		const [row] = await tx			.insert(network)			.values({				...input,				organizationId,			})			.returning(); 		if (!row) {			throw new TRPCError({				code: "INTERNAL_SERVER_ERROR",				message: "Failed to create network",			});		} 		await createDockerNetworkFromRow(row); 		return row;	}); 	return created;}; const createDockerNetworkFromRow = async (row: typeof network.$inferSelect) => {	const ipam = row.ipam ?? {};	const ipamConfig = (ipam.config ?? [])		.map((c) => {			const entry: Record<string, string> = {};			if (c.subnet) entry.Subnet = c.subnet;			if (c.gateway) entry.Gateway = c.gateway;			if (c.ipRange) entry.IPRange = c.ipRange;			return entry;		})		.filter((e) => Object.keys(e).length > 0); 	const docker = await getRemoteDocker(row.serverId ?? null);	try {		await docker.createNetwork({			Name: row.name,			Driver: row.driver,			CheckDuplicate: true,			Internal: row.internal,			Attachable: row.attachable,			// EnableIPv4 is missing from dockerode's types but supported by			// the daemon (API >= 1.47); the body is sent as-is			EnableIPv4: row.enableIPv4,			EnableIPv6: row.enableIPv6,			Options: row.mtu				? { "com.docker.network.driver.mtu": String(row.mtu) }				: undefined,			IPAM: {				Driver: ipam.driver || "default",				Config: ipamConfig.length > 0 ? ipamConfig : undefined,			},		} as Parameters<typeof docker.createNetwork>[0]);	} catch (error) {		throw new TRPCError({			code: "BAD_REQUEST",			message:				error instanceof Error					? error.message					: "Failed to create Docker network",			cause: error,		});	}}; export const recreateNetwork = async (networkId: string) => {	const row = await findNetworkById(networkId);	await createDockerNetworkFromRow(row);	return row;}; export const resolveServiceNetworks = async (	application: Partial<ApplicationNested>,) => {	if (application.networkSwarm) {		return application.networkSwarm;	} 	const { networkIds, detachDokployNetwork } = application;	const rows =		networkIds && networkIds.length > 0			? await db.query.network.findMany({					where: and(						inArray(network.networkId, networkIds),						eq(network.driver, "overlay"),					),					columns: { name: true },				})			: []; 	const networks = detachDokployNetwork ? [] : ["dokploy-network"];	for (const row of rows) {		networks.push(row.name);	} 	return networks.map((name) => ({ Target: name }));}; export const inspectNetwork = async (networkId: string) => {	const row = await findNetworkById(networkId); 	const docker = await getRemoteDocker(row.serverId ?? null);	try {		return await docker.getNetwork(row.name).inspect();	} catch (error) {		throw new TRPCError({			code: "BAD_REQUEST",			message:				error instanceof Error					? error.message					: "Failed to inspect Docker network",			cause: error,		});	}}; export const removeNetwork = async (networkId: string) => {	const row = await findNetworkById(networkId); 	const docker = await getRemoteDocker(row.serverId ?? null);	try {		await docker.getNetwork(row.name).remove();	} catch (error) {		const statusCode = (error as { statusCode?: number })?.statusCode;		if (statusCode !== 404) {			throw new TRPCError({				code: "BAD_REQUEST",				message:					error instanceof Error						? error.message						: "Failed to remove Docker network",				cause: error,			});		}	} 	const [deleted] = await db		.delete(network)		.where(eq(network.networkId, networkId))		.returning(); 	if (!deleted) {		throw new TRPCError({			code: "NOT_FOUND",			message: "Network not found",		});	} 	return deleted;};

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t