mirror of
https://github.com/Dokploy/dokploy
synced 2025-06-26 18:27:59 +00:00
refactor(volumes): rework volumes and paths
This commit is contained in:
@@ -63,6 +63,7 @@ const mySchema = z.discriminatedUnion("type", [
|
|||||||
z
|
z
|
||||||
.object({
|
.object({
|
||||||
type: z.literal("file"),
|
type: z.literal("file"),
|
||||||
|
filePath: z.string().min(1, "File path required"),
|
||||||
content: z.string().optional(),
|
content: z.string().optional(),
|
||||||
})
|
})
|
||||||
.merge(mountSchema),
|
.merge(mountSchema),
|
||||||
@@ -81,7 +82,7 @@ export const AddVolumes = ({
|
|||||||
defaultValues: {
|
defaultValues: {
|
||||||
type: serviceType === "compose" ? "file" : "bind",
|
type: serviceType === "compose" ? "file" : "bind",
|
||||||
hostPath: "",
|
hostPath: "",
|
||||||
mountPath: "",
|
mountPath: serviceType === "compose" ? "/" : "",
|
||||||
},
|
},
|
||||||
resolver: zodResolver(mySchema),
|
resolver: zodResolver(mySchema),
|
||||||
});
|
});
|
||||||
@@ -125,6 +126,7 @@ export const AddVolumes = ({
|
|||||||
serviceId,
|
serviceId,
|
||||||
content: data.content,
|
content: data.content,
|
||||||
mountPath: data.mountPath,
|
mountPath: data.mountPath,
|
||||||
|
filePath: data.filePath,
|
||||||
type: data.type,
|
type: data.type,
|
||||||
serviceType,
|
serviceType,
|
||||||
})
|
})
|
||||||
@@ -288,41 +290,62 @@ export const AddVolumes = ({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{type === "file" && (
|
{type === "file" && (
|
||||||
|
<>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="content"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Content</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<FormControl>
|
||||||
|
<Textarea
|
||||||
|
placeholder="Any content"
|
||||||
|
className="h-64"
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="filePath"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>File Path</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
placeholder="Name of the file"
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{serviceType === "application" && (
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="content"
|
name="mountPath"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Content</FormLabel>
|
<FormLabel>Mount Path (In the container)</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<FormControl>
|
<Input placeholder="Mount Path" {...field} />
|
||||||
<Textarea
|
|
||||||
placeholder="Any content"
|
|
||||||
className="h-64"
|
|
||||||
{...field}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="mountPath"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>Mount Path</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input placeholder="Mount Path" {...field} />
|
|
||||||
</FormControl>
|
|
||||||
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ export const ShowVolumesCompose = ({ composeId }: Props) => {
|
|||||||
key={mount.mountId}
|
key={mount.mountId}
|
||||||
className="flex w-full flex-col sm:flex-row sm:items-center justify-between gap-4 sm:gap-10 border rounded-lg p-4"
|
className="flex w-full flex-col sm:flex-row sm:items-center justify-between gap-4 sm:gap-10 border rounded-lg p-4"
|
||||||
>
|
>
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 flex-col gap-4 sm:gap-8">
|
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 flex-col gap-4 sm:gap-8">
|
||||||
<div className="flex flex-col gap-1">
|
<div className="flex flex-col gap-1">
|
||||||
<span className="font-medium">Mount Type</span>
|
<span className="font-medium">Mount Type</span>
|
||||||
<span className="text-sm text-muted-foreground">
|
<span className="text-sm text-muted-foreground">
|
||||||
@@ -91,12 +91,20 @@ export const ShowVolumesCompose = ({ composeId }: Props) => {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{mount.type === "file" && (
|
{mount.type === "file" && (
|
||||||
<div className="flex flex-col gap-1">
|
<>
|
||||||
<span className="font-medium">Content</span>
|
<div className="flex flex-col gap-1">
|
||||||
<span className="text-sm text-muted-foreground w-40 truncate">
|
<span className="font-medium">Content</span>
|
||||||
{mount.content}
|
<span className="text-sm text-muted-foreground w-40 truncate">
|
||||||
</span>
|
{mount.content}
|
||||||
</div>
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
|
<span className="font-medium">File Path</span>
|
||||||
|
<span className="text-sm text-muted-foreground">
|
||||||
|
{mount.filePath}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
{mount.type === "bind" && (
|
{mount.type === "bind" && (
|
||||||
<div className="flex flex-col gap-1">
|
<div className="flex flex-col gap-1">
|
||||||
|
|||||||
@@ -86,12 +86,14 @@ export const ShowVolumes = ({ mysqlId }: Props) => {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{mount.type === "file" && (
|
{mount.type === "file" && (
|
||||||
<div className="flex flex-col gap-1">
|
<>
|
||||||
<span className="font-medium">Content</span>
|
<div className="flex flex-col gap-1">
|
||||||
<span className="text-sm text-muted-foreground">
|
<span className="font-medium">Content</span>
|
||||||
{mount.content}
|
<span className="text-sm text-muted-foreground">
|
||||||
</span>
|
{mount.content}
|
||||||
</div>
|
</span>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
{mount.type === "bind" && (
|
{mount.type === "bind" && (
|
||||||
<div className="flex flex-col gap-1">
|
<div className="flex flex-col gap-1">
|
||||||
|
|||||||
1
drizzle/0024_dapper_supernaut.sql
Normal file
1
drizzle/0024_dapper_supernaut.sql
Normal file
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE "mount" ADD COLUMN "filePath" text;
|
||||||
2932
drizzle/meta/0024_snapshot.json
Normal file
2932
drizzle/meta/0024_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -169,6 +169,13 @@
|
|||||||
"when": 1721542782659,
|
"when": 1721542782659,
|
||||||
"tag": "0023_icy_maverick",
|
"tag": "0023_icy_maverick",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 24,
|
||||||
|
"version": "6",
|
||||||
|
"when": 1721603595092,
|
||||||
|
"tag": "0024_dapper_supernaut",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -156,7 +156,7 @@ export const deployCompose = async ({
|
|||||||
await cloneGithubRepository(admin, compose, deployment.logPath, true);
|
await cloneGithubRepository(admin, compose, deployment.logPath, true);
|
||||||
} else if (compose.sourceType === "git") {
|
} else if (compose.sourceType === "git") {
|
||||||
await cloneGitRepository(compose, deployment.logPath, true);
|
await cloneGitRepository(compose, deployment.logPath, true);
|
||||||
} else {
|
} else if (compose.sourceType === "raw") {
|
||||||
await createComposeFile(compose, deployment.logPath);
|
await createComposeFile(compose, deployment.logPath);
|
||||||
}
|
}
|
||||||
await buildCompose(compose, deployment.logPath);
|
await buildCompose(compose, deployment.logPath);
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
import { unlink } from "node:fs/promises";
|
import { rmdir, stat, unlink } from "node:fs/promises";
|
||||||
import path from "node:path";
|
import path, { join } from "node:path";
|
||||||
import { APPLICATIONS_PATH } from "@/server/constants";
|
import { APPLICATIONS_PATH, COMPOSE_PATH } from "@/server/constants";
|
||||||
import { db } from "@/server/db";
|
import { db } from "@/server/db";
|
||||||
import {
|
import {
|
||||||
type ServiceType,
|
type ServiceType,
|
||||||
type apiCreateMount,
|
type apiCreateMount,
|
||||||
mounts,
|
mounts,
|
||||||
} from "@/server/db/schema";
|
} from "@/server/db/schema";
|
||||||
|
import { createFile } from "@/server/utils/docker/utils";
|
||||||
|
import { removeFileOrDirectory } from "@/server/utils/filesystem/directory";
|
||||||
|
import { execAsync } from "@/server/utils/process/execAsync";
|
||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
import { type SQL, eq, sql } from "drizzle-orm";
|
import { type SQL, eq, sql } from "drizzle-orm";
|
||||||
|
|
||||||
@@ -50,6 +53,10 @@ export const createMount = async (input: typeof apiCreateMount._type) => {
|
|||||||
message: "Error input: Inserting mount",
|
message: "Error input: Inserting mount",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (value.type === "file") {
|
||||||
|
await createFileMount(value.mountId);
|
||||||
|
}
|
||||||
return value;
|
return value;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
@@ -61,6 +68,21 @@ export const createMount = async (input: typeof apiCreateMount._type) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const createFileMount = async (mountId: string) => {
|
||||||
|
try {
|
||||||
|
const mount = await findMountById(mountId);
|
||||||
|
const baseFilePath = await getBaseFilesMountPath(mountId);
|
||||||
|
await createFile(baseFilePath, mount.filePath || "", mount.content || "");
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "BAD_REQUEST",
|
||||||
|
message: "Error to create the mount",
|
||||||
|
cause: error,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const findMountById = async (mountId: string) => {
|
export const findMountById = async (mountId: string) => {
|
||||||
const mount = await db.query.mounts.findFirst({
|
const mount = await db.query.mounts.findFirst({
|
||||||
where: eq(mounts.mountId, mountId),
|
where: eq(mounts.mountId, mountId),
|
||||||
@@ -71,6 +93,7 @@ export const findMountById = async (mountId: string) => {
|
|||||||
mongo: true,
|
mongo: true,
|
||||||
mysql: true,
|
mysql: true,
|
||||||
redis: true,
|
redis: true,
|
||||||
|
compose: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (!mount) {
|
if (!mount) {
|
||||||
@@ -92,7 +115,20 @@ export const updateMount = async (
|
|||||||
...mountData,
|
...mountData,
|
||||||
})
|
})
|
||||||
.where(eq(mounts.mountId, mountId))
|
.where(eq(mounts.mountId, mountId))
|
||||||
.returning();
|
.returning()
|
||||||
|
.then((value) => value[0]);
|
||||||
|
|
||||||
|
if (!mount) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "NOT_FOUND",
|
||||||
|
message: "Mount not found",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mount.type === "file") {
|
||||||
|
await deleteFileMount(mountId);
|
||||||
|
await createFileMount(mountId);
|
||||||
|
}
|
||||||
|
|
||||||
return mount;
|
return mount;
|
||||||
};
|
};
|
||||||
@@ -133,41 +169,10 @@ export const findMountsByApplicationId = async (
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const deleteMount = async (mountId: string) => {
|
export const deleteMount = async (mountId: string) => {
|
||||||
const {
|
const { type } = await findMountById(mountId);
|
||||||
type,
|
|
||||||
mountPath,
|
|
||||||
serviceType,
|
|
||||||
application,
|
|
||||||
mariadb,
|
|
||||||
mongo,
|
|
||||||
mysql,
|
|
||||||
postgres,
|
|
||||||
redis,
|
|
||||||
} = await findMountById(mountId);
|
|
||||||
|
|
||||||
let appName = null;
|
if (type === "file") {
|
||||||
|
await deleteFileMount(mountId);
|
||||||
if (serviceType === "application") {
|
|
||||||
appName = application?.appName;
|
|
||||||
} else if (serviceType === "postgres") {
|
|
||||||
appName = postgres?.appName;
|
|
||||||
} else if (serviceType === "mariadb") {
|
|
||||||
appName = mariadb?.appName;
|
|
||||||
} else if (serviceType === "mongo") {
|
|
||||||
appName = mongo?.appName;
|
|
||||||
} else if (serviceType === "mysql") {
|
|
||||||
appName = mysql?.appName;
|
|
||||||
} else if (serviceType === "redis") {
|
|
||||||
appName = redis?.appName;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type === "file" && appName) {
|
|
||||||
const fileName = mountPath.split("/").pop() || "";
|
|
||||||
const absoluteBasePath = path.resolve(APPLICATIONS_PATH);
|
|
||||||
const filePath = path.join(absoluteBasePath, appName, "files", fileName);
|
|
||||||
try {
|
|
||||||
await unlink(filePath);
|
|
||||||
} catch (error) {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const deletedMount = await db
|
const deletedMount = await db
|
||||||
@@ -176,3 +181,40 @@ export const deleteMount = async (mountId: string) => {
|
|||||||
.returning();
|
.returning();
|
||||||
return deletedMount[0];
|
return deletedMount[0];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const deleteFileMount = async (mountId: string) => {
|
||||||
|
const mount = await findMountById(mountId);
|
||||||
|
const basePath = await getBaseFilesMountPath(mountId);
|
||||||
|
const fullPath = path.join(basePath, mount.filePath || "");
|
||||||
|
try {
|
||||||
|
await removeFileOrDirectory(fullPath);
|
||||||
|
} catch (error) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getBaseFilesMountPath = async (mountId: string) => {
|
||||||
|
const mount = await findMountById(mountId);
|
||||||
|
|
||||||
|
let absoluteBasePath = path.resolve(APPLICATIONS_PATH);
|
||||||
|
let appName = "";
|
||||||
|
let directoryPath = "";
|
||||||
|
|
||||||
|
if (mount.serviceType === "application" && mount.application) {
|
||||||
|
appName = mount.application.appName;
|
||||||
|
} else if (mount.serviceType === "postgres" && mount.postgres) {
|
||||||
|
appName = mount.postgres.appName;
|
||||||
|
} else if (mount.serviceType === "mariadb" && mount.mariadb) {
|
||||||
|
appName = mount.mariadb.appName;
|
||||||
|
} else if (mount.serviceType === "mongo" && mount.mongo) {
|
||||||
|
appName = mount.mongo.appName;
|
||||||
|
} else if (mount.serviceType === "mysql" && mount.mysql) {
|
||||||
|
appName = mount.mysql.appName;
|
||||||
|
} else if (mount.serviceType === "redis" && mount.redis) {
|
||||||
|
appName = mount.redis.appName;
|
||||||
|
} else if (mount.serviceType === "compose" && mount.compose) {
|
||||||
|
appName = mount.compose.appName;
|
||||||
|
absoluteBasePath = path.resolve(COMPOSE_PATH);
|
||||||
|
}
|
||||||
|
directoryPath = path.join(absoluteBasePath, appName, "files");
|
||||||
|
|
||||||
|
return directoryPath;
|
||||||
|
};
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ export const mounts = pgTable("mount", {
|
|||||||
type: mountType("type").notNull(),
|
type: mountType("type").notNull(),
|
||||||
hostPath: text("hostPath"),
|
hostPath: text("hostPath"),
|
||||||
volumeName: text("volumeName"),
|
volumeName: text("volumeName"),
|
||||||
|
filePath: text("filePath"),
|
||||||
content: text("content"),
|
content: text("content"),
|
||||||
serviceType: serviceType("serviceType").notNull().default("application"),
|
serviceType: serviceType("serviceType").notNull().default("application"),
|
||||||
mountPath: text("mountPath").notNull(),
|
mountPath: text("mountPath").notNull(),
|
||||||
@@ -97,6 +98,7 @@ const createSchema = createInsertSchema(mounts, {
|
|||||||
content: z.string().optional(),
|
content: z.string().optional(),
|
||||||
mountPath: z.string().min(1),
|
mountPath: z.string().min(1),
|
||||||
mountId: z.string().optional(),
|
mountId: z.string().optional(),
|
||||||
|
filePath: z.string().optional(),
|
||||||
serviceType: z
|
serviceType: z
|
||||||
.enum([
|
.enum([
|
||||||
"application",
|
"application",
|
||||||
@@ -122,6 +124,7 @@ export const apiCreateMount = createSchema
|
|||||||
content: true,
|
content: true,
|
||||||
mountPath: true,
|
mountPath: true,
|
||||||
serviceType: true,
|
serviceType: true,
|
||||||
|
filePath: true,
|
||||||
})
|
})
|
||||||
.extend({
|
.extend({
|
||||||
serviceId: z.string().min(1),
|
serviceId: z.string().min(1),
|
||||||
@@ -155,3 +158,10 @@ export const apiFindMountByApplicationId = createSchema
|
|||||||
export const apiUpdateMount = createSchema.partial().extend({
|
export const apiUpdateMount = createSchema.partial().extend({
|
||||||
mountId: z.string().min(1),
|
mountId: z.string().min(1),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
Primer Paso:
|
||||||
|
Cuando utilizamos aplicaciones en el volume mount, seria buena idea agregar un FilePath?
|
||||||
|
|
||||||
|
*/
|
||||||
|
|||||||
@@ -8,10 +8,7 @@ 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 {
|
import { prepareEnvironmentVariables } from "../docker/utils";
|
||||||
generateFileMountsCompose,
|
|
||||||
prepareEnvironmentVariables,
|
|
||||||
} from "../docker/utils";
|
|
||||||
import { spawnAsync } from "../process/spawnAsync";
|
import { spawnAsync } from "../process/spawnAsync";
|
||||||
|
|
||||||
export type ComposeNested = InferResultType<
|
export type ComposeNested = InferResultType<
|
||||||
@@ -24,7 +21,6 @@ export const buildCompose = async (compose: ComposeNested, logPath: string) => {
|
|||||||
compose;
|
compose;
|
||||||
try {
|
try {
|
||||||
const command = createCommand(compose);
|
const command = createCommand(compose);
|
||||||
generateFileMountsCompose(appName, mounts);
|
|
||||||
|
|
||||||
createEnvFile(compose);
|
createEnvFile(compose);
|
||||||
|
|
||||||
@@ -46,7 +42,7 @@ Compose Type: ${composeType} ✅`;
|
|||||||
});
|
});
|
||||||
writeStream.write(`\n${logBox}\n`);
|
writeStream.write(`\n${logBox}\n`);
|
||||||
|
|
||||||
const projectPath = join(COMPOSE_PATH, compose.appName);
|
const projectPath = join(COMPOSE_PATH, compose.appName, "code");
|
||||||
await spawnAsync(
|
await spawnAsync(
|
||||||
"docker",
|
"docker",
|
||||||
[...command.split(" ")],
|
[...command.split(" ")],
|
||||||
@@ -104,8 +100,8 @@ export const createCommand = (compose: ComposeNested) => {
|
|||||||
const createEnvFile = (compose: ComposeNested) => {
|
const createEnvFile = (compose: ComposeNested) => {
|
||||||
const { env, composePath, appName } = compose;
|
const { env, composePath, appName } = compose;
|
||||||
const composeFilePath =
|
const composeFilePath =
|
||||||
join(COMPOSE_PATH, appName, composePath) ||
|
join(COMPOSE_PATH, appName, "code", composePath) ||
|
||||||
join(COMPOSE_PATH, appName, "docker-compose.yml");
|
join(COMPOSE_PATH, appName, "code", "docker-compose.yml");
|
||||||
|
|
||||||
const envFilePath = join(dirname(composeFilePath), ".env");
|
const envFilePath = join(dirname(composeFilePath), ".env");
|
||||||
let envContent = env || "";
|
let envContent = env || "";
|
||||||
|
|||||||
@@ -6,8 +6,7 @@ import { recreateDirectory } from "../filesystem/directory";
|
|||||||
|
|
||||||
export const unzipDrop = async (zipFile: File, appName: string) => {
|
export const unzipDrop = async (zipFile: File, appName: string) => {
|
||||||
try {
|
try {
|
||||||
const basePath = APPLICATIONS_PATH;
|
const outputPath = join(APPLICATIONS_PATH, appName, "code");
|
||||||
const outputPath = join(basePath, appName);
|
|
||||||
await recreateDirectory(outputPath);
|
await recreateDirectory(outputPath);
|
||||||
const arrayBuffer = await zipFile.arrayBuffer();
|
const arrayBuffer = await zipFile.arrayBuffer();
|
||||||
const buffer = Buffer.from(arrayBuffer);
|
const buffer = Buffer.from(arrayBuffer);
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ export const buildNixpacks = async (
|
|||||||
) => {
|
) => {
|
||||||
const { env, appName } = application;
|
const { env, appName } = application;
|
||||||
const buildAppDirectory = getBuildAppDirectory(application);
|
const buildAppDirectory = getBuildAppDirectory(application);
|
||||||
|
|
||||||
const envVariables = prepareEnvironmentVariables(env);
|
const envVariables = prepareEnvironmentVariables(env);
|
||||||
try {
|
try {
|
||||||
const args = ["build", buildAppDirectory, "--name", appName];
|
const args = ["build", buildAppDirectory, "--name", appName];
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import fs from "node:fs";
|
import fs from "node:fs";
|
||||||
import path from "node:path";
|
import path, { join } from "node:path";
|
||||||
import type { Readable } from "node:stream";
|
import type { Readable } from "node:stream";
|
||||||
import { APPLICATIONS_PATH, COMPOSE_PATH, docker } from "@/server/constants";
|
import { APPLICATIONS_PATH, COMPOSE_PATH, docker } from "@/server/constants";
|
||||||
import type { ContainerInfo, ResourceRequirements } from "dockerode";
|
import type { ContainerInfo, ResourceRequirements } from "dockerode";
|
||||||
@@ -306,20 +306,9 @@ export const generateFileMounts = (
|
|||||||
.filter((mount) => mount.type === "file")
|
.filter((mount) => mount.type === "file")
|
||||||
.map((mount) => {
|
.map((mount) => {
|
||||||
const fileName = mount.mountPath.split("/").pop();
|
const fileName = mount.mountPath.split("/").pop();
|
||||||
|
|
||||||
if (!fileName) {
|
|
||||||
throw new Error("File name not found");
|
|
||||||
}
|
|
||||||
const absoluteBasePath = path.resolve(APPLICATIONS_PATH);
|
const absoluteBasePath = path.resolve(APPLICATIONS_PATH);
|
||||||
const directory = path.join(absoluteBasePath, appName, "files");
|
const directory = path.join(absoluteBasePath, appName, "files");
|
||||||
const filePath = path.join(directory, fileName);
|
const filePath = path.join(directory, fileName || "");
|
||||||
|
|
||||||
if (!fs.existsSync(directory)) {
|
|
||||||
fs.mkdirSync(directory, { recursive: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
fs.writeFileSync(filePath, mount.content || "");
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
Type: "bind" as const,
|
Type: "bind" as const,
|
||||||
Source: filePath,
|
Source: filePath,
|
||||||
@@ -328,31 +317,34 @@ export const generateFileMounts = (
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const generateFileMountsCompose = (
|
export const createFile = async (
|
||||||
appName: string,
|
outputPath: string,
|
||||||
mounts: ApplicationNested["mounts"],
|
filePath: string,
|
||||||
|
content: string,
|
||||||
) => {
|
) => {
|
||||||
if (!mounts || mounts.length === 0) {
|
try {
|
||||||
return [];
|
// Unir outputPath con filePath
|
||||||
|
const fullPath = path.join(outputPath, filePath);
|
||||||
|
|
||||||
|
// Verificar si la ruta termina en separador (indica que es un directorio)
|
||||||
|
if (fullPath.endsWith(path.sep) || filePath.endsWith("/")) {
|
||||||
|
fs.mkdirSync(fullPath, { recursive: true });
|
||||||
|
console.log(`Directorio creado: ${fullPath}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Para archivos, obtener el directorio del archivo
|
||||||
|
const directory = path.dirname(fullPath);
|
||||||
|
|
||||||
|
// Crear el directorio si no existe
|
||||||
|
fs.mkdirSync(directory, { recursive: true });
|
||||||
|
|
||||||
|
// Escribir el archivo
|
||||||
|
fs.writeFileSync(fullPath, content || "");
|
||||||
|
console.log(`Archivo creado: ${fullPath}`);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
return mounts
|
|
||||||
.filter((mount) => mount.type === "file")
|
|
||||||
.map((mount) => {
|
|
||||||
const fileName = path.basename(mount.mountPath);
|
|
||||||
const directory = path.join(
|
|
||||||
COMPOSE_PATH,
|
|
||||||
appName,
|
|
||||||
path.dirname(mount.mountPath),
|
|
||||||
);
|
|
||||||
fs.mkdirSync(directory, { recursive: true });
|
|
||||||
|
|
||||||
const filePath = path.join(directory, fileName);
|
|
||||||
|
|
||||||
fs.writeFileSync(filePath, mount.content || "");
|
|
||||||
|
|
||||||
return {};
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getServiceContainer = async (appName: string) => {
|
export const getServiceContainer = async (appName: string) => {
|
||||||
|
|||||||
@@ -25,6 +25,15 @@ export const removeDirectoryIfExistsContent = async (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const removeFileOrDirectory = async (path: string) => {
|
||||||
|
try {
|
||||||
|
await execAsync(`rm -rf ${path}`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error to remove ${path}: ${error}`);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const removeDirectoryCode = async (appName: string) => {
|
export const removeDirectoryCode = async (appName: string) => {
|
||||||
const directoryPath = path.join(APPLICATIONS_PATH, appName);
|
const directoryPath = path.join(APPLICATIONS_PATH, appName);
|
||||||
|
|
||||||
@@ -72,9 +81,11 @@ export const getBuildAppDirectory = (application: Application) => {
|
|||||||
return path.join(
|
return path.join(
|
||||||
APPLICATIONS_PATH,
|
APPLICATIONS_PATH,
|
||||||
appName,
|
appName,
|
||||||
|
"code",
|
||||||
buildPath ?? "",
|
buildPath ?? "",
|
||||||
dockerfile || "",
|
dockerfile || "",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return path.join(APPLICATIONS_PATH, appName, buildPath ?? "");
|
|
||||||
|
return path.join(APPLICATIONS_PATH, appName, "code", buildPath ?? "");
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export const cloneGitRepository = async (
|
|||||||
const writeStream = createWriteStream(logPath, { flags: "a" });
|
const writeStream = createWriteStream(logPath, { flags: "a" });
|
||||||
const keyPath = path.join(SSH_PATH, `${appName}_rsa`);
|
const keyPath = path.join(SSH_PATH, `${appName}_rsa`);
|
||||||
const basePath = isCompose ? COMPOSE_PATH : APPLICATIONS_PATH;
|
const basePath = isCompose ? COMPOSE_PATH : APPLICATIONS_PATH;
|
||||||
const outputPath = join(basePath, appName);
|
const outputPath = join(basePath, appName, "code");
|
||||||
const knownHostsPath = path.join(SSH_PATH, "known_hosts");
|
const knownHostsPath = path.join(SSH_PATH, "known_hosts");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ export const cloneGithubRepository = async (
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
const basePath = isCompose ? COMPOSE_PATH : APPLICATIONS_PATH;
|
const basePath = isCompose ? COMPOSE_PATH : APPLICATIONS_PATH;
|
||||||
const outputPath = join(basePath, appName);
|
const outputPath = join(basePath, appName, "code");
|
||||||
const octokit = authGithub(admin);
|
const octokit = authGithub(admin);
|
||||||
const token = await getGithubToken(octokit);
|
const token = await getGithubToken(octokit);
|
||||||
const repoclone = `github.com/${owner}/${repository}.git`;
|
const repoclone = `github.com/${owner}/${repository}.git`;
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { recreateDirectory } from "../filesystem/directory";
|
|||||||
export const createComposeFile = async (compose: Compose, logPath: string) => {
|
export const createComposeFile = async (compose: Compose, logPath: string) => {
|
||||||
const { appName, composeFile } = compose;
|
const { appName, composeFile } = compose;
|
||||||
const writeStream = createWriteStream(logPath, { flags: "a" });
|
const writeStream = createWriteStream(logPath, { flags: "a" });
|
||||||
const outputPath = join(COMPOSE_PATH, appName);
|
const outputPath = join(COMPOSE_PATH, appName, "code");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await recreateDirectory(outputPath);
|
await recreateDirectory(outputPath);
|
||||||
|
|||||||
Reference in New Issue
Block a user