mirror of
https://github.com/Dokploy/dokploy
synced 2025-06-26 18:27:59 +00:00
feat(multi server): add support for drag n drop
This commit is contained in:
@@ -130,7 +130,7 @@ export const SaveDragNDrop = ({ applicationId }: Props) => {
|
||||
type="submit"
|
||||
className="w-fit"
|
||||
isLoading={isLoading}
|
||||
disabled={!zip}
|
||||
disabled={!zip || isLoading}
|
||||
>
|
||||
Deploy{" "}
|
||||
</Button>
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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}"
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -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> => {
|
||||
|
||||
Reference in New Issue
Block a user