feat(multi server): add support for drag n drop

This commit is contained in:
Mauricio Siu
2024-09-15 17:05:17 -06:00
parent 0d3c978aad
commit 19295ba746
7 changed files with 103 additions and 17 deletions

View File

@@ -130,7 +130,7 @@ export const SaveDragNDrop = ({ applicationId }: Props) => {
type="submit"
className="w-fit"
isLoading={isLoading}
disabled={!zip}
disabled={!zip || isLoading}
>
Deploy{" "}
</Button>

View File

@@ -397,7 +397,7 @@ export const applicationRouter = createTRPCRouter({
});
const app = await findApplicationById(input.applicationId as string);
await unzipDrop(zipFile, app.appName);
await unzipDrop(zipFile, app);
const jobData: DeploymentJob = {
applicationId: app.applicationId,
@@ -405,6 +405,7 @@ export const applicationRouter = createTRPCRouter({
descriptionLog: "",
type: "deploy",
applicationType: "application",
server: !!app.serverId,
};
await myQueue.add(
"deployments",

View File

@@ -449,6 +449,9 @@ export const stopCompose = async (composeId: string) => {
const compose = await findComposeById(composeId);
try {
if (compose.composeType === "docker-compose") {
console.log(
`cd ${join(COMPOSE_PATH, compose.appName)} && docker compose -p ${compose.appName} stop`,
);
if (compose.serverId) {
await execAsyncRemote(
compose.serverId,

View File

@@ -9,7 +9,6 @@ import { COMPOSE_PATH } from "@/server/constants";
import type { InferResultType } from "@/server/types/with";
import boxen from "boxen";
import {
getComposePath,
writeDomainsToCompose,
writeDomainsToComposeRemote,
} from "../docker/domain";
@@ -114,11 +113,6 @@ Compose Type: ${composeType} ✅`;
cd "${projectPath}";
if [ ! -f "${composePath}" ]; then
echo "❌ Error: Compose file not found" >> "${logPath}";
exit 1;
fi
docker ${command.split(" ").join(" ")} >> "${logPath}" 2>&1 || { echo "Error: ❌ Docker command failed" >> "${logPath}"; exit 1; }
echo "Docker Compose Deployed: ✅" >> "${logPath}"

View File

@@ -2,12 +2,27 @@ import fs from "node:fs/promises";
import path, { join } from "node:path";
import { APPLICATIONS_PATH } from "@/server/constants";
import AdmZip from "adm-zip";
import { recreateDirectory } from "../filesystem/directory";
import {
recreateDirectory,
recreateDirectoryRemote,
} from "../filesystem/directory";
import type { Application } from "@/server/api/services/application";
import { execAsyncRemote } from "../process/execAsync";
import { Client, type SFTPWrapper } from "ssh2";
import { findServerById } from "@/server/api/services/server";
import { readSSHKey } from "../filesystem/ssh";
export const unzipDrop = async (zipFile: File, application: Application) => {
let sftp: SFTPWrapper | null = null;
export const unzipDrop = async (zipFile: File, appName: string) => {
try {
const { appName } = application;
const outputPath = join(APPLICATIONS_PATH, appName, "code");
await recreateDirectory(outputPath);
if (application.serverId) {
await recreateDirectoryRemote(outputPath, application.serverId);
} else {
await recreateDirectory(outputPath);
}
const arrayBuffer = await zipFile.arrayBuffer();
const buffer = Buffer.from(arrayBuffer);
@@ -28,6 +43,9 @@ export const unzipDrop = async (zipFile: File, appName: string) => {
? rootEntries[0]?.entryName.split("/")[0]
: "";
if (application.serverId) {
sftp = await getSFTPConnection(application.serverId);
}
for (const entry of zipEntries) {
let filePath = entry.entryName;
@@ -42,15 +60,64 @@ export const unzipDrop = async (zipFile: File, appName: string) => {
if (!filePath) continue;
const fullPath = path.join(outputPath, filePath);
if (entry.isDirectory) {
await fs.mkdir(fullPath, { recursive: true });
if (application.serverId) {
if (entry.isDirectory) {
await execAsyncRemote(application.serverId, `mkdir -p ${fullPath}`);
} else {
if (sftp === null) throw new Error("No SFTP connection available");
await uploadFileToServer(sftp, entry.getData(), fullPath);
}
} else {
await fs.mkdir(path.dirname(fullPath), { recursive: true });
await fs.writeFile(fullPath, entry.getData());
if (entry.isDirectory) {
await fs.mkdir(fullPath, { recursive: true });
} else {
await fs.mkdir(path.dirname(fullPath), { recursive: true });
await fs.writeFile(fullPath, entry.getData());
}
}
}
} catch (error) {
console.error("Error processing ZIP file:", error);
throw error;
} finally {
sftp?.end();
}
};
const getSFTPConnection = async (serverId: string): Promise<SFTPWrapper> => {
const server = await findServerById(serverId);
if (!server.sshKeyId) throw new Error("No SSH key available for this server");
const keys = await readSSHKey(server.sshKeyId);
return new Promise((resolve, reject) => {
const conn = new Client();
conn
.on("ready", () => {
conn.sftp((err, sftp) => {
if (err) return reject(err);
resolve(sftp);
});
})
.connect({
host: server.ipAddress,
port: server.port,
username: server.username,
privateKey: keys.privateKey,
timeout: 99999,
});
});
};
const uploadFileToServer = (
sftp: SFTPWrapper,
data: Buffer,
remotePath: string,
): Promise<void> => {
return new Promise((resolve, reject) => {
sftp.writeFile(remotePath, data, (err) => {
if (err) return reject(err);
resolve();
});
});
};

View File

@@ -151,14 +151,21 @@ export const writeDomainsToComposeRemote = async (
try {
const composeConverted = await addDomainToCompose(compose, domains);
const path = getComposePath(compose);
if (!composeConverted) {
return `
echo "❌ Error: Compose file not found" >> ${logPath};
exit 1;
`;
}
if (compose.serverId) {
const composeString = dump(composeConverted, { lineWidth: 1000 });
const encodedContent = encodeBase64(composeString);
return `echo "${encodedContent}" | base64 -d > "${path}";`;
}
} catch (error) {
return `
echo "❌ Has occured an error: ${error?.message || error}" >> ${logPath};
// @ts-ignore
return `echo "❌ Has occured an error: ${error?.message || error}" >> ${logPath};
exit 1;
`;
}

View File

@@ -17,6 +17,20 @@ export const recreateDirectory = async (pathFolder: string): Promise<void> => {
}
};
export const recreateDirectoryRemote = async (
pathFolder: string,
serverId: string | null,
): Promise<void> => {
try {
await execAsyncRemote(
serverId,
`rm -rf ${pathFolder}; mkdir -p ${pathFolder}`,
);
} catch (error) {
console.error(`Error recreating directory '${pathFolder}':`, error);
}
};
export const removeDirectoryIfExistsContent = async (
path: string,
): Promise<void> => {