mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-07-12 09:25:24 +02:00
feat: add redis installation on server and deploy applications on servers
This commit is contained in:
@@ -57,3 +57,44 @@ export const buildCustomDocker = async (
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const getDockerCommand = (
|
||||
application: ApplicationNested,
|
||||
logPath: string,
|
||||
) => {
|
||||
const { appName, env, publishDirectory, buildArgs, dockerBuildStage } =
|
||||
application;
|
||||
const dockerFilePath = getBuildAppDirectory(application);
|
||||
|
||||
try {
|
||||
const image = `${appName}`;
|
||||
|
||||
const defaultContextPath =
|
||||
dockerFilePath.substring(0, dockerFilePath.lastIndexOf("/") + 1) || ".";
|
||||
const args = prepareEnvironmentVariables(buildArgs);
|
||||
|
||||
const dockerContextPath =
|
||||
getDockerContextPath(application) || defaultContextPath;
|
||||
|
||||
const commandArgs = ["build", "-t", image, "-f", dockerFilePath, "."];
|
||||
|
||||
if (dockerBuildStage) {
|
||||
commandArgs.push("--target", dockerBuildStage);
|
||||
}
|
||||
|
||||
for (const arg of args) {
|
||||
commandArgs.push("--build-arg", arg);
|
||||
}
|
||||
|
||||
const command = `
|
||||
echo "Building ${appName}" >> ${logPath};
|
||||
cd ${dockerContextPath} || exit 1;
|
||||
docker ${commandArgs.join(" ")} >> ${logPath} 2>&1;
|
||||
echo "Docker build completed." >> ${logPath};
|
||||
`;
|
||||
|
||||
return command;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -38,3 +38,35 @@ export const buildHeroku = async (
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
|
||||
export const getHerokuCommand = (
|
||||
application: ApplicationNested,
|
||||
logPath: string,
|
||||
) => {
|
||||
const { env, appName } = application;
|
||||
|
||||
const buildAppDirectory = getBuildAppDirectory(application);
|
||||
const envVariables = prepareEnvironmentVariables(env);
|
||||
|
||||
const args = [
|
||||
"build",
|
||||
appName,
|
||||
"--path",
|
||||
buildAppDirectory,
|
||||
"--builder",
|
||||
"heroku/builder:24",
|
||||
];
|
||||
|
||||
for (const env of envVariables) {
|
||||
args.push("--env", env);
|
||||
}
|
||||
|
||||
const command = `pack ${args.join(" ")}`;
|
||||
const bashCommand = `
|
||||
echo "Starting heroku build..." >> ${logPath};
|
||||
${command} >> ${logPath} 2>&1;
|
||||
echo "Heroku build completed." >> ${logPath};
|
||||
`;
|
||||
|
||||
return bashCommand;
|
||||
};
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { createWriteStream } from "node:fs";
|
||||
import { docker } from "@/server/constants";
|
||||
// import { docker } from "@/server/constants";
|
||||
import type { InferResultType } from "@/server/types/with";
|
||||
import type { CreateServiceOptions } from "dockerode";
|
||||
import { uploadImage } from "../cluster/upload";
|
||||
import Dockerode from "dockerode";
|
||||
import {
|
||||
calculateResources,
|
||||
generateBindMounts,
|
||||
@@ -11,11 +12,14 @@ import {
|
||||
generateVolumeMounts,
|
||||
prepareEnvironmentVariables,
|
||||
} from "../docker/utils";
|
||||
import { buildCustomDocker } from "./docker-file";
|
||||
import { buildHeroku } from "./heroku";
|
||||
import { buildNixpacks } from "./nixpacks";
|
||||
import { buildPaketo } from "./paketo";
|
||||
import { buildCustomDocker, getDockerCommand } from "./docker-file";
|
||||
import { buildHeroku, getHerokuCommand } from "./heroku";
|
||||
import { buildNixpacks, getNixpacksCommand } from "./nixpacks";
|
||||
import { buildPaketo, getPaketoCommand } from "./paketo";
|
||||
import { buildStatic } from "./static";
|
||||
import { findServerById } from "@/server/api/services/server";
|
||||
import { readSSHKey } from "../filesystem/ssh";
|
||||
import { getRemoteDocker } from "../servers/remote-docker";
|
||||
|
||||
// NIXPACKS codeDirectory = where is the path of the code directory
|
||||
// HEROKU codeDirectory = where is the path of the code directory
|
||||
@@ -65,6 +69,25 @@ export const buildApplication = async (
|
||||
}
|
||||
};
|
||||
|
||||
export const getBuildCommand = (
|
||||
application: ApplicationNested,
|
||||
logPath: string,
|
||||
) => {
|
||||
const { buildType } = application;
|
||||
switch (buildType) {
|
||||
case "nixpacks":
|
||||
return getNixpacksCommand(application, logPath);
|
||||
case "heroku_buildpacks":
|
||||
return getHerokuCommand(application, logPath);
|
||||
case "paketo_buildpacks":
|
||||
return getPaketoCommand(application, logPath);
|
||||
// case "static":
|
||||
// return buildStatic(application, writeStream);
|
||||
case "dockerfile":
|
||||
return getDockerCommand(application, logPath);
|
||||
}
|
||||
};
|
||||
|
||||
export const mechanizeDockerContainer = async (
|
||||
application: ApplicationNested,
|
||||
) => {
|
||||
@@ -106,6 +129,21 @@ export const mechanizeDockerContainer = async (
|
||||
|
||||
const image = getImageName(application);
|
||||
const authConfig = getAuthConfig(application);
|
||||
const docker = await getRemoteDocker(application.serverId);
|
||||
// const server = await findServerById(application.serverId);
|
||||
// if (!server.sshKeyId) return;
|
||||
// const keys = await readSSHKey(server.sshKeyId);
|
||||
// const docker = new Dockerode({
|
||||
// host: server.ipAddress,
|
||||
// port: server.port,
|
||||
// username: server.username,
|
||||
// protocol: "ssh",
|
||||
// sshOptions: {
|
||||
// privateKey: keys.privateKey,
|
||||
// },
|
||||
// });
|
||||
// const results = await docker2.listContainers();
|
||||
// console.log(results);
|
||||
|
||||
const settings: CreateServiceOptions = {
|
||||
authconfig: authConfig,
|
||||
|
||||
@@ -6,12 +6,13 @@ import type { ApplicationNested } from ".";
|
||||
import { prepareEnvironmentVariables } from "../docker/utils";
|
||||
import { getBuildAppDirectory } from "../filesystem/directory";
|
||||
import { spawnAsync } from "../process/spawnAsync";
|
||||
import { executeCommand } from "../servers/command";
|
||||
|
||||
export const buildNixpacks = async (
|
||||
application: ApplicationNested,
|
||||
writeStream: WriteStream,
|
||||
) => {
|
||||
const { env, appName, publishDirectory } = application;
|
||||
const { env, appName, publishDirectory, serverId } = application;
|
||||
|
||||
const buildAppDirectory = getBuildAppDirectory(application);
|
||||
const buildContainerId = `${appName}-${nanoid(10)}`;
|
||||
@@ -71,3 +72,55 @@ export const buildNixpacks = async (
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
|
||||
export const getNixpacksCommand = (
|
||||
application: ApplicationNested,
|
||||
logPath: string,
|
||||
) => {
|
||||
const { env, appName, publishDirectory, serverId } = application;
|
||||
|
||||
const buildAppDirectory = getBuildAppDirectory(application);
|
||||
const buildContainerId = `${appName}-${nanoid(10)}`;
|
||||
const envVariables = prepareEnvironmentVariables(env);
|
||||
|
||||
try {
|
||||
const args = ["build", buildAppDirectory, "--name", appName];
|
||||
|
||||
for (const env of envVariables) {
|
||||
args.push("--env", env);
|
||||
}
|
||||
|
||||
if (publishDirectory) {
|
||||
/* No need for any start command, since we'll use nginx later on */
|
||||
args.push("--no-error-without-start");
|
||||
}
|
||||
|
||||
const command = `nixpacks ${args.join(" ")}`;
|
||||
const bashCommand = `
|
||||
echo "Starting nixpacks build..." >> ${logPath};
|
||||
${command} >> ${logPath} 2>&1;
|
||||
echo "Nixpacks build completed." >> ${logPath};
|
||||
`;
|
||||
|
||||
/*
|
||||
Run the container with the image created by nixpacks,
|
||||
and copy the artifacts on the host filesystem.
|
||||
Then, remove the container and create a static build.
|
||||
*/
|
||||
|
||||
// if (publishDirectory) {
|
||||
// bashCommand += `
|
||||
// docker create --name ${buildContainerId} ${appName}
|
||||
// docker cp ${buildContainerId}:/app/${publishDirectory} ${path.join(buildAppDirectory, publishDirectory)}
|
||||
// docker rm ${buildContainerId}
|
||||
// buildStatic ${application} ${writeStream}
|
||||
// `;
|
||||
// }
|
||||
|
||||
return bashCommand;
|
||||
} catch (e) {
|
||||
// await spawnAsync("docker", ["rm", buildContainerId], writeToStream);
|
||||
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -4,7 +4,6 @@ import { prepareEnvironmentVariables } from "../docker/utils";
|
||||
import { getBuildAppDirectory } from "../filesystem/directory";
|
||||
import { spawnAsync } from "../process/spawnAsync";
|
||||
|
||||
// TODO: integrate in the vps sudo chown -R $(whoami) ~/.docker
|
||||
export const buildPaketo = async (
|
||||
application: ApplicationNested,
|
||||
writeStream: WriteStream,
|
||||
@@ -36,3 +35,35 @@ export const buildPaketo = async (
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
|
||||
export const getPaketoCommand = (
|
||||
application: ApplicationNested,
|
||||
logPath: string,
|
||||
) => {
|
||||
const { env, appName } = application;
|
||||
|
||||
const buildAppDirectory = getBuildAppDirectory(application);
|
||||
const envVariables = prepareEnvironmentVariables(env);
|
||||
|
||||
const args = [
|
||||
"build",
|
||||
appName,
|
||||
"--path",
|
||||
buildAppDirectory,
|
||||
"--builder",
|
||||
"paketobuildpacks/builder-jammy-full",
|
||||
];
|
||||
|
||||
for (const env of envVariables) {
|
||||
args.push("--env", env);
|
||||
}
|
||||
|
||||
const command = `pack ${args.join(" ")}`;
|
||||
const bashCommand = `
|
||||
echo "Starting Paketo build..." >> ${logPath};
|
||||
${command} >> ${logPath} 2>&1;
|
||||
echo "Paketo build completed." >> ${logPath};
|
||||
`;
|
||||
|
||||
return bashCommand;
|
||||
};
|
||||
|
||||
@@ -10,3 +10,13 @@ export const createEnvFile = (directory: string, env: string | null) => {
|
||||
const envFileContent = prepareEnvironmentVariables(env).join("\n");
|
||||
writeFileSync(envFilePath, envFileContent);
|
||||
};
|
||||
|
||||
export const createEnvFileCommand = (directory: string, env: string | null) => {
|
||||
const envFilePath = join(dirname(directory), ".env");
|
||||
// let command = ``
|
||||
if (!existsSync(dirname(envFilePath))) {
|
||||
mkdirSync(dirname(envFilePath), { recursive: true });
|
||||
}
|
||||
const envFileContent = prepareEnvironmentVariables(env).join("\n");
|
||||
return `echo "${envFileContent}" > ${envFilePath}`;
|
||||
};
|
||||
|
||||
@@ -6,6 +6,7 @@ import type { ContainerInfo, ResourceRequirements } from "dockerode";
|
||||
import { parse } from "dotenv";
|
||||
import type { ApplicationNested } from "../builders";
|
||||
import { execAsync } from "../process/execAsync";
|
||||
import { getRemoteDocker } from "../servers/remote-docker";
|
||||
|
||||
interface RegistryAuth {
|
||||
username: string;
|
||||
@@ -51,6 +52,51 @@ export const pullImage = async (
|
||||
}
|
||||
};
|
||||
|
||||
export const pullRemoteImage = async (
|
||||
dockerImage: string,
|
||||
serverId: string,
|
||||
onData?: (data: any) => void,
|
||||
authConfig?: Partial<RegistryAuth>,
|
||||
): Promise<void> => {
|
||||
try {
|
||||
if (!dockerImage) {
|
||||
throw new Error("Docker image not found");
|
||||
}
|
||||
|
||||
const remoteDocker = await getRemoteDocker(serverId);
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
remoteDocker.pull(
|
||||
dockerImage,
|
||||
{ authconfig: authConfig },
|
||||
(err, stream) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
|
||||
remoteDocker.modem.followProgress(
|
||||
stream as Readable,
|
||||
(err: Error | null, res) => {
|
||||
if (!err) {
|
||||
resolve(res);
|
||||
}
|
||||
if (err) {
|
||||
reject(err);
|
||||
}
|
||||
},
|
||||
(event) => {
|
||||
onData?.(event);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const containerExists = async (containerName: string) => {
|
||||
const container = docker.getContainer(containerName);
|
||||
try {
|
||||
|
||||
@@ -117,6 +117,55 @@ export const cloneRawBitbucketRepository = async (entity: Compose) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const getBitbucketCloneCommand = async (
|
||||
entity: ApplicationWithBitbucket | ComposeWithBitbucket,
|
||||
logPath: string,
|
||||
isCompose = false,
|
||||
) => {
|
||||
const {
|
||||
appName,
|
||||
bitbucketRepository,
|
||||
bitbucketOwner,
|
||||
bitbucketBranch,
|
||||
bitbucketId,
|
||||
serverId,
|
||||
bitbucket,
|
||||
} = entity;
|
||||
|
||||
if (!serverId) {
|
||||
throw new TRPCError({
|
||||
code: "NOT_FOUND",
|
||||
message: "Server not found",
|
||||
});
|
||||
}
|
||||
|
||||
if (!bitbucketId) {
|
||||
throw new TRPCError({
|
||||
code: "NOT_FOUND",
|
||||
message: "Bitbucket Provider not found",
|
||||
});
|
||||
}
|
||||
|
||||
const bitbucketProvider = await findBitbucketById(bitbucketId);
|
||||
const basePath = COMPOSE_PATH;
|
||||
const outputPath = join(basePath, appName, "code");
|
||||
await recreateDirectory(outputPath);
|
||||
const repoclone = `bitbucket.org/${bitbucketOwner}/${bitbucketRepository}.git`;
|
||||
const cloneUrl = `https://${bitbucketProvider?.bitbucketUsername}:${bitbucketProvider?.appPassword}@${repoclone}`;
|
||||
|
||||
const cloneCommand = `
|
||||
rm -rf ${outputPath};
|
||||
mkdir -p ${outputPath};
|
||||
if ! git clone --branch ${bitbucketBranch} --depth 1 --progress ${cloneUrl} ${outputPath} >> ${logPath} 2>&1; then
|
||||
echo "[ERROR] Fail to clone the repository ${repoclone}" >> ${logPath};
|
||||
exit 1;
|
||||
fi
|
||||
echo "Cloned ${repoclone} to ${outputPath}: ✅" >> ${logPath};
|
||||
`;
|
||||
|
||||
return cloneCommand;
|
||||
};
|
||||
|
||||
export const getBitbucketRepositories = async (bitbucketId?: string) => {
|
||||
if (!bitbucketId) {
|
||||
return [];
|
||||
|
||||
@@ -47,3 +47,38 @@ export const buildDocker = async (
|
||||
writeStream.end();
|
||||
}
|
||||
};
|
||||
|
||||
export const buildRemoteDocker = async (
|
||||
application: ApplicationNested,
|
||||
logPath: string,
|
||||
) => {
|
||||
const { sourceType, dockerImage, username, password } = application;
|
||||
|
||||
try {
|
||||
if (!dockerImage) {
|
||||
throw new Error("Docker image not found");
|
||||
}
|
||||
let command = `
|
||||
echo "Building ${sourceType}" >> ${logPath};
|
||||
echo "Pulling ${dockerImage}" >> ${logPath};
|
||||
`;
|
||||
|
||||
if (username && password) {
|
||||
command += `
|
||||
if ! docker login --username ${username} --password ${password} https://index.docker.io/v1/ >> ${logPath} 2>&1; then
|
||||
echo "Error logging in to Docker Hub" >> ${logPath};
|
||||
exit 1;
|
||||
fi
|
||||
`;
|
||||
}
|
||||
|
||||
command += `
|
||||
echo "Pulling ${dockerImage}" >> ${logPath};
|
||||
docker pull ${dockerImage} >> ${logPath} 2>&1;
|
||||
`;
|
||||
|
||||
return command;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -87,6 +87,66 @@ export const cloneGitRepository = async (
|
||||
}
|
||||
};
|
||||
|
||||
export const getCustomGitCloneCommand = async (
|
||||
entity: {
|
||||
appName: string;
|
||||
customGitUrl?: string | null;
|
||||
customGitBranch?: string | null;
|
||||
customGitSSHKeyId?: string | null;
|
||||
},
|
||||
logPath: string,
|
||||
isCompose = false,
|
||||
) => {
|
||||
const { appName, customGitUrl, customGitBranch, customGitSSHKeyId } = entity;
|
||||
|
||||
if (!customGitUrl || !customGitBranch) {
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
message: "Error: Repository not found",
|
||||
});
|
||||
}
|
||||
|
||||
const keyPath = path.join(SSH_PATH, `${customGitSSHKeyId}_rsa`);
|
||||
const basePath = isCompose ? COMPOSE_PATH : APPLICATIONS_PATH;
|
||||
const outputPath = join(basePath, appName, "code");
|
||||
const knownHostsPath = path.join(SSH_PATH, "known_hosts");
|
||||
|
||||
if (customGitSSHKeyId) {
|
||||
await updateSSHKeyById({
|
||||
sshKeyId: customGitSSHKeyId,
|
||||
lastUsedAt: new Date().toISOString(),
|
||||
});
|
||||
}
|
||||
try {
|
||||
const command = [];
|
||||
if (!isHttpOrHttps(customGitUrl)) {
|
||||
command.push(addHostToKnownHostsCommand(customGitUrl));
|
||||
}
|
||||
command.push(`rm -rf ${outputPath};`);
|
||||
command.push(`mkdir -p ${outputPath};`);
|
||||
command.push(
|
||||
`echo "Cloning Custom Git ${customGitUrl}" to ${outputPath}: ✅ >> ${logPath};`,
|
||||
);
|
||||
if (customGitSSHKeyId) {
|
||||
command.push(
|
||||
`GIT_SSH_COMMAND="ssh -i ${keyPath} -o UserKnownHostsFile=${knownHostsPath}"`,
|
||||
);
|
||||
}
|
||||
|
||||
command.push(
|
||||
`if ! git clone --branch ${customGitBranch} --depth 1 --progress ${customGitUrl} ${outputPath} >> ${logPath} 2>&1; then
|
||||
echo "[ERROR] Fail to clone the repository ${customGitUrl}" >> ${logPath};
|
||||
exit 1;
|
||||
fi
|
||||
`,
|
||||
);
|
||||
command.push(`echo "Cloned Custom Git ${customGitUrl}: ✅" >> ${logPath}`);
|
||||
return command.join("\n");
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
const isHttpOrHttps = (url: string): boolean => {
|
||||
const regex = /^https?:\/\//;
|
||||
return regex.test(url);
|
||||
@@ -104,6 +164,13 @@ const addHostToKnownHosts = async (repositoryURL: string) => {
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
const addHostToKnownHostsCommand = (repositoryURL: string) => {
|
||||
const { domain, port } = sanitizeRepoPathSSH(repositoryURL);
|
||||
const knownHostsPath = path.join(SSH_PATH, "known_hosts");
|
||||
|
||||
return `ssh-keyscan -p ${port} ${domain} >> ${knownHostsPath};`;
|
||||
};
|
||||
const sanitizeRepoPathSSH = (input: string) => {
|
||||
const SSH_PATH_RE = new RegExp(
|
||||
[
|
||||
|
||||
@@ -11,6 +11,7 @@ import { spawnAsync } from "../process/spawnAsync";
|
||||
import type { Compose } from "@/server/api/services/compose";
|
||||
import { type Github, findGithubById } from "@/server/api/services/github";
|
||||
import type { apiFindGithubBranches } from "@/server/db/schema";
|
||||
import { executeCommand } from "../servers/command";
|
||||
|
||||
export const authGithub = (githubProvider: Github) => {
|
||||
if (!haveGithubRequirements(githubProvider)) {
|
||||
@@ -142,6 +143,63 @@ export const cloneGithubRepository = async (
|
||||
}
|
||||
};
|
||||
|
||||
export const getGithubCloneCommand = async (
|
||||
entity: ApplicationWithGithub | ComposeWithGithub,
|
||||
logPath: string,
|
||||
isCompose = false,
|
||||
) => {
|
||||
const { appName, repository, owner, branch, githubId, serverId } = entity;
|
||||
|
||||
if (!githubId || !serverId) {
|
||||
throw new TRPCError({
|
||||
code: "NOT_FOUND",
|
||||
message: "GitHub Provider not found",
|
||||
});
|
||||
}
|
||||
|
||||
const requirements = getErrorCloneRequirements(entity);
|
||||
|
||||
// Build log messages
|
||||
let logMessages = "";
|
||||
if (requirements.length > 0) {
|
||||
logMessages += `\nGitHub Repository configuration failed for application: ${appName}\n`;
|
||||
logMessages += "Reasons:\n";
|
||||
logMessages += requirements.join("\n");
|
||||
const escapedLogMessages = logMessages
|
||||
.replace(/\\/g, "\\\\")
|
||||
.replace(/"/g, '\\"')
|
||||
.replace(/\n/g, "\\n");
|
||||
|
||||
const bashCommand = `
|
||||
echo "${escapedLogMessages}" >> ${logPath};
|
||||
exit 1; # Exit with error code
|
||||
`;
|
||||
|
||||
await executeCommand(serverId, bashCommand);
|
||||
return;
|
||||
}
|
||||
|
||||
const githubProvider = await findGithubById(githubId);
|
||||
const basePath = isCompose ? COMPOSE_PATH : APPLICATIONS_PATH;
|
||||
const outputPath = join(basePath, appName, "code");
|
||||
const octokit = authGithub(githubProvider);
|
||||
const token = await getGithubToken(octokit);
|
||||
const repoclone = `github.com/${owner}/${repository}.git`;
|
||||
const cloneUrl = `https://oauth2:${token}@${repoclone}`;
|
||||
|
||||
const cloneCommand = `
|
||||
rm -rf ${outputPath};
|
||||
mkdir -p ${outputPath};
|
||||
if ! git clone --branch ${branch} --depth 1 --progress ${cloneUrl} ${outputPath} >> ${logPath} 2>&1; then
|
||||
echo "[ERROR] Fallo al clonar el repositorio ${repoclone}" >> ${logPath};
|
||||
exit 1;
|
||||
fi
|
||||
echo "Cloned ${repoclone} to ${outputPath}: ✅" >> ${logPath};
|
||||
`;
|
||||
|
||||
return cloneCommand;
|
||||
};
|
||||
|
||||
export const cloneRawGithubRepository = async (entity: Compose) => {
|
||||
const { appName, repository, owner, branch, githubId } = entity;
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import type { InferResultType } from "@/server/types/with";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { recreateDirectory } from "../filesystem/directory";
|
||||
import { spawnAsync } from "../process/spawnAsync";
|
||||
import { executeCommand } from "../servers/command";
|
||||
|
||||
export const refreshGitlabToken = async (gitlabProviderId: string) => {
|
||||
const gitlabProvider = await findGitlabById(gitlabProviderId);
|
||||
@@ -153,6 +154,78 @@ export const cloneGitlabRepository = async (
|
||||
}
|
||||
};
|
||||
|
||||
export const getGitlabCloneCommand = async (
|
||||
entity: ApplicationWithGitlab | ComposeWithGitlab,
|
||||
logPath: string,
|
||||
isCompose = false,
|
||||
) => {
|
||||
const {
|
||||
appName,
|
||||
gitlabRepository,
|
||||
gitlabOwner,
|
||||
gitlabPathNamespace,
|
||||
gitlabBranch,
|
||||
gitlabId,
|
||||
serverId,
|
||||
gitlab,
|
||||
} = entity;
|
||||
|
||||
if (!serverId) {
|
||||
throw new TRPCError({
|
||||
code: "NOT_FOUND",
|
||||
message: "Server not found",
|
||||
});
|
||||
}
|
||||
|
||||
if (!gitlabId) {
|
||||
throw new TRPCError({
|
||||
code: "NOT_FOUND",
|
||||
message: "Gitlab Provider not found",
|
||||
});
|
||||
}
|
||||
|
||||
const requirements = getErrorCloneRequirements(entity);
|
||||
|
||||
// Build log messages
|
||||
let logMessages = "";
|
||||
if (requirements.length > 0) {
|
||||
logMessages += `\nGitLab Repository configuration failed for application: ${appName}\n`;
|
||||
logMessages += "Reasons:\n";
|
||||
logMessages += requirements.join("\n");
|
||||
const escapedLogMessages = logMessages
|
||||
.replace(/\\/g, "\\\\")
|
||||
.replace(/"/g, '\\"')
|
||||
.replace(/\n/g, "\\n");
|
||||
|
||||
const bashCommand = `
|
||||
echo "${escapedLogMessages}" >> ${logPath};
|
||||
exit 1; # Exit with error code
|
||||
`;
|
||||
|
||||
await executeCommand(serverId, bashCommand);
|
||||
return;
|
||||
}
|
||||
|
||||
await refreshGitlabToken(gitlabId);
|
||||
const basePath = isCompose ? COMPOSE_PATH : APPLICATIONS_PATH;
|
||||
const outputPath = join(basePath, appName, "code");
|
||||
await recreateDirectory(outputPath);
|
||||
const repoclone = `gitlab.com/${gitlabPathNamespace}.git`;
|
||||
const cloneUrl = `https://oauth2:${gitlab?.accessToken}@${repoclone}`;
|
||||
|
||||
const cloneCommand = `
|
||||
rm -rf ${outputPath};
|
||||
mkdir -p ${outputPath};
|
||||
if ! git clone --branch ${gitlabBranch} --depth 1 --progress ${cloneUrl} ${outputPath} >> ${logPath} 2>&1; then
|
||||
echo "[ERROR] Fail to clone the repository ${repoclone}" >> ${logPath};
|
||||
exit 1;
|
||||
fi
|
||||
echo "Cloned ${repoclone} to ${outputPath}: ✅" >> ${logPath};
|
||||
`;
|
||||
|
||||
return cloneCommand;
|
||||
};
|
||||
|
||||
export const getGitlabRepositories = async (gitlabId?: string) => {
|
||||
if (!gitlabId) {
|
||||
return [];
|
||||
|
||||
47
apps/dokploy/server/utils/servers/command.ts
Normal file
47
apps/dokploy/server/utils/servers/command.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { findServerById } from "@/server/api/services/server";
|
||||
import { readSSHKey } from "../filesystem/ssh";
|
||||
import { Client } from "ssh2";
|
||||
|
||||
export const executeCommand = async (serverId: string, command: string) => {
|
||||
const server = await findServerById(serverId);
|
||||
|
||||
if (!server.sshKeyId) return;
|
||||
const keys = await readSSHKey(server.sshKeyId);
|
||||
const client = new Client();
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
client
|
||||
.on("ready", () => {
|
||||
console.log("Client :: ready", command);
|
||||
client.exec(command, (err, stream) => {
|
||||
if (err) {
|
||||
console.error("Execution error:", err);
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
stream
|
||||
.on("close", (code, signal) => {
|
||||
console.log("Connection closed ✅");
|
||||
client.end();
|
||||
if (code === 0) {
|
||||
resolve();
|
||||
} else {
|
||||
reject(new Error(`Command exited with code ${code}`));
|
||||
}
|
||||
})
|
||||
.on("data", (data: string) => {
|
||||
console.log(`OUTPUT: ${data.toString()}`);
|
||||
})
|
||||
.stderr.on("data", (data) => {
|
||||
console.error(`STDERR: ${data.toString()}`);
|
||||
});
|
||||
});
|
||||
})
|
||||
.connect({
|
||||
host: server.ipAddress,
|
||||
port: server.port,
|
||||
username: server.username,
|
||||
privateKey: keys.privateKey,
|
||||
timeout: 99999,
|
||||
});
|
||||
});
|
||||
};
|
||||
23
apps/dokploy/server/utils/servers/remote-docker.ts
Normal file
23
apps/dokploy/server/utils/servers/remote-docker.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { findServerById } from "@/server/api/services/server";
|
||||
import { readSSHKey } from "../filesystem/ssh";
|
||||
import Dockerode from "dockerode";
|
||||
import { docker } from "@/server/constants";
|
||||
|
||||
export const getRemoteDocker = async (serverId: string | null) => {
|
||||
if (!serverId) return docker;
|
||||
const server = await findServerById(serverId);
|
||||
if (!server.sshKeyId) return docker;
|
||||
const keys = await readSSHKey(server.sshKeyId);
|
||||
const dockerode = new Dockerode({
|
||||
host: server.ipAddress,
|
||||
port: server.port,
|
||||
username: server.username,
|
||||
protocol: "ssh",
|
||||
// @ts-ignore
|
||||
sshOptions: {
|
||||
privateKey: keys.privateKey,
|
||||
},
|
||||
});
|
||||
|
||||
return dockerode;
|
||||
};
|
||||
@@ -17,9 +17,13 @@ import {
|
||||
createServerDeployment,
|
||||
updateDeploymentStatus,
|
||||
} from "@/server/api/services/deployment";
|
||||
import { chmodSync, createWriteStream } from "node:fs";
|
||||
import { createWriteStream } from "node:fs";
|
||||
import { Client } from "ssh2";
|
||||
import { readSSHKey } from "../filesystem/ssh";
|
||||
import {
|
||||
getDefaultMiddlewares,
|
||||
getDefaultTraefikConfig,
|
||||
} from "@/server/setup/traefik-setup";
|
||||
|
||||
export const setupServer = async (serverId: string) => {
|
||||
const server = await findServerById(serverId);
|
||||
@@ -76,6 +80,12 @@ const connectToServer = async (serverId: string, logPath: string) => {
|
||||
${setupNetwork()}
|
||||
${setupMainDirectory()}
|
||||
${setupDirectories()}
|
||||
${createTraefikConfig()}
|
||||
${createDefaultMiddlewares()}
|
||||
${createTraefikInstance()}
|
||||
${installNixpacks()}
|
||||
${installBuildpacks()}
|
||||
${setupRedis()}
|
||||
`;
|
||||
|
||||
client.exec(bashCommand, (err, stream) => {
|
||||
@@ -90,13 +100,11 @@ const connectToServer = async (serverId: string, logPath: string) => {
|
||||
client.end();
|
||||
resolve();
|
||||
})
|
||||
.on("data", (data) => {
|
||||
.on("data", (data: string) => {
|
||||
writeStream.write(data.toString());
|
||||
console.log(`OUTPUT: ${data}`);
|
||||
})
|
||||
.stderr.on("data", (data) => {
|
||||
writeStream.write(data.toString());
|
||||
console.log(`STDERR: ${data}`);
|
||||
});
|
||||
});
|
||||
})
|
||||
@@ -105,7 +113,7 @@ const connectToServer = async (serverId: string, logPath: string) => {
|
||||
port: server.port,
|
||||
username: server.username,
|
||||
privateKey: keys.privateKey,
|
||||
timeout: 10000,
|
||||
timeout: 99999,
|
||||
});
|
||||
});
|
||||
};
|
||||
@@ -135,7 +143,6 @@ const setupDirectories = () => {
|
||||
${chmodCommand}
|
||||
`;
|
||||
|
||||
console.log(command);
|
||||
return command;
|
||||
};
|
||||
|
||||
@@ -203,16 +210,102 @@ const installDocker = () => `
|
||||
const validatePorts = () => `
|
||||
# check if something is running on port 80
|
||||
if ss -tulnp | grep ':80 ' >/dev/null; then
|
||||
echo "Error: something is already running on port 80" >&2
|
||||
exit 1
|
||||
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 "Error: something is already running on port 443" >&2
|
||||
exit 1
|
||||
echo "Something is already running on port 443" >&2
|
||||
fi
|
||||
`;
|
||||
|
||||
// mkdir -p "/Users/mauricio/Documents/Github/Personal/dokploy/apps/dokploy/.docker" && mkdir -p "/Users/mauricio/Documents/Github/Personal/dokploy/apps/dokploy/.docker/traefik" && mkdir -p "/Users/mauricio/Documents/Github/Personal/dokploy/apps/dokploy/.docker/traefik/dynamic" && mkdir -p "/Users/mauricio/Documents/Github/Personal/dokploy/apps/dokploy/.docker/logs" && mkdir -p "/Users/mauricio/Documents/Github/Personal/dokploy/apps/dokploy/.docker/applications" && mkdir -p "/Users/mauricio/Documents/Github/Personal/dokploy/apps/dokploy/.docker/ssh" && mkdir -p "/Users/mauricio/Documents/Github/Personal/dokploy/apps/dokploy/.docker/traefik/dynamic/certificates" && mkdir -p "/Users/mauricio/Documents/Github/Personal/dokploy/apps/dokploy/.docker/monitoring"
|
||||
// chmod 700 "/Users/mauricio/Documents/Github/Personal/dokploy/apps/dokploy/.docker/ssh"
|
||||
const createTraefikConfig = () => {
|
||||
const config = getDefaultTraefikConfig();
|
||||
|
||||
const command = `
|
||||
if [ -f "/etc/dokploy/traefik/dynamic/acme.json" ]; then
|
||||
chmod 600 "/etc/dokploy/traefik/dynamic/acme.json"
|
||||
fi
|
||||
if [ -f "/etc/dokploy/traefik/traefik.yml" ]; then
|
||||
echo "Traefik config already exists ✅"
|
||||
else
|
||||
echo "${config}" > /etc/dokploy/traefik/traefik.yml
|
||||
fi
|
||||
`;
|
||||
|
||||
return command;
|
||||
};
|
||||
|
||||
export const createDefaultMiddlewares = () => {
|
||||
const config = getDefaultMiddlewares();
|
||||
const command = `
|
||||
if [ -f "/etc/dokploy/traefik/dynamic/middlewares.yml" ]; then
|
||||
echo "Middlewares config already exists ✅"
|
||||
else
|
||||
echo "${config}" > /etc/dokploy/traefik/dynamic/middlewares.yml
|
||||
fi
|
||||
`;
|
||||
return command;
|
||||
};
|
||||
|
||||
export const createTraefikInstance = () => {
|
||||
const command = `
|
||||
# Check if dokpyloy-traefik exists
|
||||
if docker service ls | grep -q 'dokploy-traefik'; then
|
||||
echo "Traefik already exists ✅"
|
||||
else
|
||||
# Create the dokploy-traefik service
|
||||
docker service create \
|
||||
--name dokploy-traefik \
|
||||
--replicas 1 \
|
||||
--constraint 'node.role==manager' \
|
||||
--network dokploy-network \
|
||||
--mount type=bind,src=/etc/dokploy/traefik/traefik.yml,dst=/etc/traefik/traefik.yml \
|
||||
--mount type=bind,src=/etc/dokploy/traefik/dynamic,dst=/etc/dokploy/traefik/dynamic \
|
||||
--mount type=bind,src=/var/run/docker.sock,dst=/var/run/docker.sock \
|
||||
--label traefik.enable=true \
|
||||
--publish mode=host,target=443,published=443 \
|
||||
--publish mode=host,target=80,published=80 \
|
||||
--publish mode=host,target=8080,published=8080 \
|
||||
traefik:v3.1.2
|
||||
fi
|
||||
`;
|
||||
|
||||
return command;
|
||||
};
|
||||
|
||||
const installNixpacks = () => `
|
||||
if command_exists nixpacks; then
|
||||
echo "Nixpacks already installed ✅"
|
||||
else
|
||||
VERSION=1.28.1 bash -c "$(curl -fsSL https://nixpacks.com/install.sh)"
|
||||
echo "Nixpacks version 1.28.1 installed ✅"
|
||||
fi
|
||||
`;
|
||||
|
||||
const installBuildpacks = () => `
|
||||
if command_exists pack; then
|
||||
echo "Buildpacks already installed ✅"
|
||||
else
|
||||
curl -sSL "https://github.com/buildpacks/pack/releases/download/v0.35.0/pack-v0.35.0-linux.tgz" | tar -C /usr/local/bin/ --no-same-owner -xzv pack
|
||||
echo "Buildpacks version 0.35.0 installed ✅"
|
||||
fi
|
||||
`;
|
||||
|
||||
const setupRedis = () => `
|
||||
# Check if redis is already installed
|
||||
if docker service ls | grep -q 'dokploy-redis'; then
|
||||
echo "Redis already installed ✅"
|
||||
else
|
||||
# Install Redis
|
||||
docker service create \
|
||||
--name dokploy-redis \
|
||||
--replicas 1 \
|
||||
--constraint 'node.role==manager' \
|
||||
--mount type=volume,source=redis-data-volume,target=/data \
|
||||
--network dokploy-network \
|
||||
--publish target=6379,published=6379,protocol=tcp,mode=host \
|
||||
redis:7
|
||||
fi
|
||||
|
||||
`;
|
||||
|
||||
Reference in New Issue
Block a user