feat: load project templates from custom E2B sandboxes instead of from Cloudflare
This commit is contained in:
parent
5633727bdb
commit
5a63ab7265
@ -140,6 +140,12 @@ export class FileManager {
|
|||||||
})
|
})
|
||||||
await Promise.all(promises)
|
await Promise.all(promises)
|
||||||
|
|
||||||
|
// Reload file list from the container to include template files
|
||||||
|
const result = await this.sandbox.commands.run(`find "${this.dirName}" -type f`); // List all files recursively
|
||||||
|
const localPaths = result.stdout.split('\n').filter(path => path); // Split the output into an array and filter out empty strings
|
||||||
|
const relativePaths = localPaths.map(filePath => path.relative(this.dirName, filePath)); // Convert absolute paths to relative paths
|
||||||
|
this.files = generateFileStructure(relativePaths);
|
||||||
|
|
||||||
// Make the logged in user the owner of all project files
|
// Make the logged in user the owner of all project files
|
||||||
this.fixPermissions()
|
this.fixPermissions()
|
||||||
|
|
||||||
@ -348,8 +354,9 @@ export class FileManager {
|
|||||||
|
|
||||||
// Get file content
|
// Get file content
|
||||||
async getFile(fileId: string): Promise<string | undefined> {
|
async getFile(fileId: string): Promise<string | undefined> {
|
||||||
const file = this.fileData.find((f) => f.id === fileId)
|
const filePath = path.posix.join(this.dirName, fileId)
|
||||||
return file?.data
|
const fileContent = await this.sandbox.files.read(filePath)
|
||||||
|
return fileContent
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get folder content
|
// Get folder content
|
||||||
|
@ -13,9 +13,8 @@ import {
|
|||||||
} from "./ratelimit"
|
} from "./ratelimit"
|
||||||
import { SecureGitClient } from "./SecureGitClient"
|
import { SecureGitClient } from "./SecureGitClient"
|
||||||
import { TerminalManager } from "./TerminalManager"
|
import { TerminalManager } from "./TerminalManager"
|
||||||
import { TFile, TFolder } from "./types"
|
import { TFile, TFileData, TFolder } from "./types"
|
||||||
import { LockManager } from "./utils"
|
import { LockManager } from "./utils"
|
||||||
import { TFileData } from "./types"
|
|
||||||
const lockManager = new LockManager()
|
const lockManager = new LockManager()
|
||||||
|
|
||||||
// Define a type for SocketHandler functions
|
// Define a type for SocketHandler functions
|
||||||
@ -38,6 +37,7 @@ type ServerContext = {
|
|||||||
export class Sandbox {
|
export class Sandbox {
|
||||||
// Sandbox properties:
|
// Sandbox properties:
|
||||||
sandboxId: string;
|
sandboxId: string;
|
||||||
|
type: string;
|
||||||
fileManager: FileManager | null;
|
fileManager: FileManager | null;
|
||||||
terminalManager: TerminalManager | null;
|
terminalManager: TerminalManager | null;
|
||||||
container: E2BSandbox | null;
|
container: E2BSandbox | null;
|
||||||
@ -46,9 +46,10 @@ export class Sandbox {
|
|||||||
gitClient: SecureGitClient | null;
|
gitClient: SecureGitClient | null;
|
||||||
aiWorker: AIWorker;
|
aiWorker: AIWorker;
|
||||||
|
|
||||||
constructor(sandboxId: string, { aiWorker, dokkuClient, gitClient }: ServerContext) {
|
constructor(sandboxId: string, type: string, { aiWorker, dokkuClient, gitClient }: ServerContext) {
|
||||||
// Sandbox properties:
|
// Sandbox properties:
|
||||||
this.sandboxId = sandboxId;
|
this.sandboxId = sandboxId;
|
||||||
|
this.type = type;
|
||||||
this.fileManager = null;
|
this.fileManager = null;
|
||||||
this.terminalManager = null;
|
this.terminalManager = null;
|
||||||
this.container = null;
|
this.container = null;
|
||||||
@ -69,8 +70,12 @@ export class Sandbox {
|
|||||||
console.log(`Found existing container ${this.sandboxId}`)
|
console.log(`Found existing container ${this.sandboxId}`)
|
||||||
} else {
|
} else {
|
||||||
console.log("Creating container", this.sandboxId)
|
console.log("Creating container", this.sandboxId)
|
||||||
// Create a new container with a specified timeout
|
// Create a new container with a specified template and timeout
|
||||||
this.container = await E2BSandbox.create({
|
const templateTypes = ["vanillajs", "reactjs", "nextjs", "streamlit"];
|
||||||
|
const template = templateTypes.includes(this.type)
|
||||||
|
? `gitwit-${this.type}`
|
||||||
|
: `base`;
|
||||||
|
this.container = await E2BSandbox.create(template, {
|
||||||
timeoutMs: CONTAINER_TIMEOUT,
|
timeoutMs: CONTAINER_TIMEOUT,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -96,6 +96,7 @@ io.on("connection", async (socket) => {
|
|||||||
userId: string
|
userId: string
|
||||||
sandboxId: string
|
sandboxId: string
|
||||||
isOwner: boolean
|
isOwner: boolean
|
||||||
|
type: string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register the connection
|
// Register the connection
|
||||||
@ -111,6 +112,7 @@ io.on("connection", async (socket) => {
|
|||||||
// Create or retrieve the sandbox manager for the given sandbox ID
|
// Create or retrieve the sandbox manager for the given sandbox ID
|
||||||
const sandbox = sandboxes[data.sandboxId] ?? new Sandbox(
|
const sandbox = sandboxes[data.sandboxId] ?? new Sandbox(
|
||||||
data.sandboxId,
|
data.sandboxId,
|
||||||
|
data.type,
|
||||||
{
|
{
|
||||||
aiWorker, dokkuClient, gitClient,
|
aiWorker, dokkuClient, gitClient,
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Socket } from "socket.io"
|
import { Socket } from "socket.io"
|
||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
import { User } from "./types"
|
import { Sandbox, User } from "./types"
|
||||||
|
|
||||||
// Middleware for socket authentication
|
// Middleware for socket authentication
|
||||||
export const socketAuth = async (socket: Socket, next: Function) => {
|
export const socketAuth = async (socket: Socket, next: Function) => {
|
||||||
@ -33,6 +33,17 @@ export const socketAuth = async (socket: Socket, next: Function) => {
|
|||||||
)
|
)
|
||||||
const dbUserJSON = (await dbUser.json()) as User
|
const dbUserJSON = (await dbUser.json()) as User
|
||||||
|
|
||||||
|
// Fetch sandbox data from the database
|
||||||
|
const dbSandbox = await fetch(
|
||||||
|
`${process.env.DATABASE_WORKER_URL}/api/sandbox?id=${sandboxId}`,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `${process.env.WORKERS_KEY}`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
const dbSandboxJSON = (await dbSandbox.json()) as Sandbox
|
||||||
|
|
||||||
// Check if user data was retrieved successfully
|
// Check if user data was retrieved successfully
|
||||||
if (!dbUserJSON) {
|
if (!dbUserJSON) {
|
||||||
next(new Error("DB error."))
|
next(new Error("DB error."))
|
||||||
@ -56,6 +67,7 @@ export const socketAuth = async (socket: Socket, next: Function) => {
|
|||||||
userId,
|
userId,
|
||||||
sandboxId: sandboxId,
|
sandboxId: sandboxId,
|
||||||
isOwner: sandbox !== undefined,
|
isOwner: sandbox !== undefined,
|
||||||
|
type: dbSandboxJSON.type
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allow the connection
|
// Allow the connection
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import pLimit from "p-limit"
|
|
||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
|
|
||||||
export interface Env {
|
export interface Env {
|
||||||
@ -136,33 +135,7 @@ export default {
|
|||||||
|
|
||||||
return success
|
return success
|
||||||
} else if (path === "/api/init" && method === "POST") {
|
} else if (path === "/api/init" && method === "POST") {
|
||||||
const initSchema = z.object({
|
// This API path no longer does anything, because template files are stored in E2B sandbox templates.
|
||||||
sandboxId: z.string(),
|
|
||||||
type: z.string(),
|
|
||||||
})
|
|
||||||
|
|
||||||
const body = await request.json()
|
|
||||||
const { sandboxId, type } = initSchema.parse(body)
|
|
||||||
|
|
||||||
console.log(`Copying template: ${type}`)
|
|
||||||
|
|
||||||
// List all objects under the directory
|
|
||||||
const { objects } = await env.Templates.list({ prefix: type })
|
|
||||||
|
|
||||||
// Copy each object to the new directory with a 5 concurrency limit
|
|
||||||
const limit = pLimit(5)
|
|
||||||
await Promise.all(
|
|
||||||
objects.map(({ key }) =>
|
|
||||||
limit(async () => {
|
|
||||||
const destinationKey = key.replace(type, `projects/${sandboxId}`)
|
|
||||||
const fileBody = await env.Templates.get(key).then(
|
|
||||||
(res) => res?.body ?? ""
|
|
||||||
)
|
|
||||||
await env.R2.put(destinationKey, fileBody)
|
|
||||||
})
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
return success
|
return success
|
||||||
} else {
|
} else {
|
||||||
return notFound
|
return notFound
|
||||||
|
@ -44,8 +44,8 @@ export default function RunButtonModal({
|
|||||||
} else if (!isRunning && terminals.length < 4) {
|
} else if (!isRunning && terminals.length < 4) {
|
||||||
const command =
|
const command =
|
||||||
sandboxData.type === "streamlit"
|
sandboxData.type === "streamlit"
|
||||||
? "pip install -r requirements.txt && streamlit run main.py --server.runOnSave true"
|
? "./venv/bin/streamlit run main.py --server.runOnSave true"
|
||||||
: "yarn install && yarn dev"
|
: "npm run dev"
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Create a new terminal with the appropriate command
|
// Create a new terminal with the appropriate command
|
||||||
|
Loading…
x
Reference in New Issue
Block a user