refactor(server): add support for multi server

This commit is contained in:
Mauricio Siu
2024-09-15 01:24:55 -06:00
parent 033bf66405
commit d2c8632c4f
26 changed files with 340 additions and 244 deletions

View File

@@ -16,6 +16,7 @@ import {
SelectValue,
} from "@/components/ui/select";
import { api } from "@/utils/api";
import { Loader2 } from "lucide-react";
import dynamic from "next/dynamic";
import { useEffect, useState } from "react";
export const DockerLogs = dynamic(
@@ -34,7 +35,7 @@ interface Props {
}
export const ShowDockerLogs = ({ appName, serverId }: Props) => {
const { data } = api.docker.getContainersByAppNameMatch.useQuery(
const { data, isLoading } = api.docker.getContainersByAppNameMatch.useQuery(
{
appName,
serverId,
@@ -64,7 +65,14 @@ export const ShowDockerLogs = ({ appName, serverId }: Props) => {
<Label>Select a container to view logs</Label>
<Select onValueChange={setContainerId} value={containerId}>
<SelectTrigger>
<SelectValue placeholder="Select a container" />
{isLoading ? (
<div className="flex flex-row gap-2 items-center justify-center text-sm text-muted-foreground">
<span>Loading...</span>
<Loader2 className="animate-spin size-4" />
</div>
) : (
<SelectValue placeholder="Select a container" />
)}
</SelectTrigger>
<SelectContent>
<SelectGroup>

View File

@@ -16,6 +16,7 @@ import {
SelectValue,
} from "@/components/ui/select";
import { api } from "@/utils/api";
import { Loader, Loader2 } from "lucide-react";
import dynamic from "next/dynamic";
import { useEffect, useState } from "react";
export const DockerLogs = dynamic(
@@ -39,7 +40,7 @@ export const ShowDockerLogsCompose = ({
appType,
serverId,
}: Props) => {
const { data } = api.docker.getContainersByAppNameMatch.useQuery(
const { data, isLoading } = api.docker.getContainersByAppNameMatch.useQuery(
{
appName,
appType,
@@ -70,7 +71,14 @@ export const ShowDockerLogsCompose = ({
<Label>Select a container to view logs</Label>
<Select onValueChange={setContainerId} value={containerId}>
<SelectTrigger>
<SelectValue placeholder="Select a container" />
{isLoading ? (
<div className="flex flex-row gap-2 items-center justify-center text-sm text-muted-foreground">
<span>Loading...</span>
<Loader2 className="animate-spin size-4" />
</div>
) : (
<SelectValue placeholder="Select a container" />
)}
</SelectTrigger>
<SelectContent>
<SelectGroup>

View File

@@ -20,6 +20,7 @@ import { api } from "@/utils/api";
import { useEffect, useState } from "react";
import { toast } from "sonner";
import { DockerMonitoring } from "../../monitoring/docker/show";
import { Loader2 } from "lucide-react";
interface Props {
appName: string;
@@ -32,7 +33,7 @@ export const ShowMonitoringCompose = ({
appType = "stack",
serverId,
}: Props) => {
const { data } = api.docker.getContainersByAppNameMatch.useQuery(
const { data, isLoading } = api.docker.getContainersByAppNameMatch.useQuery(
{
appName: appName,
appType,
@@ -49,7 +50,7 @@ export const ShowMonitoringCompose = ({
const [containerId, setContainerId] = useState<string | undefined>();
const { mutateAsync: restart, isLoading } =
const { mutateAsync: restart, isLoading: isRestarting } =
api.docker.restartContainer.useMutation();
useEffect(() => {
@@ -80,7 +81,14 @@ export const ShowMonitoringCompose = ({
value={containerAppName}
>
<SelectTrigger>
<SelectValue placeholder="Select a container" />
{isLoading ? (
<div className="flex flex-row gap-2 items-center justify-center text-sm text-muted-foreground">
<span>Loading...</span>
<Loader2 className="animate-spin size-4" />
</div>
) : (
<SelectValue placeholder="Select a container" />
)}
</SelectTrigger>
<SelectContent>
<SelectGroup>
@@ -98,7 +106,7 @@ export const ShowMonitoringCompose = ({
</SelectContent>
</Select>
<Button
isLoading={isLoading}
isLoading={isRestarting}
onClick={async () => {
if (!containerId) return;
toast.success(`Restarting container ${containerAppName}`);

View File

@@ -17,6 +17,7 @@ import {
SelectValue,
} from "@/components/ui/select";
import { api } from "@/utils/api";
import { Loader2 } from "lucide-react";
import dynamic from "next/dynamic";
import type React from "react";
import { useEffect, useState } from "react";
@@ -38,7 +39,7 @@ interface Props {
}
export const DockerTerminalModal = ({ children, appName, serverId }: Props) => {
const { data } = api.docker.getContainersByAppNameMatch.useQuery(
const { data, isLoading } = api.docker.getContainersByAppNameMatch.useQuery(
{
appName,
serverId,
@@ -67,7 +68,14 @@ export const DockerTerminalModal = ({ children, appName, serverId }: Props) => {
<Label>Select a container to view logs</Label>
<Select onValueChange={setContainerId} value={containerId}>
<SelectTrigger>
<SelectValue placeholder="Select a container" />
{isLoading ? (
<div className="flex flex-row gap-2 items-center justify-center text-sm text-muted-foreground">
<span>Loading...</span>
<Loader2 className="animate-spin size-4" />
</div>
) : (
<SelectValue placeholder="Select a container" />
)}
</SelectTrigger>
<SelectContent>
<SelectGroup>

View File

@@ -18,6 +18,7 @@ import {
SelectValue,
} from "@/components/ui/select";
import { api } from "@/utils/api";
import { Loader2 } from "lucide-react";
import dynamic from "next/dynamic";
import type React from "react";
import { useEffect, useState } from "react";
@@ -38,7 +39,7 @@ interface Props {
}
export const ShowModalLogs = ({ appName, children }: Props) => {
const { data } = api.docker.getContainersByAppLabel.useQuery(
const { data, isLoading } = api.docker.getContainersByAppLabel.useQuery(
{
appName,
},
@@ -72,7 +73,14 @@ export const ShowModalLogs = ({ appName, children }: Props) => {
<Label>Select a container to view logs</Label>
<Select onValueChange={setContainerId} value={containerId}>
<SelectTrigger>
<SelectValue placeholder="Select a container" />
{isLoading ? (
<div className="flex flex-row gap-2 items-center justify-center text-sm text-muted-foreground">
<span>Loading...</span>
<Loader2 className="animate-spin size-4" />
</div>
) : (
<SelectValue placeholder="Select a container" />
)}
</SelectTrigger>
<SelectContent>
<SelectGroup>

View File

@@ -23,7 +23,7 @@ import {
type DeploymentJob,
cleanQueuesByApplication,
} from "@/server/queues/deployments-queue";
import { enqueueDeploymentJob, myQueue } from "@/server/queues/queueSetup";
import { myQueue } from "@/server/queues/queueSetup";
import {
removeService,
startService,
@@ -134,12 +134,19 @@ export const applicationRouter = createTRPCRouter({
.returning();
const cleanupOperations = [
async () => deleteAllMiddlewares(application),
async () => await deleteAllMiddlewares(application),
async () => await removeDeployments(application),
async () => await removeDirectoryCode(application?.appName),
async () => await removeMonitoringDirectory(application?.appName),
async () => await removeTraefikConfig(application?.appName),
async () => await removeService(application?.appName),
async () =>
await removeDirectoryCode(application.appName, application.serverId),
async () =>
await removeMonitoringDirectory(
application.appName,
application.serverId,
),
async () =>
await removeTraefikConfig(application.appName, application.serverId),
async () =>
await removeService(application?.appName, application.serverId),
];
for (const operation of cleanupOperations) {
@@ -327,7 +334,7 @@ export const applicationRouter = createTRPCRouter({
deploy: protectedProcedure
.input(apiFindOneApplication)
.mutation(async ({ input, ctx }) => {
const application = await findApplicationById(input.applicationId);
// const application = await findApplicationById(input.applicationId);
const jobData: DeploymentJob = {
applicationId: input.applicationId,
titleLog: "Manual deployment",
@@ -335,10 +342,18 @@ export const applicationRouter = createTRPCRouter({
type: "deploy",
applicationType: "application",
};
if (!application.serverId) {
} else {
await enqueueDeploymentJob(application.serverId, jobData);
}
await myQueue.add(
"deployments",
{ ...jobData },
{
removeOnComplete: true,
removeOnFail: true,
},
);
// if (!application.serverId) {
// } else {
// await enqueueDeploymentJob(application.serverId, jobData);
// }
}),
cleanQueues: protectedProcedure

View File

@@ -13,7 +13,7 @@ import {
type DeploymentJob,
cleanQueuesByCompose,
} from "@/server/queues/deployments-queue";
import { enqueueDeploymentJob, myQueue } from "@/server/queues/queueSetup";
import { myQueue } from "@/server/queues/queueSetup";
import { createCommand } from "@/server/utils/builders/compose";
import {
randomizeComposeFile,
@@ -176,14 +176,7 @@ export const composeRouter = createTRPCRouter({
// applicationType: "compose",
// descriptionLog: "",
// };
// await myQueue.add(
// "deployments",
// { ...jobData },
// {
// removeOnComplete: true,
// removeOnFail: true,
// },
// );
const compose = await findComposeById(input.composeId);
const jobData: DeploymentJob = {
composeId: input.composeId,
@@ -192,10 +185,18 @@ export const composeRouter = createTRPCRouter({
applicationType: "compose",
descriptionLog: "",
};
if (!compose.serverId) {
} else {
await enqueueDeploymentJob(compose.serverId, jobData);
}
await myQueue.add(
"deployments",
{ ...jobData },
{
removeOnComplete: true,
removeOnFail: true,
},
);
// if (!compose.serverId) {
// } else {
// await enqueueDeploymentJob(compose.serverId, jobData);
// }
}),
redeploy: protectedProcedure
.input(apiFindCompose)

View File

@@ -38,6 +38,7 @@ import {
} from "@/server/utils/providers/gitlab";
import { validUniqueServerAppName } from "./project";
import { executeCommand } from "@/server/utils/servers/command";
import { execAsyncRemote } from "@/server/utils/process/execAsync";
export type Application = typeof applications.$inferSelect;
export const createApplication = async (
@@ -164,7 +165,7 @@ export const deployApplication = async ({
try {
if (application.serverId) {
let command = "set -e";
let command = "set -e;";
if (application.sourceType === "github") {
command += await getGithubCloneCommand(application, deployment.logPath);
} else if (application.sourceType === "gitlab") {
@@ -186,8 +187,7 @@ export const deployApplication = async ({
if (application.sourceType !== "docker") {
command += getBuildCommand(application, deployment.logPath);
}
await executeCommand(application.serverId, command);
await execAsyncRemote(application.serverId, command);
await mechanizeDockerContainer(application);
} else {
if (application.sourceType === "github") {
@@ -209,8 +209,6 @@ export const deployApplication = async ({
}
}
console.log("Command", "Finish");
await updateDeploymentStatus(deployment.deploymentId, "done");
await updateApplicationStatus(applicationId, "done");
@@ -263,18 +261,31 @@ export const rebuildApplication = async ({
});
try {
if (application.sourceType === "github") {
await buildApplication(application, deployment.logPath);
} else if (application.sourceType === "gitlab") {
await buildApplication(application, deployment.logPath);
} else if (application.sourceType === "bitbucket") {
await buildApplication(application, deployment.logPath);
} else if (application.sourceType === "docker") {
await buildDocker(application, deployment.logPath);
} else if (application.sourceType === "git") {
await buildApplication(application, deployment.logPath);
} else if (application.sourceType === "drop") {
await buildApplication(application, deployment.logPath);
if (application.serverId) {
let command = "set -e;";
if (application.sourceType === "docker") {
command += await buildRemoteDocker(application, deployment.logPath);
}
if (application.sourceType !== "docker") {
command += getBuildCommand(application, deployment.logPath);
}
await execAsyncRemote(application.serverId, command);
await mechanizeDockerContainer(application);
} else {
if (application.sourceType === "github") {
await buildApplication(application, deployment.logPath);
} else if (application.sourceType === "gitlab") {
await buildApplication(application, deployment.logPath);
} else if (application.sourceType === "bitbucket") {
await buildApplication(application, deployment.logPath);
} else if (application.sourceType === "docker") {
await buildDocker(application, deployment.logPath);
} else if (application.sourceType === "git") {
await buildApplication(application, deployment.logPath);
} else if (application.sourceType === "drop") {
await buildApplication(application, deployment.logPath);
}
}
await updateDeploymentStatus(deployment.deploymentId, "done");
await updateApplicationStatus(applicationId, "done");

View File

@@ -241,20 +241,6 @@ export const deployCompose = async ({
} else if (compose.sourceType === "raw") {
command += getCreateComposeFileCommand(compose);
}
// Promise.resolve()
// .then(() => {
// return execAsyncRemote(compose.serverId, command);
// })
// .then(() => {
// return getBuildComposeCommand(compose, deployment.logPath);
// })
// .catch((err) => {
// throw err;
// })
// .then(() => {
// console.log(" ---- done ----");
// });
async function* sequentialSteps() {
yield execAsyncRemote(compose.serverId, command);
yield getBuildComposeCommand(compose, deployment.logPath);
@@ -262,9 +248,6 @@ export const deployCompose = async ({
const steps = sequentialSteps();
for await (const step of steps) {
if (step.stderr) {
console.log(step.stderr);
}
step;
}
@@ -330,7 +313,12 @@ export const rebuildCompose = async ({
});
try {
await buildCompose(compose, deployment.logPath);
if (compose.serverId) {
await getBuildComposeCommand(compose, deployment.logPath);
} else {
await buildCompose(compose, deployment.logPath);
}
await updateDeploymentStatus(deployment.deploymentId, "done");
await updateCompose(composeId, {
composeStatus: "done",
@@ -351,13 +339,24 @@ export const removeCompose = async (compose: Compose) => {
const projectPath = join(COMPOSE_PATH, compose.appName);
if (compose.composeType === "stack") {
await execAsync(`docker stack rm ${compose.appName}`, {
const command = `cd ${projectPath} && docker stack rm ${compose.appName} && rm -rf ${projectPath}`;
if (compose.serverId) {
await execAsyncRemote(compose.serverId, command);
} else {
await execAsync(command);
}
await execAsync(command, {
cwd: projectPath,
});
} else {
await execAsync(`docker compose -p ${compose.appName} down`, {
cwd: projectPath,
});
const command = `cd ${projectPath} && docker compose -p ${compose.appName} down && rm -rf ${projectPath}`;
if (compose.serverId) {
await execAsyncRemote(compose.serverId, command);
} else {
await execAsync(command, {
cwd: projectPath,
});
}
}
} catch (error) {
throw error;
@@ -370,9 +369,16 @@ export const stopCompose = async (composeId: string) => {
const compose = await findComposeById(composeId);
try {
if (compose.composeType === "docker-compose") {
await execAsync(`docker compose -p ${compose.appName} stop`, {
cwd: join(COMPOSE_PATH, compose.appName),
});
if (compose.serverId) {
await execAsyncRemote(
compose.serverId,
`cd ${join(COMPOSE_PATH, compose.appName)} && docker compose -p ${compose.appName} stop`,
);
} else {
await execAsync(`docker compose -p ${compose.appName} stop`, {
cwd: join(COMPOSE_PATH, compose.appName),
});
}
}
await updateCompose(composeId, {

View File

@@ -16,6 +16,7 @@ import { type Application, findApplicationById } from "./application";
import { type Compose, findComposeById } from "./compose";
import { findServerById, type Server } from "./server";
import { executeCommand } from "@/server/utils/servers/command";
import { execAsyncRemote } from "@/server/utils/process/execAsync";
export type Deployment = typeof deployments.$inferSelect;
@@ -208,14 +209,23 @@ const removeLastTenComposeDeployments = async (composeId: string) => {
export const removeDeployments = async (application: Application) => {
const { appName, applicationId } = application;
const logsPath = path.join(LOGS_PATH, appName);
await removeDirectoryIfExistsContent(logsPath);
if (application.serverId) {
await execAsyncRemote(application.serverId, `rm -rf ${logsPath}`);
} else {
await removeDirectoryIfExistsContent(logsPath);
}
await removeDeploymentsByApplicationId(applicationId);
};
export const removeDeploymentsByComposeId = async (compose: Compose) => {
const { appName } = compose;
const logsPath = path.join(LOGS_PATH, appName);
await removeDirectoryIfExistsContent(logsPath);
if (compose.serverId) {
await execAsyncRemote(compose.serverId, `rm -rf ${logsPath}`);
} else {
await removeDirectoryIfExistsContent(logsPath);
}
await db
.delete(deployments)
.where(eq(deployments.composeId, compose.composeId))

View File

@@ -29,52 +29,52 @@ type DeployJob =
export type DeploymentJob = DeployJob;
// export const deploymentWorker = new Worker(
// "deployments",
// async (job: Job<DeploymentJob>) => {
// try {
// if (job.data.applicationType === "application") {
// await updateApplicationStatus(job.data.applicationId, "running");
// if (job.data.type === "redeploy") {
// await rebuildApplication({
// applicationId: job.data.applicationId,
// titleLog: job.data.titleLog,
// descriptionLog: job.data.descriptionLog,
// });
// } else if (job.data.type === "deploy") {
// await deployApplication({
// applicationId: job.data.applicationId,
// titleLog: job.data.titleLog,
// descriptionLog: job.data.descriptionLog,
// });
// }
// } else if (job.data.applicationType === "compose") {
// await updateCompose(job.data.composeId, {
// composeStatus: "running",
// });
// if (job.data.type === "deploy") {
// await deployCompose({
// composeId: job.data.composeId,
// titleLog: job.data.titleLog,
// descriptionLog: job.data.descriptionLog,
// });
// } else if (job.data.type === "redeploy") {
// await rebuildCompose({
// composeId: job.data.composeId,
// titleLog: job.data.titleLog,
// descriptionLog: job.data.descriptionLog,
// });
// }
// }
// } catch (error) {
// console.log("Error", error);
// }
// },
// {
// autorun: false,
// connection: redisConfig,
// },
// );
export const deploymentWorker = new Worker(
"deployments",
async (job: Job<DeploymentJob>) => {
try {
if (job.data.applicationType === "application") {
await updateApplicationStatus(job.data.applicationId, "running");
if (job.data.type === "redeploy") {
await rebuildApplication({
applicationId: job.data.applicationId,
titleLog: job.data.titleLog,
descriptionLog: job.data.descriptionLog,
});
} else if (job.data.type === "deploy") {
await deployApplication({
applicationId: job.data.applicationId,
titleLog: job.data.titleLog,
descriptionLog: job.data.descriptionLog,
});
}
} else if (job.data.applicationType === "compose") {
await updateCompose(job.data.composeId, {
composeStatus: "running",
});
if (job.data.type === "deploy") {
await deployCompose({
composeId: job.data.composeId,
titleLog: job.data.titleLog,
descriptionLog: job.data.descriptionLog,
});
} else if (job.data.type === "redeploy") {
await rebuildCompose({
composeId: job.data.composeId,
titleLog: job.data.titleLog,
descriptionLog: job.data.descriptionLog,
});
}
}
} catch (error) {
console.log("Error", error);
}
},
{
autorun: false,
connection: redisConfig,
},
);
export const cleanQueuesByApplication = async (applicationId: string) => {
const jobs = await myQueue.getJobs(["waiting", "delayed"]);

View File

@@ -13,10 +13,10 @@ import {
} from "../api/services/compose";
export const redisConfig: ConnectionOptions = {
host: "31.220.108.27",
password: "xYBugfHkULig1iLN",
// host: process.env.NODE_ENV === "production" ? "dokploy-redis" : "127.0.0.1",
port: 1233,
// host: "31.220.108.27",
// password: "xYBugfHkULig1iLN",
host: process.env.NODE_ENV === "production" ? "dokploy-redis" : "127.0.0.1",
// port: 1233,
};
// TODO: maybe add a options to clean the queue to the times
const myQueue = new Queue("deployments", {
@@ -48,83 +48,3 @@ function createRedisConnection(server: Server) {
port: "6379",
} as ConnectionOptions;
}
async function setupServerQueueAndWorker(server: Server) {
const connection = createRedisConnection(server);
if (!workersMap.has(server.serverId)) {
const queue = new Queue(`deployments-${server.serverId}`, {
connection,
});
const worker = new Worker(
`deployments-${server.serverId}`,
async (job: Job<DeploymentJob>) => {
// Ejecuta el trabajo de despliegue
try {
if (job.data.applicationType === "application") {
await updateApplicationStatus(job.data.applicationId, "running");
if (job.data.type === "redeploy") {
await rebuildApplication({
applicationId: job.data.applicationId,
titleLog: job.data.titleLog,
descriptionLog: job.data.descriptionLog,
});
} else if (job.data.type === "deploy") {
await deployApplication({
applicationId: job.data.applicationId,
titleLog: job.data.titleLog,
descriptionLog: job.data.descriptionLog,
});
}
} else if (job.data.applicationType === "compose") {
await updateCompose(job.data.composeId, {
composeStatus: "running",
});
if (job.data.type === "deploy") {
await deployCompose({
composeId: job.data.composeId,
titleLog: job.data.titleLog,
descriptionLog: job.data.descriptionLog,
});
} else if (job.data.type === "redeploy") {
await rebuildCompose({
composeId: job.data.composeId,
titleLog: job.data.titleLog,
descriptionLog: job.data.descriptionLog,
});
}
}
} catch (error) {
console.log("Error", error);
}
},
{
limiter: {
max: 1,
duration: 1000,
},
connection,
},
);
// Almacena worker y queue para reutilizar
workersMap.set(server.serverId, worker);
queuesMap.set(server.serverId, queue);
}
return {
queue: queuesMap.get(server.serverId),
worker: workersMap.get(server.serverId),
};
}
export async function enqueueDeploymentJob(
serverId: string,
jobData: DeploymentJob,
) {
const server = await findServerById(serverId);
const { queue } = await setupServerQueueAndWorker(server);
await queue?.add(`deployments-${serverId}`, jobData, {
removeOnComplete: true,
removeOnFail: true,
});
}

View File

@@ -24,6 +24,7 @@ import {
setupTerminalWebSocketServer,
} from "./wss/terminal";
import { IS_CLOUD } from "./constants";
import { deploymentWorker } from "./queues/deployments-queue";
config({ path: ".env" });
const PORT = Number.parseInt(process.env.PORT || "3000", 10);
@@ -68,7 +69,7 @@ void app.prepare().then(async () => {
server.listen(PORT);
console.log("Server Started:", PORT);
// deploymentWorker.run();
deploymentWorker.run();
} catch (e) {
console.error("Main Server Error", e);
}

View File

@@ -98,14 +98,24 @@ Compose Type: ${composeType} ✅`;
});
const bashCommand = `
set -e;
echo "${logBox}" >> ${logPath};
${newCompose}
${envCommand}
cd ${projectPath} || exit 1;
docker ${command.split(" ").join(" ")} >> ${logPath} 2>&1;
echo "Docker Compose Deployed: ✅" >> ${logPath};
`;
set -e
{
echo "${logBox}" >> "${logPath}"
${newCompose}
${envCommand}
cd "${projectPath}";
docker ${command.split(" ").join(" ")} >> "${logPath}" 2>&1 || { echo "Error: ❌ Docker command failed" >> "${logPath}"; exit 1; }
echo "Docker Compose Deployed: ✅" >> "${logPath}"
} || {
echo "Error: ❌ Script execution failed" >> "${logPath}"
exit 1
}
`;
return await execAsyncRemote(compose.serverId, bashCommand);
};

View File

@@ -30,8 +30,6 @@ export const buildHeroku = async (
if (writeStream.writable) {
writeStream.write(data);
}
// Stream the data
console.log(data);
});
return true;
} catch (e) {

View File

@@ -94,7 +94,7 @@ export const getNixpacksCommand = (
/* No need for any start command, since we'll use nginx later on */
args.push("--no-error-without-start");
}
console.log("args", args);
const command = `nixpacks ${args.join(" ")}`;
const bashCommand = `
echo "Starting nixpacks build..." >> ${logPath};
@@ -108,6 +108,9 @@ echo "Nixpacks build completed." >> ${logPath};
Then, remove the container and create a static build.
*/
if (publishDirectory) {
}
// if (publishDirectory) {
// bashCommand += `
// docker create --name ${buildContainerId} ${appName}

View File

@@ -137,7 +137,7 @@ export const writeDomainsToComposeRemote = async (
domains: Domain[],
) => {
if (!domains.length) {
return;
return "";
}
const composeConverted = await addDomainToCompose(compose, domains);
const path = getComposePath(compose);

View File

@@ -214,9 +214,17 @@ export const startServiceRemote = async (serverId: string, appName: string) => {
}
};
export const removeService = async (appName: string) => {
export const removeService = async (
appName: string,
serverId?: string | null,
) => {
try {
await execAsync(`docker service rm ${appName}`);
const command = `docker service rm ${appName}`;
if (serverId) {
await execAsyncRemote(serverId, command);
} else {
await execAsync(command);
}
} catch (error) {
return error;
}

View File

@@ -6,7 +6,7 @@ import {
COMPOSE_PATH,
MONITORING_PATH,
} from "@/server/constants";
import { execAsync } from "../process/execAsync";
import { execAsync, execAsyncRemote } from "../process/execAsync";
export const recreateDirectory = async (pathFolder: string): Promise<void> => {
try {
@@ -34,31 +34,54 @@ export const removeFileOrDirectory = async (path: string) => {
}
};
export const removeDirectoryCode = async (appName: string) => {
export const removeDirectoryCode = async (
appName: string,
serverId?: string | null,
) => {
const directoryPath = path.join(APPLICATIONS_PATH, appName);
const command = `rm -rf ${directoryPath}`;
try {
await execAsync(`rm -rf ${directoryPath}`);
if (serverId) {
await execAsyncRemote(serverId, command);
} else {
await execAsync(command);
}
} catch (error) {
console.error(`Error to remove ${directoryPath}: ${error}`);
throw error;
}
};
export const removeComposeDirectory = async (appName: string) => {
export const removeComposeDirectory = async (
appName: string,
serverId?: string | null,
) => {
const directoryPath = path.join(COMPOSE_PATH, appName);
const command = `rm -rf ${directoryPath}`;
try {
await execAsync(`rm -rf ${directoryPath}`);
if (serverId) {
await execAsyncRemote(serverId, command);
} else {
await execAsync(command);
}
} catch (error) {
console.error(`Error to remove ${directoryPath}: ${error}`);
throw error;
}
};
export const removeMonitoringDirectory = async (appName: string) => {
export const removeMonitoringDirectory = async (
appName: string,
serverId?: string | null,
) => {
const directoryPath = path.join(MONITORING_PATH, appName);
const command = `rm -rf ${directoryPath}`;
try {
await execAsync(`rm -rf ${directoryPath}`);
if (serverId) {
await execAsyncRemote(serverId, command);
} else {
await execAsync(command);
}
} catch (error) {
console.error(`Error to remove ${directoryPath}: ${error}`);
throw error;

View File

@@ -33,7 +33,11 @@ export const execAsyncRemote = async (
if (code === 0) {
resolve({ stdout, stderr });
} else {
reject(new Error(`Command exited with code ${code}`));
reject(
new Error(
`Command exited with code ${code}. Stderr: ${stderr}, command: ${command}`,
),
);
}
})
.on("data", (data: string) => {

View File

@@ -181,6 +181,11 @@ export const getBitbucketCloneCommand = async (
}
if (!bitbucketId) {
const command = `
echo "Error: ❌ Bitbucket Provider not found" >> ${logPath};
exit 1;
`;
await execAsyncRemote(serverId, command);
throw new TRPCError({
code: "NOT_FOUND",
message: "Bitbucket Provider not found",
@@ -198,7 +203,7 @@ export const getBitbucketCloneCommand = async (
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};
echo "[ERROR] Fail to clone the repository ${repoclone}" >> ${logPath};
exit 1;
fi
echo "Cloned ${repoclone} to ${outputPath}: ✅" >> ${logPath};

View File

@@ -6,7 +6,7 @@ import { TRPCError } from "@trpc/server";
import { recreateDirectory } from "../filesystem/directory";
import { execAsync, execAsyncRemote } from "../process/execAsync";
import { spawnAsync } from "../process/spawnAsync";
import { Compose } from "@/server/api/services/compose";
import type { Compose } from "@/server/api/services/compose";
export const cloneGitRepository = async (
entity: {
@@ -108,6 +108,12 @@ export const getCustomGitCloneCommand = async (
} = entity;
if (!customGitUrl || !customGitBranch) {
const command = `
echo "Error: ❌ Repository not found" >> ${logPath};
exit 1;
`;
await execAsyncRemote(serverId, command);
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error: Repository not found",
@@ -143,7 +149,7 @@ export const getCustomGitCloneCommand = async (
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};
echo "[ERROR] Fail to clone the repository ${customGitUrl}" >> ${logPath};
exit 1;
fi
`,

View File

@@ -151,7 +151,20 @@ export const getGithubCloneCommand = async (
) => {
const { appName, repository, owner, branch, githubId, serverId } = entity;
if (!githubId || !serverId) {
if (!serverId) {
throw new TRPCError({
code: "NOT_FOUND",
message: "Server not found",
});
}
if (!githubId) {
const command = `
echo "Error: ❌ Github Provider not found" >> ${logPath};
exit 1;
`;
await executeCommand(serverId, command);
throw new TRPCError({
code: "NOT_FOUND",
message: "GitHub Provider not found",
@@ -192,7 +205,7 @@ export const getGithubCloneCommand = async (
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};
echo "[ERROR] Fallo al clonar el repositorio ${repoclone}" >> ${logPath};
exit 1;
fi
echo "Cloned ${repoclone} to ${outputPath}: ✅" >> ${logPath};

View File

@@ -179,6 +179,12 @@ export const getGitlabCloneCommand = async (
}
if (!gitlabId) {
const command = `
echo "Error: ❌ Gitlab Provider not found" >> ${logPath};
exit 1;
`;
await execAsyncRemote(serverId, command);
throw new TRPCError({
code: "NOT_FOUND",
message: "Gitlab Provider not found",
@@ -203,7 +209,7 @@ export const getGitlabCloneCommand = async (
exit 1; # Exit with error code
`;
await executeCommand(serverId, bashCommand);
await execAsyncRemote(serverId, bashCommand);
return;
}
@@ -218,7 +224,7 @@ export const getGitlabCloneCommand = async (
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};
echo "[ERROR] Fail to clone the repository ${repoclone}" >> ${logPath};
exit 1;
fi
echo "Cloned ${repoclone} to ${outputPath}: ✅" >> ${logPath};

View File

@@ -47,9 +47,20 @@ export const createTraefikConfig = (appName: string) => {
);
};
export const removeTraefikConfig = async (appName: string) => {
export const removeTraefikConfig = async (
appName: string,
serverId?: string | null,
) => {
try {
const configPath = path.join(DYNAMIC_TRAEFIK_PATH, `${appName}.yml`);
if (serverId) {
await execAsyncRemote(serverId, `rm ${configPath}`);
} else {
if (fs.existsSync(configPath)) {
await fs.promises.unlink(configPath);
}
}
if (fs.existsSync(configPath)) {
await fs.promises.unlink(configPath);
}

View File

@@ -5,6 +5,7 @@ import { dump, load } from "js-yaml";
import type { ApplicationNested } from "../builders";
import type { FileConfig } from "./file-types";
import { execAsyncRemote } from "../process/execAsync";
import { writeTraefikConfigRemote } from "./application";
export const addMiddleware = (config: FileConfig, middlewareName: string) => {
if (config.http?.routers) {
@@ -43,7 +44,7 @@ export const deleteMiddleware = (
}
};
export const deleteAllMiddlewares = (application: ApplicationNested) => {
export const deleteAllMiddlewares = async (application: ApplicationNested) => {
const config = loadMiddlewares<FileConfig>();
const { security, appName, redirects } = application;
@@ -60,7 +61,11 @@ export const deleteAllMiddlewares = (application: ApplicationNested) => {
}
}
writeMiddleware(config);
if (application.serverId) {
await writeTraefikConfigRemote(config, "middlewares", application.serverId);
} else {
writeMiddleware(config);
}
};
export const loadMiddlewares = <T>() => {