mirror of
https://github.com/Dokploy/dokploy
synced 2025-06-26 18:27:59 +00:00
feat: add multi server compose
This commit is contained in:
@@ -174,9 +174,8 @@ export const AddDomain = ({
|
|||||||
isLoading={isLoadingGenerate}
|
isLoading={isLoadingGenerate}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
generateDomain({
|
generateDomain({
|
||||||
applicationId:
|
appName: application?.appName || "",
|
||||||
application?.applicationId || "",
|
serverId: application?.serverId || "",
|
||||||
// appName: application?.appName || "",
|
|
||||||
})
|
})
|
||||||
.then((domain) => {
|
.then((domain) => {
|
||||||
field.onChange(domain);
|
field.onChange(domain);
|
||||||
|
|||||||
@@ -45,14 +45,17 @@ export const DeployApplication = ({ applicationId }: Props) => {
|
|||||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||||
<AlertDialogAction
|
<AlertDialogAction
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
toast.success("Deploying Application....");
|
|
||||||
|
|
||||||
await refetch();
|
|
||||||
await deploy({
|
await deploy({
|
||||||
applicationId,
|
applicationId,
|
||||||
}).catch(() => {
|
})
|
||||||
toast.error("Error to deploy Application");
|
.then(async () => {
|
||||||
});
|
toast.success("Application deployed succesfully");
|
||||||
|
await refetch();
|
||||||
|
})
|
||||||
|
|
||||||
|
.catch(() => {
|
||||||
|
toast.error("Error to deploy Application");
|
||||||
|
});
|
||||||
|
|
||||||
await refetch();
|
await refetch();
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -310,6 +310,7 @@ export const AddDomainCompose = ({
|
|||||||
isLoading={isLoadingGenerate}
|
isLoading={isLoadingGenerate}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
generateDomain({
|
generateDomain({
|
||||||
|
serverId: compose?.serverId || "",
|
||||||
appName: compose?.appName || "",
|
appName: compose?.appName || "",
|
||||||
})
|
})
|
||||||
.then((domain) => {
|
.then((domain) => {
|
||||||
|
|||||||
@@ -152,9 +152,7 @@ export const composeRouter = createTRPCRouter({
|
|||||||
.query(async ({ input }) => {
|
.query(async ({ input }) => {
|
||||||
const compose = await findComposeById(input.composeId);
|
const compose = await findComposeById(input.composeId);
|
||||||
const domains = await findDomainsByComposeId(input.composeId);
|
const domains = await findDomainsByComposeId(input.composeId);
|
||||||
|
|
||||||
const composeFile = await addDomainToCompose(compose, domains);
|
const composeFile = await addDomainToCompose(compose, domains);
|
||||||
|
|
||||||
return dump(composeFile, {
|
return dump(composeFile, {
|
||||||
lineWidth: 1000,
|
lineWidth: 1000,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import {
|
|||||||
removeDomainById,
|
removeDomainById,
|
||||||
updateDomainById,
|
updateDomainById,
|
||||||
} from "../services/domain";
|
} from "../services/domain";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
export const domainRouter = createTRPCRouter({
|
export const domainRouter = createTRPCRouter({
|
||||||
create: protectedProcedure
|
create: protectedProcedure
|
||||||
@@ -47,9 +48,9 @@ export const domainRouter = createTRPCRouter({
|
|||||||
return await findDomainsByComposeId(input.composeId);
|
return await findDomainsByComposeId(input.composeId);
|
||||||
}),
|
}),
|
||||||
generateDomain: protectedProcedure
|
generateDomain: protectedProcedure
|
||||||
.input(apiFindOneApplication)
|
.input(z.object({ serverId: z.string(), appName: z.string() }))
|
||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input }) => {
|
||||||
return generateTraefikMeDomain(input.applicationId);
|
return generateTraefikMeDomain(input.serverId, input.appName);
|
||||||
}),
|
}),
|
||||||
|
|
||||||
update: protectedProcedure
|
update: protectedProcedure
|
||||||
|
|||||||
@@ -164,30 +164,29 @@ export const deployApplication = async ({
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
if (application.serverId) {
|
if (application.serverId) {
|
||||||
let command = `
|
let command = "set -e";
|
||||||
set -e
|
|
||||||
`;
|
|
||||||
if (application.sourceType === "github") {
|
if (application.sourceType === "github") {
|
||||||
command += await getGithubCloneCommand(application, deployment.logPath);
|
command += await getGithubCloneCommand(application, deployment.logPath);
|
||||||
command += getBuildCommand(application, deployment.logPath);
|
|
||||||
} else if (application.sourceType === "gitlab") {
|
} else if (application.sourceType === "gitlab") {
|
||||||
command += await getGitlabCloneCommand(application, deployment.logPath);
|
command += await getGitlabCloneCommand(application, deployment.logPath);
|
||||||
command += getBuildCommand(application, deployment.logPath);
|
|
||||||
} else if (application.sourceType === "bitbucket") {
|
} else if (application.sourceType === "bitbucket") {
|
||||||
command += await getBitbucketCloneCommand(
|
command += await getBitbucketCloneCommand(
|
||||||
application,
|
application,
|
||||||
deployment.logPath,
|
deployment.logPath,
|
||||||
);
|
);
|
||||||
command += getBuildCommand(application, deployment.logPath);
|
|
||||||
} else if (application.sourceType === "git") {
|
} else if (application.sourceType === "git") {
|
||||||
command += await getCustomGitCloneCommand(
|
command += await getCustomGitCloneCommand(
|
||||||
application,
|
application,
|
||||||
deployment.logPath,
|
deployment.logPath,
|
||||||
);
|
);
|
||||||
command += getBuildCommand(application, deployment.logPath);
|
|
||||||
} else if (application.sourceType === "docker") {
|
} else if (application.sourceType === "docker") {
|
||||||
command += await buildRemoteDocker(application, deployment.logPath);
|
command += await buildRemoteDocker(application, deployment.logPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (application.sourceType !== "docker") {
|
||||||
|
command += getBuildCommand(application, deployment.logPath);
|
||||||
|
}
|
||||||
|
|
||||||
await executeCommand(application.serverId, command);
|
await executeCommand(application.serverId, command);
|
||||||
await mechanizeDockerContainer(application);
|
await mechanizeDockerContainer(application);
|
||||||
} else {
|
} else {
|
||||||
@@ -210,6 +209,8 @@ export const deployApplication = async ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log("Command", "Finish");
|
||||||
|
|
||||||
await updateDeploymentStatus(deployment.deploymentId, "done");
|
await updateDeploymentStatus(deployment.deploymentId, "done");
|
||||||
await updateApplicationStatus(applicationId, "done");
|
await updateApplicationStatus(applicationId, "done");
|
||||||
|
|
||||||
|
|||||||
@@ -8,10 +8,15 @@ import {
|
|||||||
getBuildComposeCommand,
|
getBuildComposeCommand,
|
||||||
} from "@/server/utils/builders/compose";
|
} from "@/server/utils/builders/compose";
|
||||||
import { randomizeSpecificationFile } from "@/server/utils/docker/compose";
|
import { randomizeSpecificationFile } from "@/server/utils/docker/compose";
|
||||||
import { cloneCompose, loadDockerCompose } from "@/server/utils/docker/domain";
|
import {
|
||||||
|
cloneCompose,
|
||||||
|
cloneComposeRemote,
|
||||||
|
loadDockerCompose,
|
||||||
|
loadDockerComposeRemote,
|
||||||
|
} from "@/server/utils/docker/domain";
|
||||||
import { sendBuildErrorNotifications } from "@/server/utils/notifications/build-error";
|
import { sendBuildErrorNotifications } from "@/server/utils/notifications/build-error";
|
||||||
import { sendBuildSuccessNotifications } from "@/server/utils/notifications/build-success";
|
import { sendBuildSuccessNotifications } from "@/server/utils/notifications/build-success";
|
||||||
import { execAsync } from "@/server/utils/process/execAsync";
|
import { execAsync, execAsyncRemote } from "@/server/utils/process/execAsync";
|
||||||
import {
|
import {
|
||||||
cloneBitbucketRepository,
|
cloneBitbucketRepository,
|
||||||
getBitbucketCloneCommand,
|
getBitbucketCloneCommand,
|
||||||
@@ -38,8 +43,8 @@ import { eq } from "drizzle-orm";
|
|||||||
import { getDokployUrl } from "./admin";
|
import { getDokployUrl } from "./admin";
|
||||||
import { createDeploymentCompose, updateDeploymentStatus } from "./deployment";
|
import { createDeploymentCompose, updateDeploymentStatus } from "./deployment";
|
||||||
import { validUniqueServerAppName } from "./project";
|
import { validUniqueServerAppName } from "./project";
|
||||||
import { getBuildCommand } from "@/server/utils/builders";
|
|
||||||
import { executeCommand } from "@/server/utils/servers/command";
|
import { executeCommand } from "@/server/utils/servers/command";
|
||||||
|
import type { ComposeSpecification } from "@/server/utils/docker/types";
|
||||||
|
|
||||||
export type Compose = typeof compose.$inferSelect;
|
export type Compose = typeof compose.$inferSelect;
|
||||||
|
|
||||||
@@ -136,10 +141,20 @@ export const loadServices = async (
|
|||||||
const compose = await findComposeById(composeId);
|
const compose = await findComposeById(composeId);
|
||||||
|
|
||||||
if (type === "fetch") {
|
if (type === "fetch") {
|
||||||
await cloneCompose(compose);
|
if (compose.serverId) {
|
||||||
|
await cloneComposeRemote(compose);
|
||||||
|
} else {
|
||||||
|
await cloneCompose(compose);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let composeData = await loadDockerCompose(compose);
|
let composeData: ComposeSpecification | null;
|
||||||
|
|
||||||
|
if (compose.serverId) {
|
||||||
|
composeData = await loadDockerComposeRemote(compose);
|
||||||
|
} else {
|
||||||
|
composeData = await loadDockerCompose(compose);
|
||||||
|
}
|
||||||
|
|
||||||
if (compose.randomize && composeData) {
|
if (compose.randomize && composeData) {
|
||||||
const randomizedCompose = randomizeSpecificationFile(
|
const randomizedCompose = randomizeSpecificationFile(
|
||||||
@@ -196,8 +211,8 @@ export const deployCompose = async ({
|
|||||||
try {
|
try {
|
||||||
if (compose.serverId) {
|
if (compose.serverId) {
|
||||||
let command = `
|
let command = `
|
||||||
set -e
|
set -e;
|
||||||
`;
|
`;
|
||||||
if (compose.sourceType === "github") {
|
if (compose.sourceType === "github") {
|
||||||
command += await getGithubCloneCommand(
|
command += await getGithubCloneCommand(
|
||||||
compose,
|
compose,
|
||||||
@@ -225,8 +240,23 @@ export const deployCompose = async ({
|
|||||||
} else if (compose.sourceType === "raw") {
|
} else if (compose.sourceType === "raw") {
|
||||||
command += getCreateComposeFileCommand(compose);
|
command += getCreateComposeFileCommand(compose);
|
||||||
}
|
}
|
||||||
command += getBuildComposeCommand(compose, deployment.logPath);
|
|
||||||
await executeCommand(compose.serverId, command);
|
// await executeCommand(compose.serverId, command);
|
||||||
|
command += await getBuildComposeCommand(compose, deployment.logPath);
|
||||||
|
|
||||||
|
console.log(command);
|
||||||
|
|
||||||
|
// console.log(buildCommand);
|
||||||
|
try {
|
||||||
|
const { stderr, stdout } = await execAsyncRemote(
|
||||||
|
compose.serverId,
|
||||||
|
command,
|
||||||
|
);
|
||||||
|
console.log(stderr);
|
||||||
|
console.log(stdout);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (compose.sourceType === "github") {
|
if (compose.sourceType === "github") {
|
||||||
await cloneGithubRepository(compose, deployment.logPath, true);
|
await cloneGithubRepository(compose, deployment.logPath, true);
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { TRPCError } from "@trpc/server";
|
|||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
import { findAdmin } from "./admin";
|
import { findAdmin } from "./admin";
|
||||||
import { findApplicationById } from "./application";
|
import { findApplicationById } from "./application";
|
||||||
|
import { findServerById } from "./server";
|
||||||
|
|
||||||
export type Domain = typeof domains.$inferSelect;
|
export type Domain = typeof domains.$inferSelect;
|
||||||
|
|
||||||
@@ -33,11 +34,21 @@ export const createDomain = async (input: typeof apiCreateDomain._type) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const generateTraefikMeDomain = async (appName: string) => {
|
export const generateTraefikMeDomain = async (
|
||||||
const application = await findApplicationById(appName);
|
serverId: string,
|
||||||
|
appName: string,
|
||||||
|
) => {
|
||||||
|
if (serverId) {
|
||||||
|
const server = await findServerById(serverId);
|
||||||
|
return generateRandomDomain({
|
||||||
|
serverIp: server.ipAddress,
|
||||||
|
projectName: appName,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const admin = await findAdmin();
|
||||||
return generateRandomDomain({
|
return generateRandomDomain({
|
||||||
serverIp: application.server?.ipAddress || "",
|
serverIp: admin?.serverIp || "",
|
||||||
projectName: application.appName,
|
projectName: appName,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,10 @@ import { dirname, join } from "node:path";
|
|||||||
import { COMPOSE_PATH } from "@/server/constants";
|
import { COMPOSE_PATH } from "@/server/constants";
|
||||||
import type { InferResultType } from "@/server/types/with";
|
import type { InferResultType } from "@/server/types/with";
|
||||||
import boxen from "boxen";
|
import boxen from "boxen";
|
||||||
import { writeDomainsToCompose } from "../docker/domain";
|
import {
|
||||||
|
writeDomainsToCompose,
|
||||||
|
writeDomainsToComposeRemote,
|
||||||
|
} from "../docker/domain";
|
||||||
import { prepareEnvironmentVariables } from "../docker/utils";
|
import { prepareEnvironmentVariables } from "../docker/utils";
|
||||||
import { spawnAsync } from "../process/spawnAsync";
|
import { spawnAsync } from "../process/spawnAsync";
|
||||||
|
|
||||||
@@ -65,13 +68,16 @@ Compose Type: ${composeType} ✅`;
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getBuildComposeCommand = (
|
export const getBuildComposeCommand = async (
|
||||||
compose: ComposeNested,
|
compose: ComposeNested,
|
||||||
logPath: string,
|
logPath: string,
|
||||||
) => {
|
) => {
|
||||||
const { sourceType, appName, mounts, composeType, domains } = compose;
|
const { sourceType, appName, mounts, composeType, domains } = compose;
|
||||||
const command = createCommand(compose);
|
const command = createCommand(compose);
|
||||||
|
const envCommand = getCreateEnvFileCommand(compose);
|
||||||
const projectPath = join(COMPOSE_PATH, compose.appName, "code");
|
const projectPath = join(COMPOSE_PATH, compose.appName, "code");
|
||||||
|
|
||||||
|
const newCompose = await writeDomainsToComposeRemote(compose, domains);
|
||||||
const logContent = `
|
const logContent = `
|
||||||
App Name: ${appName}
|
App Name: ${appName}
|
||||||
Build Compose 🐳
|
Build Compose 🐳
|
||||||
@@ -80,8 +86,19 @@ Command: docker ${command}
|
|||||||
Source Type: docker ${sourceType} ✅
|
Source Type: docker ${sourceType} ✅
|
||||||
Compose Type: ${composeType} ✅`;
|
Compose Type: ${composeType} ✅`;
|
||||||
|
|
||||||
|
const logBox = boxen(logContent, {
|
||||||
|
padding: {
|
||||||
|
left: 1,
|
||||||
|
right: 1,
|
||||||
|
bottom: 1,
|
||||||
|
},
|
||||||
|
width: 80,
|
||||||
|
borderStyle: "double",
|
||||||
|
});
|
||||||
|
|
||||||
const bashCommand = `
|
const bashCommand = `
|
||||||
echo "${logContent}" >> ${logPath};
|
${newCompose}
|
||||||
|
echo "${logBox}" >> ${logPath};
|
||||||
cd ${projectPath} || exit 1;
|
cd ${projectPath} || exit 1;
|
||||||
docker ${command.split(" ").join(" ")} >> ${logPath} 2>&1;
|
docker ${command.split(" ").join(" ")} >> ${logPath} 2>&1;
|
||||||
echo "Docker Compose Deployed: ✅" >> ${logPath};
|
echo "Docker Compose Deployed: ✅" >> ${logPath};
|
||||||
@@ -144,3 +161,26 @@ const createEnvFile = (compose: ComposeNested) => {
|
|||||||
}
|
}
|
||||||
writeFileSync(envFilePath, envFileContent);
|
writeFileSync(envFilePath, envFileContent);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getCreateEnvFileCommand = (compose: ComposeNested) => {
|
||||||
|
const { env, composePath, appName } = compose;
|
||||||
|
const composeFilePath =
|
||||||
|
join(COMPOSE_PATH, appName, "code", composePath) ||
|
||||||
|
join(COMPOSE_PATH, appName, "code", "docker-compose.yml");
|
||||||
|
|
||||||
|
const envFilePath = join(dirname(composeFilePath), ".env");
|
||||||
|
let envContent = env || "";
|
||||||
|
if (!envContent.includes("DOCKER_CONFIG")) {
|
||||||
|
envContent += "\nDOCKER_CONFIG=/root/.docker/config.json";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (compose.randomize) {
|
||||||
|
envContent += `\nCOMPOSE_PREFIX=${compose.suffix}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const envFileContent = prepareEnvironmentVariables(envContent).join("\n");
|
||||||
|
return `
|
||||||
|
mkdir -p ${envFilePath};
|
||||||
|
echo "${envFileContent}" > ${envFilePath} 2>/dev/null;
|
||||||
|
`;
|
||||||
|
};
|
||||||
|
|||||||
@@ -5,17 +5,33 @@ import type { Compose } from "@/server/api/services/compose";
|
|||||||
import type { Domain } from "@/server/api/services/domain";
|
import type { Domain } from "@/server/api/services/domain";
|
||||||
import { COMPOSE_PATH } from "@/server/constants";
|
import { COMPOSE_PATH } from "@/server/constants";
|
||||||
import { dump, load } from "js-yaml";
|
import { dump, load } from "js-yaml";
|
||||||
import { cloneRawBitbucketRepository } from "../providers/bitbucket";
|
import {
|
||||||
import { cloneGitRawRepository } from "../providers/git";
|
cloneRawBitbucketRepository,
|
||||||
import { cloneRawGithubRepository } from "../providers/github";
|
cloneRawBitbucketRepositoryRemote,
|
||||||
import { cloneRawGitlabRepository } from "../providers/gitlab";
|
} from "../providers/bitbucket";
|
||||||
import { createComposeFileRaw } from "../providers/raw";
|
import {
|
||||||
|
cloneGitRawRepository,
|
||||||
|
cloneRawGitRepositoryRemote,
|
||||||
|
} from "../providers/git";
|
||||||
|
import {
|
||||||
|
cloneRawGithubRepository,
|
||||||
|
cloneRawGithubRepositoryRemote,
|
||||||
|
} from "../providers/github";
|
||||||
|
import {
|
||||||
|
cloneRawGitlabRepository,
|
||||||
|
cloneRawGitlabRepositoryRemote,
|
||||||
|
} from "../providers/gitlab";
|
||||||
|
import {
|
||||||
|
createComposeFileRaw,
|
||||||
|
createComposeFileRawRemote,
|
||||||
|
} from "../providers/raw";
|
||||||
import { randomizeSpecificationFile } from "./compose";
|
import { randomizeSpecificationFile } from "./compose";
|
||||||
import type {
|
import type {
|
||||||
ComposeSpecification,
|
ComposeSpecification,
|
||||||
DefinitionsService,
|
DefinitionsService,
|
||||||
PropertiesNetworks,
|
PropertiesNetworks,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
|
import { execAsyncRemote } from "../process/execAsync";
|
||||||
|
|
||||||
export const cloneCompose = async (compose: Compose) => {
|
export const cloneCompose = async (compose: Compose) => {
|
||||||
if (compose.sourceType === "github") {
|
if (compose.sourceType === "github") {
|
||||||
@@ -31,6 +47,20 @@ export const cloneCompose = async (compose: Compose) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const cloneComposeRemote = async (compose: Compose) => {
|
||||||
|
if (compose.sourceType === "github") {
|
||||||
|
await cloneRawGithubRepositoryRemote(compose);
|
||||||
|
} else if (compose.sourceType === "gitlab") {
|
||||||
|
await cloneRawGitlabRepositoryRemote(compose);
|
||||||
|
} else if (compose.sourceType === "bitbucket") {
|
||||||
|
await cloneRawBitbucketRepositoryRemote(compose);
|
||||||
|
} else if (compose.sourceType === "git") {
|
||||||
|
await cloneRawGitRepositoryRemote(compose);
|
||||||
|
} else if (compose.sourceType === "raw") {
|
||||||
|
await createComposeFileRawRemote(compose);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const getComposePath = (compose: Compose) => {
|
export const getComposePath = (compose: Compose) => {
|
||||||
const { appName, sourceType, composePath } = compose;
|
const { appName, sourceType, composePath } = compose;
|
||||||
let path = "";
|
let path = "";
|
||||||
@@ -57,6 +87,23 @@ export const loadDockerCompose = async (
|
|||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const loadDockerComposeRemote = async (
|
||||||
|
compose: Compose,
|
||||||
|
): Promise<ComposeSpecification | null> => {
|
||||||
|
const path = getComposePath(compose);
|
||||||
|
try {
|
||||||
|
if (!compose.serverId) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const { stdout } = await execAsyncRemote(compose.serverId, `cat ${path}`);
|
||||||
|
if (!stdout) return null;
|
||||||
|
const parsedConfig = load(stdout) as ComposeSpecification;
|
||||||
|
return parsedConfig;
|
||||||
|
} catch (err) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const readComposeFile = async (compose: Compose) => {
|
export const readComposeFile = async (compose: Compose) => {
|
||||||
const path = getComposePath(compose);
|
const path = getComposePath(compose);
|
||||||
if (existsSync(path)) {
|
if (existsSync(path)) {
|
||||||
@@ -84,12 +131,39 @@ export const writeDomainsToCompose = async (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const writeDomainsToComposeRemote = async (
|
||||||
|
compose: Compose,
|
||||||
|
domains: Domain[],
|
||||||
|
) => {
|
||||||
|
if (!domains.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const composeConverted = await addDomainToCompose(compose, domains);
|
||||||
|
const path = getComposePath(compose);
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (compose.serverId) {
|
||||||
|
const composeString = dump(composeConverted, { lineWidth: 1000 });
|
||||||
|
return `echo "${composeString}" >> ${path};`;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const addDomainToCompose = async (
|
export const addDomainToCompose = async (
|
||||||
compose: Compose,
|
compose: Compose,
|
||||||
domains: Domain[],
|
domains: Domain[],
|
||||||
) => {
|
) => {
|
||||||
const { appName } = compose;
|
const { appName } = compose;
|
||||||
let result = await loadDockerCompose(compose);
|
|
||||||
|
let result: ComposeSpecification | null;
|
||||||
|
|
||||||
|
if (compose.serverId) {
|
||||||
|
result = await loadDockerComposeRemote(compose);
|
||||||
|
} else {
|
||||||
|
result = await loadDockerCompose(compose);
|
||||||
|
}
|
||||||
|
|
||||||
if (!result || domains.length === 0) {
|
if (!result || domains.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import type { InferResultType } from "@/server/types/with";
|
|||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
import { recreateDirectory } from "../filesystem/directory";
|
import { recreateDirectory } from "../filesystem/directory";
|
||||||
import { spawnAsync } from "../process/spawnAsync";
|
import { spawnAsync } from "../process/spawnAsync";
|
||||||
|
import { execAsyncRemote } from "../process/execAsync";
|
||||||
|
|
||||||
export type ApplicationWithBitbucket = InferResultType<
|
export type ApplicationWithBitbucket = InferResultType<
|
||||||
"applications",
|
"applications",
|
||||||
@@ -117,6 +118,46 @@ export const cloneRawBitbucketRepository = async (entity: Compose) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const cloneRawBitbucketRepositoryRemote = async (compose: Compose) => {
|
||||||
|
const {
|
||||||
|
appName,
|
||||||
|
bitbucketRepository,
|
||||||
|
bitbucketOwner,
|
||||||
|
bitbucketBranch,
|
||||||
|
bitbucketId,
|
||||||
|
serverId,
|
||||||
|
} = compose;
|
||||||
|
|
||||||
|
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}`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await execAsyncRemote(
|
||||||
|
serverId,
|
||||||
|
`git clone --branch ${bitbucketBranch} --depth 1 ${cloneUrl} ${outputPath}`,
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const getBitbucketCloneCommand = async (
|
export const getBitbucketCloneCommand = async (
|
||||||
entity: ApplicationWithBitbucket | ComposeWithBitbucket,
|
entity: ApplicationWithBitbucket | ComposeWithBitbucket,
|
||||||
logPath: string,
|
logPath: string,
|
||||||
|
|||||||
@@ -4,8 +4,9 @@ import { updateSSHKeyById } from "@/server/api/services/ssh-key";
|
|||||||
import { APPLICATIONS_PATH, COMPOSE_PATH, SSH_PATH } from "@/server/constants";
|
import { APPLICATIONS_PATH, COMPOSE_PATH, SSH_PATH } from "@/server/constants";
|
||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
import { recreateDirectory } from "../filesystem/directory";
|
import { recreateDirectory } from "../filesystem/directory";
|
||||||
import { execAsync } from "../process/execAsync";
|
import { execAsync, execAsyncRemote } from "../process/execAsync";
|
||||||
import { spawnAsync } from "../process/spawnAsync";
|
import { spawnAsync } from "../process/spawnAsync";
|
||||||
|
import { Compose } from "@/server/api/services/compose";
|
||||||
|
|
||||||
export const cloneGitRepository = async (
|
export const cloneGitRepository = async (
|
||||||
entity: {
|
entity: {
|
||||||
@@ -143,6 +144,7 @@ export const getCustomGitCloneCommand = async (
|
|||||||
command.push(`echo "Cloned Custom Git ${customGitUrl}: ✅" >> ${logPath}`);
|
command.push(`echo "Cloned Custom Git ${customGitUrl}: ✅" >> ${logPath}`);
|
||||||
return command.join("\n");
|
return command.join("\n");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -264,3 +266,63 @@ export const cloneGitRawRepository = async (entity: {
|
|||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const cloneRawGitRepositoryRemote = async (compose: Compose) => {
|
||||||
|
const {
|
||||||
|
appName,
|
||||||
|
customGitBranch,
|
||||||
|
customGitUrl,
|
||||||
|
customGitSSHKeyId,
|
||||||
|
serverId,
|
||||||
|
} = compose;
|
||||||
|
|
||||||
|
if (!serverId) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "NOT_FOUND",
|
||||||
|
message: "Server not found",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (!customGitUrl) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "NOT_FOUND",
|
||||||
|
message: "Git Provider not found",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const keyPath = path.join(SSH_PATH, `${customGitSSHKeyId}_rsa`);
|
||||||
|
const basePath = COMPOSE_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};`);
|
||||||
|
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} ; then
|
||||||
|
echo "[ERROR] Fail to clone the repository ";
|
||||||
|
exit 1;
|
||||||
|
fi
|
||||||
|
`,
|
||||||
|
);
|
||||||
|
|
||||||
|
await execAsyncRemote(serverId, command.join("\n"));
|
||||||
|
} catch (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import type { Compose } from "@/server/api/services/compose";
|
|||||||
import { type Github, findGithubById } from "@/server/api/services/github";
|
import { type Github, findGithubById } from "@/server/api/services/github";
|
||||||
import type { apiFindGithubBranches } from "@/server/db/schema";
|
import type { apiFindGithubBranches } from "@/server/db/schema";
|
||||||
import { executeCommand } from "../servers/command";
|
import { executeCommand } from "../servers/command";
|
||||||
|
import { execAsyncRemote } from "../process/execAsync";
|
||||||
|
|
||||||
export const authGithub = (githubProvider: Github) => {
|
export const authGithub = (githubProvider: Github) => {
|
||||||
if (!haveGithubRequirements(githubProvider)) {
|
if (!haveGithubRequirements(githubProvider)) {
|
||||||
@@ -233,6 +234,39 @@ export const cloneRawGithubRepository = async (entity: Compose) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const cloneRawGithubRepositoryRemote = async (compose: Compose) => {
|
||||||
|
const { appName, repository, owner, branch, githubId, serverId } = compose;
|
||||||
|
|
||||||
|
if (!serverId) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "NOT_FOUND",
|
||||||
|
message: "Server not found",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (!githubId) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "NOT_FOUND",
|
||||||
|
message: "GitHub Provider not found",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const githubProvider = await findGithubById(githubId);
|
||||||
|
const basePath = COMPOSE_PATH;
|
||||||
|
const outputPath = join(basePath, appName, "code");
|
||||||
|
const octokit = authGithub(githubProvider);
|
||||||
|
const token = await getGithubToken(octokit);
|
||||||
|
const repoclone = `github.com/${owner}/${repository}.git`;
|
||||||
|
await recreateDirectory(outputPath);
|
||||||
|
const cloneUrl = `https://oauth2:${token}@${repoclone}`;
|
||||||
|
try {
|
||||||
|
await execAsyncRemote(
|
||||||
|
serverId,
|
||||||
|
`git clone --branch ${branch} --depth 1 ${cloneUrl} ${outputPath}`,
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const getGithubRepositories = async (githubId?: string) => {
|
export const getGithubRepositories = async (githubId?: string) => {
|
||||||
if (!githubId) {
|
if (!githubId) {
|
||||||
return [];
|
return [];
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { TRPCError } from "@trpc/server";
|
|||||||
import { recreateDirectory } from "../filesystem/directory";
|
import { recreateDirectory } from "../filesystem/directory";
|
||||||
import { spawnAsync } from "../process/spawnAsync";
|
import { spawnAsync } from "../process/spawnAsync";
|
||||||
import { executeCommand } from "../servers/command";
|
import { executeCommand } from "../servers/command";
|
||||||
|
import { execAsyncRemote } from "../process/execAsync";
|
||||||
|
|
||||||
export const refreshGitlabToken = async (gitlabProviderId: string) => {
|
export const refreshGitlabToken = async (gitlabProviderId: string) => {
|
||||||
const gitlabProvider = await findGitlabById(gitlabProviderId);
|
const gitlabProvider = await findGitlabById(gitlabProviderId);
|
||||||
@@ -361,6 +362,39 @@ export const cloneRawGitlabRepository = async (entity: Compose) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const cloneRawGitlabRepositoryRemote = async (compose: Compose) => {
|
||||||
|
const { appName, gitlabPathNamespace, branch, gitlabId, serverId } = compose;
|
||||||
|
|
||||||
|
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 gitlabProvider = await findGitlabById(gitlabId);
|
||||||
|
|
||||||
|
await refreshGitlabToken(gitlabId);
|
||||||
|
const basePath = COMPOSE_PATH;
|
||||||
|
const outputPath = join(basePath, appName, "code");
|
||||||
|
await recreateDirectory(outputPath);
|
||||||
|
const repoclone = `gitlab.com/${gitlabPathNamespace}.git`;
|
||||||
|
const cloneUrl = `https://oauth2:${gitlabProvider?.accessToken}@${repoclone}`;
|
||||||
|
try {
|
||||||
|
await execAsyncRemote(
|
||||||
|
serverId,
|
||||||
|
`git clone --branch ${branch} --depth 1 ${cloneUrl} ${outputPath}`,
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const testGitlabConnection = async (
|
export const testGitlabConnection = async (
|
||||||
input: typeof apiGitlabTestConnection._type,
|
input: typeof apiGitlabTestConnection._type,
|
||||||
) => {
|
) => {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { join } from "node:path";
|
|||||||
import type { Compose } from "@/server/api/services/compose";
|
import type { Compose } from "@/server/api/services/compose";
|
||||||
import { COMPOSE_PATH } from "@/server/constants";
|
import { COMPOSE_PATH } from "@/server/constants";
|
||||||
import { recreateDirectory } from "../filesystem/directory";
|
import { recreateDirectory } from "../filesystem/directory";
|
||||||
|
import { execAsyncRemote } from "../process/execAsync";
|
||||||
|
|
||||||
export const createComposeFile = async (compose: Compose, logPath: string) => {
|
export const createComposeFile = async (compose: Compose, logPath: string) => {
|
||||||
const { appName, composeFile } = compose;
|
const { appName, composeFile } = compose;
|
||||||
@@ -45,3 +46,19 @@ export const createComposeFileRaw = async (compose: Compose) => {
|
|||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const createComposeFileRawRemote = async (compose: Compose) => {
|
||||||
|
const { appName, composeFile, serverId } = compose;
|
||||||
|
const outputPath = join(COMPOSE_PATH, appName, "code");
|
||||||
|
const filePath = join(outputPath, "docker-compose.yml");
|
||||||
|
|
||||||
|
try {
|
||||||
|
const command = `
|
||||||
|
mkdir -p ${outputPath};
|
||||||
|
echo "${composeFile}" > ${filePath};
|
||||||
|
`;
|
||||||
|
await execAsyncRemote(serverId, command);
|
||||||
|
} catch (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user