787 lines · 24.9 KB · TypeScript
RawDownload
import path from "node:path";import { IS_CLOUD, paths } from "@dokploy/server/constants";import { getDokployUrl } from "@dokploy/server/services/admin";import {	createServerDeployment,	updateDeploymentStatus,} from "@dokploy/server/services/deployment";import {	findServerById,	updateServerById,} from "@dokploy/server/services/server";import {	getDefaultMiddlewares,	getDefaultServerTraefikConfig,	TRAEFIK_HTTP3_PORT,	TRAEFIK_PORT,	TRAEFIK_SSL_PORT,	TRAEFIK_VERSION,} from "@dokploy/server/setup/traefik-setup";import slug from "slugify";import { Client } from "ssh2";import { recreateDirectory } from "../utils/filesystem/directory";import { setupMonitoring } from "./monitoring-setup"; const generateToken = () => {	const array = new Uint8Array(64);	crypto.getRandomValues(array);	return Array.from(array, (byte) => byte.toString(16).padStart(2, "0")).join(		"",	);}; export const slugify = (text: string | undefined) => {	if (!text) {		return "";	} 	const cleanedText = text.trim().replace(/[^a-zA-Z0-9\s]/g, ""); 	return slug(cleanedText, {		lower: true,		trim: true,		strict: true,	});}; export const serverSetup = async (	serverId: string,	onData?: (data: any) => void,) => {	const server = await findServerById(serverId);	const { LOGS_PATH } = paths(); 	const slugifyName = slugify(`server ${server.name}`); 	const fullPath = path.join(LOGS_PATH, slugifyName); 	await recreateDirectory(fullPath); 	const deployment = await createServerDeployment({		serverId: server.serverId,		title: "Setup Server",		description: "Setup Server",	}); 	try {		const isBuildServer = server.serverType === "build";		onData?.(			isBuildServer				? "\nInstalling Build Server Dependencies: ✅\n"				: "\nInstalling Server Dependencies: ✅\n",		);		await installRequirements(serverId, onData); 		if (IS_CLOUD) {			onData?.("\nConfiguring Monitoring: 🔄\n"); 			const baseUrl = await getDokployUrl();			const token = generateToken();			const urlCallback = `${baseUrl}/api/trpc/notification.receiveNotification`; 			// Update server with monitoring configuration			await updateServerById(serverId, {				metricsConfig: {					server: {						...server.metricsConfig.server,						token: token,						urlCallback: urlCallback,						cronJob: server.metricsConfig.server.cronJob || "0 0 * * *",					},					containers: server.metricsConfig.containers,				},			}); 			await setupMonitoring(serverId);			onData?.("\nMonitoring Configured: ✅\n");		} 		await updateDeploymentStatus(deployment.deploymentId, "done"); 		onData?.("\nSetup Server: ✅\n");	} catch (err) {		console.log(err); 		await updateDeploymentStatus(deployment.deploymentId, "error");		onData?.(`${err} ❌\n`);	}}; export const reportDockerVersion = () => `if command -v docker >/dev/null 2>&1; then	INSTALLED_DOCKER_VERSION=$(docker version --format '{{.Server.Version}}' 2>/dev/null || true)	if [ -z "$INSTALLED_DOCKER_VERSION" ]; then		INSTALLED_DOCKER_VERSION=$(docker --version 2>/dev/null | awk '{print $3}' | tr -d ',' || true)	fi	if [ -z "$INSTALLED_DOCKER_VERSION" ]; then		INSTALLED_DOCKER_VERSION="unknown"	fi	DOCKER_VERSION_REPORT="$INSTALLED_DOCKER_VERSION (already installed)"else	DOCKER_VERSION_REPORT="$DOCKER_VERSION (will be installed)"fi`; export const defaultCommand = (isBuildServer = false) => {	const bashCommand = `set -e;DOCKER_VERSION=28.5.0OS_TYPE=$(grep -w "ID" /etc/os-release | cut -d "=" -f 2 | tr -d '"')SYS_ARCH=$(uname -m)CURRENT_USER=$USER echo "Installing requirements for: OS: $OS_TYPE" # Auto-detect sudo requirementif [ "$EUID" -eq 0 ]; then	SUDO_CMD=""	echo "Running as root"else	if sudo -n true 2>/dev/null; then		SUDO_CMD="sudo"		echo "Running as $CURRENT_USER with sudo privileges"	else		echo "Error: Non-root user requires passwordless sudo access. ❌"		echo "Configure with: echo '$CURRENT_USER ALL=(ALL) NOPASSWD:ALL' | sudo tee /etc/sudoers.d/$CURRENT_USER"		exit 1	fifi # Check if the OS is manjaro, if so, change it to archif [ "$OS_TYPE" = "manjaro" ] || [ "$OS_TYPE" = "manjaro-arm" ]; then	OS_TYPE="arch"fi # Check if the OS is Asahi Linux, if so, change it to fedoraif [ "$OS_TYPE" = "fedora-asahi-remix" ]; then	OS_TYPE="fedora"fi # Check if the OS is popOS, if so, change it to ubuntuif [ "$OS_TYPE" = "pop" ]; then	OS_TYPE="ubuntu"fi # Check if the OS is linuxmint, if so, change it to ubuntuif [ "$OS_TYPE" = "linuxmint" ]; then	OS_TYPE="ubuntu"fi #Check if the OS is zorin, if so, change it to ubuntuif [ "$OS_TYPE" = "zorin" ]; then	OS_TYPE="ubuntu"fi if [ "$OS_TYPE" = "arch" ] || [ "$OS_TYPE" = "archarm" ]; then	OS_VERSION="rolling"else	OS_VERSION=$(grep -w "VERSION_ID" /etc/os-release | cut -d "=" -f 2 | tr -d '"')fi # Ubuntu 26.04 (resolute) ships a docker-ce repo that has no 28.5.0, so the# default pin is absent from apt-cache madison and get.docker.com aborts before# installing Docker. Repin to a version present on every architecture Docker# ships resolute for: amd64/arm64/armhf start at 29.3.1, but s390x only carries# 29.4.0-29.4.2, so 29.4.2 is the newest version common to all of them. This# must run after OS_VERSION is resolved and before the banner so the reported# Docker version stays accurate.if [ "$OS_TYPE" = "ubuntu" ] && [ "$OS_VERSION" = "26.04" ]; then	DOCKER_VERSION=29.4.2fi if [ "$OS_TYPE" = 'amzn' ]; then    $SUDO_CMD dnf install -y findutils >/dev/nullfi case "$OS_TYPE" inarch | ubuntu | debian | raspbian | centos | fedora | rhel | ol | rocky | sles | opensuse-leap | opensuse-tumbleweed | almalinux | opencloudos | amzn | alpine) ;;*)	echo "This script only supports Debian, Redhat, Arch Linux, Alpine Linux, or SLES based operating systems for now."	exit	;;esac ${reportDockerVersion()}echo -e "---------------------------------------------"echo "| CPU Architecture  | $SYS_ARCH"echo "| Operating System  | $OS_TYPE $OS_VERSION"echo "| Docker            | $DOCKER_VERSION_REPORT"${isBuildServer ? 'echo "| Server Type       | Build Server"' : ""}echo -e "---------------------------------------------\n"echo -e "1. Installing required packages (curl, wget, git, jq, openssl). " command_exists() {	command -v "$@" > /dev/null 2>&1} ${installUtilities()} ${	!isBuildServer		? `echo -e "2. Validating ports. "${validatePorts()}   echo -e "3. Installing RClone. "${installRClone()} echo -e "4. Installing Docker. "${installDocker()} echo -e "5. Setting up Docker Swarm"${setupSwarm()} echo -e "6. Setting up Network"${setupNetwork()} echo -e "7. Setting up Directories"${setupMainDirectory()}${setupDirectories()} echo -e "8. Setting up Traefik"${createTraefikConfig()} echo -e "9. Setting up Middlewares"${createDefaultMiddlewares()} echo -e "10. Setting up Traefik Instance"${createTraefikInstance()} echo -e "11. Installing Nixpacks"${installNixpacks()} echo -e "12. Installing Buildpacks"${installBuildpacks()} echo -e "13. Installing Railpack"${installRailpack()} echo -e "14. Configuring permissions"${setupPermissions()}`		: `echo -e "2. Installing Docker. "${installDocker()} echo -e "3. Setting up Directories"${setupMainDirectory()}${setupDirectories()} echo -e "4. Installing Nixpacks"${installNixpacks()} echo -e "5. Installing Buildpacks"${installBuildpacks()} echo -e "6. Installing Railpack"${installRailpack()} echo -e "7. Configuring permissions"${setupPermissions()}`}				`; 	return bashCommand;}; const installRequirements = async (	serverId: string,	onData?: (data: any) => void,) => {	const client = new Client();	const server = await findServerById(serverId);	if (!server.sshKeyId) {		onData?.("❌ No SSH Key found, please assign one to this server");		throw new Error("No SSH Key found");	} 	const isBuildServer = server.serverType === "build"; 	return new Promise<void>((resolve, reject) => {		client			.once("ready", () => {				const command = server.command || defaultCommand(isBuildServer);				client.exec(command, (err, stream) => {					if (err) {						onData?.(err.message);						reject(err);						return;					}					stream						.on("close", () => {							client.end();							resolve();						})						.on("data", (data: string) => {							onData?.(data.toString());						})						.stderr.on("data", (data) => {							onData?.(data.toString());						});				});			})			.on("error", (err) => {				client.end();				if (err.level === "client-authentication") {					const technicalDetail = `Error: ${err.message} ${err.level}`;					const friendlyMessage = [						"",						"❌ Couldn't connect to your server — the SSH key was not accepted.",						"",						"This usually means the key doesn't match what's on the server, or the key format is invalid.",						"",						`Technical details: ${technicalDetail}`,						"",						"💡 Hints:",						"  • Check that the SSH key you added in Dokploy is the same one installed on the server (e.g. in ~/.ssh/authorized_keys).",						"  • Try generating a new SSH key in Dokploy and add only the public key to the server, then try again.",						"  • Make sure to follow the instructions on the Setup Server Button on the SSH Keys tab",					].join("\n");					onData?.(friendlyMessage);					reject(						new Error(							`Authentication failed: Invalid SSH private key. ${technicalDetail}`,						),					);				} else {					const technicalDetail = `${err.message} ${err.level ?? ""}`.trim();					const friendlyMessage = [						"",						"❌ Couldn't connect to your server.",						"",						"The connection failed before setup could run. Common causes: wrong IP or port, firewall blocking access, or the server is offline.",						"",						`Technical details: ${technicalDetail}`,						"",						"💡 Hints:",						"  • Check that the server IP address and SSH port are correct and the server is powered on.",						"  • If the server is in a private network, ensure Dokploy can reach it (VPN, firewall rules, or correct security groups).",						"  • Make sure the SSH port (usually 22) is open and the SSH service is running on the server.",					].join("\n");					onData?.(friendlyMessage);					reject(new Error(`SSH connection error: ${technicalDetail}`));				}			})			.connect({				host: server.ipAddress,				port: server.port,				username: server.username,				privateKey: server.sshKey?.privateKey,			});	});}; const setupDirectories = () => {	const { SSH_PATH } = paths(true);	const directories = Object.values(paths(true)); 	const createDirsCommand = directories		.map((dir) => `mkdir -p "${dir}"`)		.join(" && ");	const chmodCommand = `chmod 700 "${SSH_PATH}"`; 	const command = `	${createDirsCommand}	${chmodCommand}	`; 	return command;}; const setupMainDirectory = () => `	# Check if the /etc/dokploy directory exists	if [ -d /etc/dokploy ]; then		echo "/etc/dokploy already exists ✅"	else		# Create the /etc/dokploy directory		$SUDO_CMD mkdir -p /etc/dokploy		echo "Directory /etc/dokploy created ✅"	fi	# Ensure the current user owns the directory	if [ -n "$SUDO_CMD" ]; then		$SUDO_CMD chown -R $CURRENT_USER:$CURRENT_USER /etc/dokploy	fi`; export const setupSwarm = () => `		# Check if the node is already part of a Docker Swarm		if $SUDO_CMD docker info | grep -q 'Swarm: active'; then			echo "Already part of a Docker Swarm ✅"		else			# Get IP address			get_ip() {				local ip="" 				# Try IPv4 with multiple services				# First attempt: ifconfig.io				ip=\$(curl -4s --connect-timeout 5 https://ifconfig.io 2>/dev/null) 				# Second attempt: icanhazip.com				if [ -z "\$ip" ]; then					ip=\$(curl -4s --connect-timeout 5 https://icanhazip.com 2>/dev/null)				fi 				# Third attempt: ipecho.net				if [ -z "\$ip" ]; then					ip=\$(curl -4s --connect-timeout 5 https://ipecho.net/plain 2>/dev/null)				fi 				# If no IPv4, try IPv6 with multiple services				if [ -z "\$ip" ]; then					# Try IPv6 with ifconfig.io					ip=\$(curl -6s --connect-timeout 5 https://ifconfig.io 2>/dev/null) 					# Try IPv6 with icanhazip.com					if [ -z "\$ip" ]; then						ip=\$(curl -6s --connect-timeout 5 https://icanhazip.com 2>/dev/null)					fi 					# Try IPv6 with ipecho.net					if [ -z "\$ip" ]; then						ip=\$(curl -6s --connect-timeout 5 https://ipecho.net/plain 2>/dev/null)					fi				fi 				if [ -z "\$ip" ]; then					echo "Error: Could not determine server IP address automatically (neither IPv4 nor IPv6)." >&2					echo "Please set the ADVERTISE_ADDR environment variable manually." >&2					echo "Example: export ADVERTISE_ADDR=<your-server-ip>" >&2					exit 1				fi 				echo "\$ip"			}			advertise_addr=\$(get_ip)			echo "Advertise address: \$advertise_addr" 			# Initialize Docker Swarm			$SUDO_CMD docker swarm init --advertise-addr \$advertise_addr			echo "Swarm initialized ✅"		fi	`; const setupNetwork = () => `	# Check if the dokploy-network already exists	if $SUDO_CMD docker network ls | grep -q 'dokploy-network'; then		echo "Network dokploy-network already exists ✅"	else		# Create the dokploy-network if it doesn't exist		if $SUDO_CMD docker network create --driver overlay --attachable dokploy-network; then			echo "Network created ✅"		else			echo "Failed to create dokploy-network ❌" >&2			exit 1		fi	fi`; const validatePorts = () => `	# check if something is running on port 80	if ss -tulnp | grep ':80 ' >/dev/null; then		echo "Something is already running on port 80" >&2	fi 	# check if something is running on port 443	if ss -tulnp | grep ':443 ' >/dev/null; then		echo "Something is already running on port 443" >&2	fi`; const installUtilities = () => ` 	case "$OS_TYPE" in	arch)		$SUDO_CMD pacman -Sy --noconfirm --needed unzip curl wget git git-lfs jq openssl >/dev/null || true		;;	alpine)		$SUDO_CMD sed -i '/^#.*\/community/s/^#//' /etc/apk/repositories

Lines 1–500 of 787.