@lakitu/sdk 0.1.23 ā 0.1.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1,13 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* lakitu build
|
|
3
3
|
*
|
|
4
|
-
* Build E2B sandbox template
|
|
5
|
-
*
|
|
6
|
-
* Strategy:
|
|
7
|
-
* 1. Start local convex-backend
|
|
8
|
-
* 2. Deploy sandbox functions with `convex dev --once`
|
|
9
|
-
* 3. Capture the state directory
|
|
10
|
-
* 4. Build E2B template with pre-built state baked in
|
|
4
|
+
* Build E2B sandbox template using Dockerfile approach.
|
|
5
|
+
* Uses fromDockerfile() to leverage Docker's COPY instead of SDK's problematic tar streaming.
|
|
11
6
|
*/
|
|
12
7
|
interface BuildOptions {
|
|
13
8
|
base?: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../../cli/commands/build.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../../cli/commands/build.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAaH,UAAU,YAAY;IACpB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAiMD,wBAAsB,KAAK,CAAC,OAAO,EAAE,YAAY,iBAgBhD"}
|
|
@@ -1,30 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* lakitu build
|
|
3
3
|
*
|
|
4
|
-
* Build E2B sandbox template
|
|
5
|
-
*
|
|
6
|
-
* Strategy:
|
|
7
|
-
* 1. Start local convex-backend
|
|
8
|
-
* 2. Deploy sandbox functions with `convex dev --once`
|
|
9
|
-
* 3. Capture the state directory
|
|
10
|
-
* 4. Build E2B template with pre-built state baked in
|
|
4
|
+
* Build E2B sandbox template using Dockerfile approach.
|
|
5
|
+
* Uses fromDockerfile() to leverage Docker's COPY instead of SDK's problematic tar streaming.
|
|
11
6
|
*/
|
|
12
7
|
import { Template, defaultBuildLogger, waitForPort } from "e2b";
|
|
13
8
|
import { existsSync, mkdirSync, rmSync, cpSync, writeFileSync, readFileSync, readdirSync } from "fs";
|
|
14
9
|
import { join, dirname } from "path";
|
|
15
|
-
import { execSync
|
|
10
|
+
import { execSync } from "child_process";
|
|
16
11
|
import { fileURLToPath } from "url";
|
|
17
12
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
18
|
-
// Handle both TS source (cli/commands) and compiled JS (dist/cli/commands)
|
|
19
13
|
const PACKAGE_ROOT = __dirname.includes("/dist/")
|
|
20
14
|
? join(__dirname, "../../..")
|
|
21
15
|
: join(__dirname, "../..");
|
|
22
16
|
function findApiKey() {
|
|
23
|
-
// 1. Environment variable
|
|
24
17
|
if (process.env.E2B_API_KEY) {
|
|
25
18
|
return { key: process.env.E2B_API_KEY, source: "E2B_API_KEY env var" };
|
|
26
19
|
}
|
|
27
|
-
// 2. .env files
|
|
28
20
|
const envPaths = [
|
|
29
21
|
join(process.cwd(), ".env.local"),
|
|
30
22
|
join(process.cwd(), ".env"),
|
|
@@ -38,15 +30,14 @@ function findApiKey() {
|
|
|
38
30
|
}
|
|
39
31
|
catch { /* not found */ }
|
|
40
32
|
}
|
|
41
|
-
// 3. E2B CLI config
|
|
42
33
|
const homeDir = process.env.HOME || process.env.USERPROFILE || "";
|
|
43
34
|
try {
|
|
44
35
|
const configPath = join(homeDir, ".e2b/config.json");
|
|
45
36
|
const config = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
46
37
|
if (config.teamApiKey)
|
|
47
|
-
return { key: config.teamApiKey, source: "~/.e2b/config.json
|
|
38
|
+
return { key: config.teamApiKey, source: "~/.e2b/config.json" };
|
|
48
39
|
if (config.accessToken)
|
|
49
|
-
return { key: config.accessToken, source: "~/.e2b/config.json
|
|
40
|
+
return { key: config.accessToken, source: "~/.e2b/config.json" };
|
|
50
41
|
}
|
|
51
42
|
catch { /* not found */ }
|
|
52
43
|
return null;
|
|
@@ -55,15 +46,7 @@ function preflightCheck() {
|
|
|
55
46
|
const result = findApiKey();
|
|
56
47
|
if (!result) {
|
|
57
48
|
console.error("ā E2B API key not found\n");
|
|
58
|
-
console.error("
|
|
59
|
-
console.error(" 1. Set environment variable:");
|
|
60
|
-
console.error(" export E2B_API_KEY=your_key\n");
|
|
61
|
-
console.error(" 2. Add to .env.local:");
|
|
62
|
-
console.error(" echo 'E2B_API_KEY=your_key' >> .env.local\n");
|
|
63
|
-
console.error(" 3. Login via E2B CLI:");
|
|
64
|
-
console.error(" npm install -g @e2b/cli");
|
|
65
|
-
console.error(" e2b auth login\n");
|
|
66
|
-
console.error("Get your API key at: https://e2b.dev/dashboard\n");
|
|
49
|
+
console.error("Set E2B_API_KEY in .env.local or run 'e2b auth login'\n");
|
|
67
50
|
process.exit(1);
|
|
68
51
|
}
|
|
69
52
|
console.log(`š Using API key from ${result.source}\n`);
|
|
@@ -72,85 +55,7 @@ function preflightCheck() {
|
|
|
72
55
|
async function sleep(ms) {
|
|
73
56
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
74
57
|
}
|
|
75
|
-
|
|
76
|
-
* Pre-build Convex locally: start backend, deploy functions, capture state
|
|
77
|
-
*/
|
|
78
|
-
async function prebuildConvex() {
|
|
79
|
-
const stateDir = "/tmp/lakitu-convex-state";
|
|
80
|
-
const sandboxConvexDir = join(PACKAGE_ROOT, "convex/sandbox");
|
|
81
|
-
console.log("=== Pre-building Convex locally ===");
|
|
82
|
-
// Clean up any existing state
|
|
83
|
-
rmSync(stateDir, { recursive: true, force: true });
|
|
84
|
-
mkdirSync(stateDir, { recursive: true });
|
|
85
|
-
// Kill any existing convex-backend
|
|
86
|
-
try {
|
|
87
|
-
execSync("pkill -f convex-backend", { stdio: "ignore" });
|
|
88
|
-
await sleep(1000);
|
|
89
|
-
}
|
|
90
|
-
catch { /* not running */ }
|
|
91
|
-
console.log("Starting local convex-backend...");
|
|
92
|
-
// Filter out Bun's node shim from PATH so convex-backend finds real Node.js
|
|
93
|
-
const cleanPath = (process.env.PATH || "")
|
|
94
|
-
.split(":")
|
|
95
|
-
.filter(p => !p.includes("bun-node-"))
|
|
96
|
-
.join(":");
|
|
97
|
-
// Start convex-backend in background
|
|
98
|
-
const backend = spawn("convex-backend", [
|
|
99
|
-
join(stateDir, "convex_local_backend.sqlite3"),
|
|
100
|
-
"--port", "3210",
|
|
101
|
-
"--site-proxy-port", "3211",
|
|
102
|
-
"--local-storage", stateDir,
|
|
103
|
-
], {
|
|
104
|
-
cwd: stateDir,
|
|
105
|
-
stdio: "pipe",
|
|
106
|
-
env: { ...process.env, PATH: cleanPath },
|
|
107
|
-
});
|
|
108
|
-
// Wait for backend to be ready
|
|
109
|
-
console.log("Waiting for backend to be ready...");
|
|
110
|
-
for (let i = 0; i < 30; i++) {
|
|
111
|
-
try {
|
|
112
|
-
const res = await fetch("http://127.0.0.1:3210/version");
|
|
113
|
-
if (res.ok) {
|
|
114
|
-
console.log(`Backend ready after ${i + 1} seconds`);
|
|
115
|
-
break;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
catch { /* not ready yet */ }
|
|
119
|
-
if (i === 29) {
|
|
120
|
-
backend.kill();
|
|
121
|
-
throw new Error("Backend failed to start after 30 seconds");
|
|
122
|
-
}
|
|
123
|
-
await sleep(1000);
|
|
124
|
-
}
|
|
125
|
-
// Deploy functions using convex dev --once
|
|
126
|
-
console.log("Deploying functions with convex dev --once...");
|
|
127
|
-
const tempEnvFile = "/tmp/lakitu-convex-env";
|
|
128
|
-
writeFileSync(tempEnvFile, `CONVEX_SELF_HOSTED_URL=http://127.0.0.1:3210
|
|
129
|
-
CONVEX_SELF_HOSTED_ADMIN_KEY=0135d8598650f8f5cb0f30c34ec2e2bb62793bc28717c8eb6fb577996d50be5f4281b59181095065c5d0f86a2c31ddbe9b597ec62b47ded69782cd
|
|
130
|
-
`);
|
|
131
|
-
try {
|
|
132
|
-
// Run from package root where convex.json is (specifies functions: "convex/sandbox")
|
|
133
|
-
execSync(`npx convex dev --once --typecheck disable --env-file ${tempEnvFile}`, {
|
|
134
|
-
cwd: PACKAGE_ROOT,
|
|
135
|
-
stdio: "inherit",
|
|
136
|
-
env: { ...process.env, CONVEX_DEPLOYMENT: undefined, PATH: cleanPath },
|
|
137
|
-
});
|
|
138
|
-
console.log("Functions deployed successfully!");
|
|
139
|
-
}
|
|
140
|
-
catch (error) {
|
|
141
|
-
backend.kill();
|
|
142
|
-
throw new Error("Convex deploy failed");
|
|
143
|
-
}
|
|
144
|
-
// Give backend a moment to flush state
|
|
145
|
-
await sleep(2000);
|
|
146
|
-
// Stop backend gracefully
|
|
147
|
-
console.log("Stopping backend...");
|
|
148
|
-
backend.kill("SIGTERM");
|
|
149
|
-
await sleep(1000);
|
|
150
|
-
console.log("=== Pre-build complete ===\n");
|
|
151
|
-
return stateDir;
|
|
152
|
-
}
|
|
153
|
-
// Base template: Ubuntu + Bun + Convex Backend + Node.js
|
|
58
|
+
// Base template using standard Template builder
|
|
154
59
|
const baseTemplate = Template()
|
|
155
60
|
.fromImage("e2bdev/code-interpreter:latest")
|
|
156
61
|
.runCmd("sudo apt-get update && sudo apt-get install -y git curl sqlite3 libsqlite3-dev build-essential unzip")
|
|
@@ -170,47 +75,6 @@ const baseTemplate = Template()
|
|
|
170
75
|
CONVEX_URL: "http://localhost:3210",
|
|
171
76
|
LOCAL_CONVEX_URL: "http://localhost:3210",
|
|
172
77
|
});
|
|
173
|
-
// Custom template: Add Lakitu code + PRE-BUILT Convex state + AUTO-START backend
|
|
174
|
-
// Uses tar files to bypass E2B's extraction issues with directory COPY
|
|
175
|
-
function customTemplate(baseId, buildDir, hasProjectKsa) {
|
|
176
|
-
const copyKsaCmd = hasProjectKsa
|
|
177
|
-
? 'tar -xzf /tmp/project-ksa.tar.gz -C /home/user && cp -r /home/user/project-ksa/*.ts /home/user/lakitu/ksa/ && '
|
|
178
|
-
: '';
|
|
179
|
-
return Template()
|
|
180
|
-
.fromTemplate(baseId)
|
|
181
|
-
// Copy tar archives instead of directories
|
|
182
|
-
.copy(`${buildDir}/lakitu.tar.gz`, "/tmp/lakitu.tar.gz")
|
|
183
|
-
.copy(`${buildDir}/start.sh`, "/home/user/start.sh")
|
|
184
|
-
.copy(`${buildDir}/convex-state.tar.gz`, "/tmp/convex-state.tar.gz")
|
|
185
|
-
.copy(hasProjectKsa ? `${buildDir}/project-ksa.tar.gz` : `${buildDir}/start.sh`, hasProjectKsa ? "/tmp/project-ksa.tar.gz" : "/tmp/dummy.sh")
|
|
186
|
-
// Extract tars and set up
|
|
187
|
-
.runCmd(`
|
|
188
|
-
cd /home/user && tar -xzf /tmp/lakitu.tar.gz && \
|
|
189
|
-
mkdir -p /home/user/.convex/convex-backend-state && \
|
|
190
|
-
tar -xzf /tmp/convex-state.tar.gz -C /home/user/.convex/convex-backend-state && \
|
|
191
|
-
mv /home/user/.convex/convex-backend-state/convex-state /home/user/.convex/convex-backend-state/lakitu && \
|
|
192
|
-
${copyKsaCmd}chmod +x /home/user/start.sh && \
|
|
193
|
-
export HOME=/home/user && \
|
|
194
|
-
export PATH="/home/user/.bun/bin:/usr/local/bin:/usr/bin:/bin" && \
|
|
195
|
-
cd /home/user/lakitu && bun install && \
|
|
196
|
-
echo '#!/bin/bash\\nbun run /home/user/lakitu/runtime/pdf/pdf-generator.ts "$@"' | sudo tee /usr/local/bin/generate-pdf && \
|
|
197
|
-
sudo chmod +x /usr/local/bin/generate-pdf && \
|
|
198
|
-
echo '#!/bin/bash\\nbun run /home/user/lakitu/runtime/browser/agent-browser-cli.ts "$@"' | sudo tee /usr/local/bin/agent-browser && \
|
|
199
|
-
sudo chmod +x /usr/local/bin/agent-browser && \
|
|
200
|
-
ln -sf /home/user/lakitu/ksa /home/user/ksa && \
|
|
201
|
-
chown -R user:user /home/user/lakitu /home/user/ksa /home/user/.convex && \
|
|
202
|
-
rm /tmp/*.tar.gz /tmp/dummy.sh 2>/dev/null || true && \
|
|
203
|
-
echo "KSA modules:" && ls /home/user/lakitu/ksa/*.ts 2>/dev/null | head -20
|
|
204
|
-
`)
|
|
205
|
-
.setEnvs({
|
|
206
|
-
HOME: "/home/user",
|
|
207
|
-
PATH: "/home/user/.bun/bin:/usr/local/bin:/usr/bin:/bin",
|
|
208
|
-
CONVEX_URL: "http://localhost:3210",
|
|
209
|
-
LOCAL_CONVEX_URL: "http://localhost:3210",
|
|
210
|
-
CONVEX_LOCAL_STORAGE: "/home/user/.convex/convex-backend-state/lakitu",
|
|
211
|
-
})
|
|
212
|
-
.setStartCmd("/home/user/start.sh", waitForPort(3210));
|
|
213
|
-
}
|
|
214
78
|
async function buildBase(apiKey) {
|
|
215
79
|
console.log("š§ Building Lakitu base template...\n");
|
|
216
80
|
const result = await Template.build(baseTemplate, {
|
|
@@ -223,13 +87,10 @@ async function buildBase(apiKey) {
|
|
|
223
87
|
}
|
|
224
88
|
async function buildCustom(apiKey, baseId) {
|
|
225
89
|
const buildDir = "/tmp/lakitu-build";
|
|
226
|
-
|
|
227
|
-
const stateDir = await prebuildConvex();
|
|
228
|
-
// Step 2: Prepare build context
|
|
229
|
-
console.log("š¦ Preparing build context...");
|
|
90
|
+
console.log("š¦ Preparing Dockerfile build context...");
|
|
230
91
|
rmSync(buildDir, { recursive: true, force: true });
|
|
231
92
|
mkdirSync(buildDir, { recursive: true });
|
|
232
|
-
// Copy lakitu source
|
|
93
|
+
// Copy lakitu source
|
|
233
94
|
execSync(`rsync -av \
|
|
234
95
|
--exclude='node_modules' \
|
|
235
96
|
--exclude='.git' \
|
|
@@ -246,53 +107,81 @@ async function buildCustom(apiKey, baseId) {
|
|
|
246
107
|
--exclude='vitest.config.ts' \
|
|
247
108
|
--exclude='CLAUDE.md' \
|
|
248
109
|
--exclude='scripts' \
|
|
110
|
+
--exclude='convex/cloud' \
|
|
249
111
|
${PACKAGE_ROOT}/ ${join(buildDir, "lakitu")}/`, {
|
|
250
112
|
stdio: "pipe",
|
|
251
113
|
});
|
|
252
|
-
// Copy user's project KSAs
|
|
114
|
+
// Copy user's project KSAs
|
|
253
115
|
const userKsaDir = join(process.cwd(), "lakitu");
|
|
254
|
-
let hasProjectKsa = false;
|
|
255
116
|
if (existsSync(userKsaDir)) {
|
|
256
117
|
console.log(" Copying project KSAs from lakitu/...");
|
|
257
118
|
const ksaFiles = readdirSync(userKsaDir).filter((f) => f.endsWith(".ts"));
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
for (const file of ksaFiles) {
|
|
261
|
-
cpSync(join(userKsaDir, file), join(buildDir, "project-ksa", file));
|
|
262
|
-
}
|
|
263
|
-
hasProjectKsa = true;
|
|
264
|
-
console.log(` ā Copied ${ksaFiles.length} project KSAs`);
|
|
119
|
+
for (const file of ksaFiles) {
|
|
120
|
+
cpSync(join(userKsaDir, file), join(buildDir, "lakitu/ksa", file));
|
|
265
121
|
}
|
|
122
|
+
console.log(` ā Copied ${ksaFiles.length} project KSAs`);
|
|
266
123
|
}
|
|
267
124
|
// Copy start script
|
|
268
125
|
cpSync(join(PACKAGE_ROOT, "template/e2b/start.sh"), join(buildDir, "start.sh"));
|
|
269
|
-
//
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
126
|
+
// Create Dockerfile that builds on base template
|
|
127
|
+
const dockerfile = `# Lakitu Custom Template
|
|
128
|
+
FROM ${baseId}
|
|
129
|
+
|
|
130
|
+
# Copy lakitu code (using Docker's COPY, not SDK's tar streaming)
|
|
131
|
+
COPY --chown=user:user lakitu/ /home/user/lakitu/
|
|
132
|
+
COPY --chown=user:user start.sh /home/user/start.sh
|
|
133
|
+
|
|
134
|
+
# Install dependencies and set up
|
|
135
|
+
RUN chmod +x /home/user/start.sh && \\
|
|
136
|
+
export HOME=/home/user && \\
|
|
137
|
+
export PATH="/home/user/.bun/bin:/usr/local/bin:/usr/bin:/bin" && \\
|
|
138
|
+
cd /home/user/lakitu && /home/user/.bun/bin/bun install && \\
|
|
139
|
+
echo '#!/bin/bash\\nbun run /home/user/lakitu/runtime/pdf/pdf-generator.ts "$@"' | sudo tee /usr/local/bin/generate-pdf && \\
|
|
140
|
+
sudo chmod +x /usr/local/bin/generate-pdf && \\
|
|
141
|
+
echo '#!/bin/bash\\nbun run /home/user/lakitu/runtime/browser/agent-browser-cli.ts "$@"' | sudo tee /usr/local/bin/agent-browser && \\
|
|
142
|
+
sudo chmod +x /usr/local/bin/agent-browser && \\
|
|
143
|
+
ln -sf /home/user/lakitu/ksa /home/user/ksa && \\
|
|
144
|
+
chown -R user:user /home/user/lakitu /home/user/ksa
|
|
145
|
+
|
|
146
|
+
# Pre-deploy Convex functions
|
|
147
|
+
RUN export HOME=/home/user && \\
|
|
148
|
+
export PATH="/home/user/.bun/bin:/usr/local/bin:/usr/bin:/bin" && \\
|
|
149
|
+
export CONVEX_LOCAL_STORAGE=/home/user/.convex/convex-backend-state/lakitu && \\
|
|
150
|
+
mkdir -p $CONVEX_LOCAL_STORAGE && \\
|
|
151
|
+
cd /home/user/lakitu && \\
|
|
152
|
+
convex-backend --port 3210 --site-proxy-port 3211 --local-storage $CONVEX_LOCAL_STORAGE & \\
|
|
153
|
+
sleep 5 && \\
|
|
154
|
+
./node_modules/.bin/convex dev --once --typecheck disable && \\
|
|
155
|
+
sleep 2 && \\
|
|
156
|
+
pkill convex-backend || true && \\
|
|
157
|
+
chown -R user:user /home/user/.convex
|
|
158
|
+
|
|
159
|
+
ENV HOME=/home/user
|
|
160
|
+
ENV PATH="/home/user/.bun/bin:/usr/local/bin:/usr/bin:/bin"
|
|
161
|
+
ENV CONVEX_URL="http://localhost:3210"
|
|
162
|
+
ENV LOCAL_CONVEX_URL="http://localhost:3210"
|
|
163
|
+
ENV CONVEX_LOCAL_STORAGE="/home/user/.convex/convex-backend-state/lakitu"
|
|
164
|
+
|
|
165
|
+
USER user
|
|
166
|
+
WORKDIR /home/user/workspace
|
|
167
|
+
`;
|
|
168
|
+
writeFileSync(join(buildDir, "Dockerfile"), dockerfile);
|
|
281
169
|
console.log(" ā Build context ready\n");
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
const
|
|
170
|
+
console.log(`š§ Building Lakitu custom template (Dockerfile method)...\n`);
|
|
171
|
+
// Use fromDockerfile which leverages Docker's COPY instead of SDK tar streaming
|
|
172
|
+
const template = Template()
|
|
173
|
+
.fromDockerfile(join(buildDir, "Dockerfile"))
|
|
174
|
+
.setStartCmd("/home/user/start.sh", waitForPort(3210));
|
|
175
|
+
const result = await Template.build(template, {
|
|
285
176
|
alias: "lakitu",
|
|
286
177
|
apiKey,
|
|
287
178
|
onBuildLogs: defaultBuildLogger(),
|
|
288
179
|
});
|
|
289
180
|
console.log(`\nā
Custom template: ${result.templateId}`);
|
|
290
|
-
console.log(" Functions are PRE-DEPLOYED - sandbox starts instantly!");
|
|
291
181
|
return result.templateId;
|
|
292
182
|
}
|
|
293
183
|
export async function build(options) {
|
|
294
|
-
console.log("š Lakitu Template Builder\n");
|
|
295
|
-
// Preflight: Check for API key before doing any work
|
|
184
|
+
console.log("š Lakitu Template Builder (Dockerfile method)\n");
|
|
296
185
|
const apiKey = preflightCheck();
|
|
297
186
|
if (options.base) {
|
|
298
187
|
await buildBase(apiKey);
|
|
@@ -301,11 +190,9 @@ export async function build(options) {
|
|
|
301
190
|
await buildCustom(apiKey, options.baseId || "lakitu-base");
|
|
302
191
|
}
|
|
303
192
|
else {
|
|
304
|
-
// Build both
|
|
305
193
|
const baseId = await buildBase(apiKey);
|
|
306
194
|
await buildCustom(apiKey, baseId);
|
|
307
195
|
}
|
|
308
196
|
console.log("\nš Build complete!");
|
|
309
|
-
// Force exit - E2B SDK keeps event loop alive
|
|
310
197
|
process.exit(0);
|
|
311
198
|
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Build E2B template using Dockerfile (bypasses SDK .copy() issues)
|
|
3
|
+
set -e
|
|
4
|
+
|
|
5
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
6
|
+
LAKITU_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
7
|
+
PROJECT_ROOT="$(cd "$LAKITU_DIR/../.." && pwd)"
|
|
8
|
+
BUILD_CONTEXT="/tmp/lakitu-docker-build"
|
|
9
|
+
|
|
10
|
+
echo "=== Lakitu Docker Build ==="
|
|
11
|
+
echo "Script dir: $SCRIPT_DIR"
|
|
12
|
+
echo "Lakitu dir: $LAKITU_DIR"
|
|
13
|
+
echo "Project root: $PROJECT_ROOT"
|
|
14
|
+
|
|
15
|
+
# Clean and create build context
|
|
16
|
+
rm -rf "$BUILD_CONTEXT"
|
|
17
|
+
mkdir -p "$BUILD_CONTEXT"
|
|
18
|
+
|
|
19
|
+
# Copy lakitu source (excluding unnecessary files)
|
|
20
|
+
echo "Copying lakitu source..."
|
|
21
|
+
rsync -av \
|
|
22
|
+
--exclude='node_modules' \
|
|
23
|
+
--exclude='.git' \
|
|
24
|
+
--exclude='template' \
|
|
25
|
+
--exclude='assets' \
|
|
26
|
+
--exclude='cli' \
|
|
27
|
+
--exclude='tests' \
|
|
28
|
+
--exclude='dist' \
|
|
29
|
+
--exclude='.github' \
|
|
30
|
+
--exclude='.env*' \
|
|
31
|
+
--exclude='convex/cloud' \
|
|
32
|
+
"$LAKITU_DIR/" "$BUILD_CONTEXT/lakitu/"
|
|
33
|
+
|
|
34
|
+
# Copy project KSAs if they exist
|
|
35
|
+
if [ -d "$PROJECT_ROOT/lakitu" ]; then
|
|
36
|
+
echo "Copying project KSAs..."
|
|
37
|
+
cp "$PROJECT_ROOT/lakitu/"*.ts "$BUILD_CONTEXT/lakitu/ksa/" 2>/dev/null || true
|
|
38
|
+
echo "Copied $(ls "$PROJECT_ROOT/lakitu/"*.ts 2>/dev/null | wc -l) project KSAs"
|
|
39
|
+
fi
|
|
40
|
+
|
|
41
|
+
# Copy Dockerfile and scripts
|
|
42
|
+
echo "Copying Docker files..."
|
|
43
|
+
cp "$SCRIPT_DIR/e2b/start.sh" "$BUILD_CONTEXT/"
|
|
44
|
+
cp "$SCRIPT_DIR/e2b/prebuild.sh" "$BUILD_CONTEXT/"
|
|
45
|
+
|
|
46
|
+
# Create Dockerfile in build context
|
|
47
|
+
cat > "$BUILD_CONTEXT/Dockerfile" << 'DOCKERFILE'
|
|
48
|
+
# Lakitu E2B Sandbox Template
|
|
49
|
+
FROM e2bdev/code-interpreter:latest
|
|
50
|
+
|
|
51
|
+
# System dependencies
|
|
52
|
+
RUN apt-get update && apt-get install -y \
|
|
53
|
+
git curl sqlite3 libsqlite3-dev \
|
|
54
|
+
build-essential unzip \
|
|
55
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
56
|
+
|
|
57
|
+
# Bun runtime
|
|
58
|
+
RUN curl -fsSL https://bun.sh/install | bash
|
|
59
|
+
ENV PATH="/root/.bun/bin:/home/user/.bun/bin:$PATH"
|
|
60
|
+
|
|
61
|
+
# Node.js for npx convex
|
|
62
|
+
RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && \
|
|
63
|
+
apt-get install -y nodejs && \
|
|
64
|
+
rm -rf /var/lib/apt/lists/*
|
|
65
|
+
|
|
66
|
+
# Convex local backend
|
|
67
|
+
RUN curl -L -o /tmp/convex.zip "https://github.com/get-convex/convex-backend/releases/download/precompiled-2026-01-08-272e7f4/convex-local-backend-x86_64-unknown-linux-gnu.zip" && \
|
|
68
|
+
unzip /tmp/convex.zip -d /tmp && \
|
|
69
|
+
mv /tmp/convex-local-backend /usr/local/bin/convex-backend && \
|
|
70
|
+
chmod +x /usr/local/bin/convex-backend && \
|
|
71
|
+
rm /tmp/convex.zip
|
|
72
|
+
|
|
73
|
+
# Directory structure
|
|
74
|
+
RUN mkdir -p /home/user/workspace /home/user/.convex/convex-backend-state/lakitu /home/user/artifacts && \
|
|
75
|
+
chown -R user:user /home/user
|
|
76
|
+
|
|
77
|
+
# Copy lakitu code
|
|
78
|
+
COPY --chown=user:user lakitu/ /home/user/lakitu/
|
|
79
|
+
|
|
80
|
+
# Copy scripts
|
|
81
|
+
COPY --chown=user:user start.sh /home/user/start.sh
|
|
82
|
+
COPY --chown=user:user prebuild.sh /home/user/prebuild.sh
|
|
83
|
+
RUN chmod +x /home/user/start.sh /home/user/prebuild.sh
|
|
84
|
+
|
|
85
|
+
# Install dependencies
|
|
86
|
+
WORKDIR /home/user/lakitu
|
|
87
|
+
RUN /root/.bun/bin/bun install
|
|
88
|
+
|
|
89
|
+
# Create CLI tools
|
|
90
|
+
RUN echo '#!/bin/bash\nbun run /home/user/lakitu/runtime/pdf/pdf-generator.ts "$@"' > /usr/local/bin/generate-pdf && \
|
|
91
|
+
chmod +x /usr/local/bin/generate-pdf && \
|
|
92
|
+
echo '#!/bin/bash\nbun run /home/user/lakitu/runtime/browser/agent-browser-cli.ts "$@"' > /usr/local/bin/agent-browser && \
|
|
93
|
+
chmod +x /usr/local/bin/agent-browser
|
|
94
|
+
|
|
95
|
+
# Symlink KSA
|
|
96
|
+
RUN ln -sf /home/user/lakitu/ksa /home/user/ksa
|
|
97
|
+
|
|
98
|
+
# Pre-deploy Convex functions (bakes them into image)
|
|
99
|
+
RUN /home/user/prebuild.sh
|
|
100
|
+
|
|
101
|
+
# Fix ownership
|
|
102
|
+
RUN chown -R user:user /home/user
|
|
103
|
+
|
|
104
|
+
# Environment
|
|
105
|
+
ENV HOME=/home/user
|
|
106
|
+
ENV PATH="/home/user/.bun/bin:/usr/local/bin:/usr/bin:/bin"
|
|
107
|
+
ENV CONVEX_URL="http://localhost:3210"
|
|
108
|
+
ENV LOCAL_CONVEX_URL="http://localhost:3210"
|
|
109
|
+
ENV CONVEX_LOCAL_STORAGE="/home/user/.convex/convex-backend-state/lakitu"
|
|
110
|
+
|
|
111
|
+
USER user
|
|
112
|
+
WORKDIR /home/user/workspace
|
|
113
|
+
DOCKERFILE
|
|
114
|
+
|
|
115
|
+
# Create e2b.toml
|
|
116
|
+
cat > "$BUILD_CONTEXT/e2b.toml" << 'TOML'
|
|
117
|
+
template_id = "lakitu"
|
|
118
|
+
template_name = "lakitu"
|
|
119
|
+
dockerfile = "Dockerfile"
|
|
120
|
+
start_cmd = "/home/user/start.sh"
|
|
121
|
+
TOML
|
|
122
|
+
|
|
123
|
+
echo ""
|
|
124
|
+
echo "Build context ready at: $BUILD_CONTEXT"
|
|
125
|
+
echo "Contents:"
|
|
126
|
+
ls -la "$BUILD_CONTEXT"
|
|
127
|
+
echo ""
|
|
128
|
+
echo "Lakitu KSA modules:"
|
|
129
|
+
ls "$BUILD_CONTEXT/lakitu/ksa/"*.ts 2>/dev/null | head -10
|
|
130
|
+
echo ""
|
|
131
|
+
|
|
132
|
+
# Run E2B template build
|
|
133
|
+
echo "=== Running e2b template build ==="
|
|
134
|
+
cd "$BUILD_CONTEXT"
|
|
135
|
+
e2b template build
|
|
136
|
+
|
|
137
|
+
echo ""
|
|
138
|
+
echo "=== Build complete! ==="
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* Build E2B template using Dockerfile (bypasses SDK .copy() issues)
|
|
4
|
+
*
|
|
5
|
+
* Uses Build System v2's fromDockerfile() which leverages Docker's build context
|
|
6
|
+
* instead of the problematic .copy() tar streaming.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { Template, defaultBuildLogger, waitForPort } from "e2b";
|
|
10
|
+
import { $ } from "bun";
|
|
11
|
+
import { existsSync, readdirSync } from "fs";
|
|
12
|
+
import { join, dirname } from "path";
|
|
13
|
+
|
|
14
|
+
const SCRIPT_DIR = dirname(import.meta.path);
|
|
15
|
+
const LAKITU_DIR = join(SCRIPT_DIR, "..");
|
|
16
|
+
const PROJECT_ROOT = join(LAKITU_DIR, "../..");
|
|
17
|
+
const BUILD_CONTEXT = "/tmp/lakitu-docker-build";
|
|
18
|
+
|
|
19
|
+
async function getApiKey(): Promise<string> {
|
|
20
|
+
if (process.env.E2B_API_KEY) return process.env.E2B_API_KEY;
|
|
21
|
+
|
|
22
|
+
const envPaths = [
|
|
23
|
+
join(process.cwd(), ".env.local"),
|
|
24
|
+
join(PROJECT_ROOT, ".env.local"),
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
for (const path of envPaths) {
|
|
28
|
+
try {
|
|
29
|
+
const content = await Bun.file(path).text();
|
|
30
|
+
const match = content.match(/E2B_API_KEY=(.+)/);
|
|
31
|
+
if (match) return match[1].trim();
|
|
32
|
+
} catch { /* not found */ }
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
throw new Error("E2B_API_KEY not found");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function prepareBuildContext() {
|
|
39
|
+
console.log("=== Preparing Docker build context ===");
|
|
40
|
+
|
|
41
|
+
// Clean and create
|
|
42
|
+
await $`rm -rf ${BUILD_CONTEXT}`.quiet();
|
|
43
|
+
await $`mkdir -p ${BUILD_CONTEXT}`.quiet();
|
|
44
|
+
|
|
45
|
+
// Copy lakitu source
|
|
46
|
+
console.log("Copying lakitu source...");
|
|
47
|
+
await $`rsync -av \
|
|
48
|
+
--exclude='node_modules' \
|
|
49
|
+
--exclude='.git' \
|
|
50
|
+
--exclude='template' \
|
|
51
|
+
--exclude='assets' \
|
|
52
|
+
--exclude='cli' \
|
|
53
|
+
--exclude='tests' \
|
|
54
|
+
--exclude='dist' \
|
|
55
|
+
--exclude='.github' \
|
|
56
|
+
--exclude='.env*' \
|
|
57
|
+
--exclude='convex/cloud' \
|
|
58
|
+
${LAKITU_DIR}/ ${BUILD_CONTEXT}/lakitu/`.quiet();
|
|
59
|
+
|
|
60
|
+
// Copy project KSAs
|
|
61
|
+
const projectKsaDir = join(PROJECT_ROOT, "lakitu");
|
|
62
|
+
if (existsSync(projectKsaDir)) {
|
|
63
|
+
console.log("Copying project KSAs...");
|
|
64
|
+
const ksaFiles = readdirSync(projectKsaDir).filter(f => f.endsWith(".ts"));
|
|
65
|
+
for (const file of ksaFiles) {
|
|
66
|
+
await $`cp ${join(projectKsaDir, file)} ${BUILD_CONTEXT}/lakitu/ksa/`.quiet();
|
|
67
|
+
}
|
|
68
|
+
console.log(` Copied ${ksaFiles.length} project KSAs`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Copy scripts
|
|
72
|
+
await $`cp ${SCRIPT_DIR}/e2b/start.sh ${BUILD_CONTEXT}/`.quiet();
|
|
73
|
+
await $`cp ${SCRIPT_DIR}/e2b/prebuild.sh ${BUILD_CONTEXT}/`.quiet();
|
|
74
|
+
|
|
75
|
+
// Create Dockerfile
|
|
76
|
+
const dockerfile = `# Lakitu E2B Sandbox Template
|
|
77
|
+
FROM e2bdev/code-interpreter:latest
|
|
78
|
+
|
|
79
|
+
# System dependencies
|
|
80
|
+
RUN apt-get update && apt-get install -y \\
|
|
81
|
+
git curl sqlite3 libsqlite3-dev \\
|
|
82
|
+
build-essential unzip \\
|
|
83
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
84
|
+
|
|
85
|
+
# Bun runtime
|
|
86
|
+
RUN curl -fsSL https://bun.sh/install | bash
|
|
87
|
+
ENV PATH="/root/.bun/bin:/home/user/.bun/bin:\$PATH"
|
|
88
|
+
|
|
89
|
+
# Node.js for npx convex
|
|
90
|
+
RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && \\
|
|
91
|
+
apt-get install -y nodejs && \\
|
|
92
|
+
rm -rf /var/lib/apt/lists/*
|
|
93
|
+
|
|
94
|
+
# Convex local backend
|
|
95
|
+
RUN curl -L -o /tmp/convex.zip "https://github.com/get-convex/convex-backend/releases/download/precompiled-2026-01-08-272e7f4/convex-local-backend-x86_64-unknown-linux-gnu.zip" && \\
|
|
96
|
+
unzip /tmp/convex.zip -d /tmp && \\
|
|
97
|
+
mv /tmp/convex-local-backend /usr/local/bin/convex-backend && \\
|
|
98
|
+
chmod +x /usr/local/bin/convex-backend && \\
|
|
99
|
+
rm /tmp/convex.zip
|
|
100
|
+
|
|
101
|
+
# Directory structure
|
|
102
|
+
RUN mkdir -p /home/user/workspace /home/user/.convex/convex-backend-state/lakitu /home/user/artifacts && \\
|
|
103
|
+
chown -R user:user /home/user
|
|
104
|
+
|
|
105
|
+
# Copy lakitu code
|
|
106
|
+
COPY --chown=user:user lakitu/ /home/user/lakitu/
|
|
107
|
+
|
|
108
|
+
# Copy scripts
|
|
109
|
+
COPY --chown=user:user start.sh /home/user/start.sh
|
|
110
|
+
COPY --chown=user:user prebuild.sh /home/user/prebuild.sh
|
|
111
|
+
RUN chmod +x /home/user/start.sh /home/user/prebuild.sh
|
|
112
|
+
|
|
113
|
+
# Install dependencies
|
|
114
|
+
WORKDIR /home/user/lakitu
|
|
115
|
+
RUN /root/.bun/bin/bun install
|
|
116
|
+
|
|
117
|
+
# Create CLI tools
|
|
118
|
+
RUN echo '#!/bin/bash\\nbun run /home/user/lakitu/runtime/pdf/pdf-generator.ts "\$@"' > /usr/local/bin/generate-pdf && \\
|
|
119
|
+
chmod +x /usr/local/bin/generate-pdf && \\
|
|
120
|
+
echo '#!/bin/bash\\nbun run /home/user/lakitu/runtime/browser/agent-browser-cli.ts "\$@"' > /usr/local/bin/agent-browser && \\
|
|
121
|
+
chmod +x /usr/local/bin/agent-browser
|
|
122
|
+
|
|
123
|
+
# Symlink KSA
|
|
124
|
+
RUN ln -sf /home/user/lakitu/ksa /home/user/ksa
|
|
125
|
+
|
|
126
|
+
# Pre-deploy Convex functions
|
|
127
|
+
RUN /home/user/prebuild.sh
|
|
128
|
+
|
|
129
|
+
# Fix ownership
|
|
130
|
+
RUN chown -R user:user /home/user
|
|
131
|
+
|
|
132
|
+
# Environment
|
|
133
|
+
ENV HOME=/home/user
|
|
134
|
+
ENV PATH="/home/user/.bun/bin:/usr/local/bin:/usr/bin:/bin"
|
|
135
|
+
ENV CONVEX_URL="http://localhost:3210"
|
|
136
|
+
ENV LOCAL_CONVEX_URL="http://localhost:3210"
|
|
137
|
+
ENV CONVEX_LOCAL_STORAGE="/home/user/.convex/convex-backend-state/lakitu"
|
|
138
|
+
|
|
139
|
+
USER user
|
|
140
|
+
WORKDIR /home/user/workspace
|
|
141
|
+
`;
|
|
142
|
+
|
|
143
|
+
await Bun.write(join(BUILD_CONTEXT, "Dockerfile"), dockerfile);
|
|
144
|
+
|
|
145
|
+
console.log("Build context ready:");
|
|
146
|
+
await $`ls -la ${BUILD_CONTEXT}`;
|
|
147
|
+
console.log("\nKSA modules:");
|
|
148
|
+
await $`ls ${BUILD_CONTEXT}/lakitu/ksa/*.ts 2>/dev/null | head -10`;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async function build() {
|
|
152
|
+
console.log("š Lakitu Docker Build (v2 fromDockerfile)\n");
|
|
153
|
+
|
|
154
|
+
const apiKey = await getApiKey();
|
|
155
|
+
console.log("š API key found\n");
|
|
156
|
+
|
|
157
|
+
await prepareBuildContext();
|
|
158
|
+
|
|
159
|
+
console.log("\n=== Building E2B template from Dockerfile ===\n");
|
|
160
|
+
|
|
161
|
+
const template = Template()
|
|
162
|
+
.fromDockerfile(join(BUILD_CONTEXT, "Dockerfile"))
|
|
163
|
+
.setStartCmd("/home/user/start.sh", waitForPort(3210));
|
|
164
|
+
|
|
165
|
+
const result = await Template.build(template, {
|
|
166
|
+
alias: "lakitu",
|
|
167
|
+
apiKey,
|
|
168
|
+
onBuildLogs: defaultBuildLogger(),
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
console.log(`\nā
Template built: ${result.templateId}`);
|
|
172
|
+
process.exit(0);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
build().catch(e => {
|
|
176
|
+
console.error("Build failed:", e);
|
|
177
|
+
process.exit(1);
|
|
178
|
+
});
|