sandboxbox 1.0.0 ā 1.0.2
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.
- package/cli.js +40 -17
- package/package.json +2 -2
- package/scripts/build.js +197 -0
- package/scripts/download-bubblewrap.js +17 -3
package/cli.js
CHANGED
@@ -14,7 +14,8 @@ import { readFileSync, existsSync, writeFileSync, mkdirSync } from 'fs';
|
|
14
14
|
import { execSync } from 'child_process';
|
15
15
|
import { fileURLToPath } from 'url';
|
16
16
|
import { dirname, resolve } from 'path';
|
17
|
-
|
17
|
+
|
18
|
+
// We'll import bubblewrap later, only on Linux systems
|
18
19
|
|
19
20
|
const __filename = fileURLToPath(import.meta.url);
|
20
21
|
const __dirname = dirname(__filename);
|
@@ -66,19 +67,26 @@ function showHelp() {
|
|
66
67
|
console.log(color('magenta', 'š 8ms startup ⢠True isolation ⢠Playwright ready'));
|
67
68
|
}
|
68
69
|
|
69
|
-
function checkBubblewrap() {
|
70
|
-
|
71
|
-
|
70
|
+
async function checkBubblewrap() {
|
71
|
+
try {
|
72
|
+
const { bubblewrap } = await import('./lib/bubblewrap.js');
|
72
73
|
|
73
|
-
if (
|
74
|
-
console.log(color('
|
75
|
-
|
76
|
-
|
77
|
-
|
74
|
+
if (bubblewrap.isAvailable()) {
|
75
|
+
console.log(color('green', `ā
Bubblewrap found: ${bubblewrap.getVersion()}`));
|
76
|
+
|
77
|
+
if (!bubblewrap.checkUserNamespaces()) {
|
78
|
+
console.log(color('yellow', 'ā ļø User namespaces not available'));
|
79
|
+
console.log(color('yellow', ' Try: sudo sysctl kernel.unprivileged_userns_clone=1'));
|
80
|
+
console.log(color('yellow', ' Or: echo 1 | sudo tee /proc/sys/kernel/unprivileged_userns_clone'));
|
81
|
+
}
|
78
82
|
|
79
|
-
|
80
|
-
|
81
|
-
|
83
|
+
return true;
|
84
|
+
} else {
|
85
|
+
console.log(color('red', bubblewrap.findBubblewrap().message));
|
86
|
+
return false;
|
87
|
+
}
|
88
|
+
} catch (error) {
|
89
|
+
console.log(color('red', `ā Failed to load bubblewrap manager: ${error.message}`));
|
82
90
|
return false;
|
83
91
|
}
|
84
92
|
}
|
@@ -128,6 +136,21 @@ CMD ["npm", "test"]
|
|
128
136
|
}
|
129
137
|
|
130
138
|
async function main() {
|
139
|
+
// Check platform first
|
140
|
+
if (process.platform !== 'linux') {
|
141
|
+
console.log(color('red', 'ā SandboxBox only works on Linux systems'));
|
142
|
+
console.log(color('yellow', 'š§ Required: Linux with bubblewrap (bwrap)'));
|
143
|
+
console.log('');
|
144
|
+
console.log(color('cyan', 'š” Alternatives for Windows users:'));
|
145
|
+
console.log(' ⢠Use WSL2 (Windows Subsystem for Linux 2)');
|
146
|
+
console.log(' ⢠Use Docker Desktop with Linux containers');
|
147
|
+
console.log(' ⢠Use GitHub Actions (ubuntu-latest runners)');
|
148
|
+
console.log(' ⢠Use a cloud Linux instance (AWS, GCP, Azure)');
|
149
|
+
console.log('');
|
150
|
+
console.log(color('green', 'ā
On Linux/WSL2, simply run: npx sandboxbox --help'));
|
151
|
+
process.exit(1);
|
152
|
+
}
|
153
|
+
|
131
154
|
const args = process.argv.slice(2);
|
132
155
|
|
133
156
|
showBanner();
|
@@ -143,7 +166,7 @@ async function main() {
|
|
143
166
|
switch (command) {
|
144
167
|
case 'setup':
|
145
168
|
console.log(color('blue', 'šļø Setting up Alpine Linux environment...'));
|
146
|
-
if (!checkBubblewrap()) process.exit(1);
|
169
|
+
if (!(await checkBubblewrap())) process.exit(1);
|
147
170
|
runScript('./container.js', ['setup']);
|
148
171
|
break;
|
149
172
|
|
@@ -154,21 +177,21 @@ async function main() {
|
|
154
177
|
process.exit(1);
|
155
178
|
}
|
156
179
|
console.log(color('blue', 'šļø Building container...'));
|
157
|
-
if (!checkBubblewrap()) process.exit(1);
|
180
|
+
if (!(await checkBubblewrap())) process.exit(1);
|
158
181
|
runScript('./container.js', ['build', commandArgs[0]]);
|
159
182
|
break;
|
160
183
|
|
161
184
|
case 'run':
|
162
185
|
const projectDir = commandArgs[0] || '.';
|
163
186
|
console.log(color('blue', 'š Running Playwright tests...'));
|
164
|
-
if (!checkBubblewrap()) process.exit(1);
|
187
|
+
if (!(await checkBubblewrap())) process.exit(1);
|
165
188
|
runScript('./container.js', ['run', projectDir]);
|
166
189
|
break;
|
167
190
|
|
168
191
|
case 'shell':
|
169
192
|
const shellDir = commandArgs[0] || '.';
|
170
193
|
console.log(color('blue', 'š Starting interactive shell...'));
|
171
|
-
if (!checkBubblewrap()) process.exit(1);
|
194
|
+
if (!(await checkBubblewrap())) process.exit(1);
|
172
195
|
runScript('./container.js', ['shell', shellDir]);
|
173
196
|
break;
|
174
197
|
|
@@ -181,7 +204,7 @@ async function main() {
|
|
181
204
|
const sampleDockerfile = createSampleDockerfile(testDir);
|
182
205
|
|
183
206
|
// Check for bubblewrap before proceeding
|
184
|
-
if (!checkBubblewrap()) {
|
207
|
+
if (!(await checkBubblewrap())) {
|
185
208
|
console.log(color('yellow', '\nš Sample Dockerfile created successfully!'));
|
186
209
|
console.log(color('yellow', 'To run tests, install bubblewrap and try again:'));
|
187
210
|
console.log(color('cyan', ` npx sandboxbox build "${sampleDockerfile}"`));
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "sandboxbox",
|
3
|
-
"version": "1.0.
|
3
|
+
"version": "1.0.2",
|
4
4
|
"description": "Zero-privilege container runner with Playwright support",
|
5
5
|
"type": "module",
|
6
6
|
"main": "index.js",
|
@@ -8,7 +8,7 @@
|
|
8
8
|
"sandboxbox": "./cli.js"
|
9
9
|
},
|
10
10
|
"scripts": {
|
11
|
-
"
|
11
|
+
"install": "node scripts/build.js",
|
12
12
|
"start": "node cli.js",
|
13
13
|
"setup": "node cli.js setup",
|
14
14
|
"build": "node cli.js build",
|
package/scripts/build.js
ADDED
@@ -0,0 +1,197 @@
|
|
1
|
+
#!/usr/bin/env node
|
2
|
+
|
3
|
+
/**
|
4
|
+
* SQLite-style build script for SandboxBox
|
5
|
+
* Downloads and compiles bubblewrap binary during npm install
|
6
|
+
*/
|
7
|
+
|
8
|
+
import fs from 'fs';
|
9
|
+
import path from 'path';
|
10
|
+
import https from 'https';
|
11
|
+
import { execSync } from 'child_process';
|
12
|
+
import { fileURLToPath } from 'url';
|
13
|
+
import { dirname } from 'path';
|
14
|
+
|
15
|
+
const __filename = fileURLToPath(import.meta.url);
|
16
|
+
const __dirname = dirname(__filename);
|
17
|
+
|
18
|
+
const BINARY_DIR = path.join(__dirname, '..', 'bin');
|
19
|
+
const BWRAP_VERSION = '0.11.0';
|
20
|
+
|
21
|
+
console.log('š¦ Building SandboxBox with bubblewrap...');
|
22
|
+
|
23
|
+
async function downloadAndBuild() {
|
24
|
+
// Create binary directory
|
25
|
+
if (!fs.existsSync(BINARY_DIR)) {
|
26
|
+
fs.mkdirSync(BINARY_DIR, { recursive: true });
|
27
|
+
}
|
28
|
+
|
29
|
+
const binaryPath = path.join(BINARY_DIR, 'bwrap');
|
30
|
+
|
31
|
+
// Skip on non-Linux platforms but still create stub
|
32
|
+
if (process.platform !== 'linux') {
|
33
|
+
console.log('ā¹ļø Skipping bubblewrap build on non-Linux platform');
|
34
|
+
console.log(' SandboxBox works on Linux only');
|
35
|
+
return;
|
36
|
+
}
|
37
|
+
|
38
|
+
// Check if already built
|
39
|
+
if (fs.existsSync(binaryPath)) {
|
40
|
+
try {
|
41
|
+
execSync(`"${binaryPath}" --version`, { stdio: 'ignore' });
|
42
|
+
console.log('ā
Bubblewrap already built');
|
43
|
+
return;
|
44
|
+
} catch (e) {
|
45
|
+
console.log('ā ļø Existing binary broken, rebuilding...');
|
46
|
+
}
|
47
|
+
}
|
48
|
+
|
49
|
+
// Try to use system bubblewrap first (fallback option)
|
50
|
+
try {
|
51
|
+
const systemBwrap = execSync('which bwrap', { encoding: 'utf8' }).trim();
|
52
|
+
if (systemBwrap && fs.existsSync(systemBwrap)) {
|
53
|
+
fs.copyFileSync(systemBwrap, binaryPath);
|
54
|
+
fs.chmodSync(binaryPath, 0o755);
|
55
|
+
console.log('ā
Using system bubblewrap:', systemBwrap);
|
56
|
+
return;
|
57
|
+
}
|
58
|
+
} catch (e) {
|
59
|
+
// System bwrap not found, continue with build
|
60
|
+
}
|
61
|
+
|
62
|
+
// Build from source like SQLite does
|
63
|
+
await buildFromSource(binaryPath);
|
64
|
+
}
|
65
|
+
|
66
|
+
async function buildFromSource(binaryPath) {
|
67
|
+
console.log('šØ Building bubblewrap from source (SQLite-style)...');
|
68
|
+
|
69
|
+
const tmpDir = fs.mkdtempSync(path.join(process.env.TMPDIR || '/tmp', 'bwrap-build-'));
|
70
|
+
|
71
|
+
try {
|
72
|
+
// Download source tarball
|
73
|
+
console.log('š„ Downloading bubblewrap source...');
|
74
|
+
const tarball = `bubblewrap-${BWRAP_VERSION}.tar.xz`;
|
75
|
+
const tarballPath = path.join(tmpDir, tarball);
|
76
|
+
const url = `https://github.com/containers/bubblewrap/releases/download/v${BWRAP_VERSION}/${tarball}`;
|
77
|
+
|
78
|
+
await new Promise((resolve, reject) => {
|
79
|
+
const file = fs.createWriteStream(tarballPath);
|
80
|
+
|
81
|
+
function download(url) {
|
82
|
+
https.get(url, (response) => {
|
83
|
+
// Handle redirects
|
84
|
+
if (response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) {
|
85
|
+
console.log(`š Following redirect to: ${response.headers.location}`);
|
86
|
+
download(response.headers.location);
|
87
|
+
return;
|
88
|
+
}
|
89
|
+
|
90
|
+
if (response.statusCode !== 200) {
|
91
|
+
reject(new Error(`HTTP ${response.statusCode}: ${response.statusMessage}`));
|
92
|
+
return;
|
93
|
+
}
|
94
|
+
|
95
|
+
response.pipe(file);
|
96
|
+
|
97
|
+
file.on('finish', () => {
|
98
|
+
file.close();
|
99
|
+
resolve();
|
100
|
+
});
|
101
|
+
}).on('error', reject);
|
102
|
+
}
|
103
|
+
|
104
|
+
download(url);
|
105
|
+
});
|
106
|
+
|
107
|
+
// Extract source
|
108
|
+
console.log('š¦ Extracting source...');
|
109
|
+
execSync(`tar -xf "${tarballPath}" -C "${tmpDir}"`, { stdio: 'inherit' });
|
110
|
+
|
111
|
+
const sourceDir = path.join(tmpDir, `bubblewrap-${BWRAP_VERSION}`);
|
112
|
+
|
113
|
+
// Check for required build tools
|
114
|
+
const missingTools = [];
|
115
|
+
try {
|
116
|
+
execSync('which gcc', { stdio: 'ignore' });
|
117
|
+
} catch (e) {
|
118
|
+
missingTools.push('gcc');
|
119
|
+
}
|
120
|
+
|
121
|
+
try {
|
122
|
+
execSync('which xz', { stdio: 'ignore' });
|
123
|
+
} catch (e) {
|
124
|
+
missingTools.push('xz');
|
125
|
+
}
|
126
|
+
|
127
|
+
if (missingTools.length > 0) {
|
128
|
+
console.log(`ā ļø Missing build tools: ${missingTools.join(', ')}`);
|
129
|
+
console.log(' On Ubuntu/Debian: sudo apt-get install build-essential xz-utils');
|
130
|
+
console.log(' On CentOS/RHEL: sudo yum groupinstall "Development Tools" && sudo yum install xz');
|
131
|
+
console.log(' Falling back to system bubblewrap check...');
|
132
|
+
|
133
|
+
// Create a placeholder binary that will show helpful error
|
134
|
+
const placeholderScript = `#!/bin/bash
|
135
|
+
echo "ā Bubblewrap not available"
|
136
|
+
echo ""
|
137
|
+
echo "š” Install bubblewrap system-wide:"
|
138
|
+
echo " sudo apt-get install bubblewrap # Ubuntu/Debian"
|
139
|
+
echo " sudo apk add bubblewrap # Alpine"
|
140
|
+
echo " sudo yum install bubblewrap # CentOS/RHEL"
|
141
|
+
echo ""
|
142
|
+
echo "Or install build tools and reinstall SandboxBox:"
|
143
|
+
echo " sudo apt-get install build-essential xz-utils"
|
144
|
+
echo " npm uninstall sandboxbox && npm install sandboxbox"
|
145
|
+
exit 1
|
146
|
+
`;
|
147
|
+
fs.writeFileSync(binaryPath, placeholderScript);
|
148
|
+
fs.chmodSync(binaryPath, 0o755);
|
149
|
+
console.log('š Created placeholder binary with installation instructions');
|
150
|
+
return;
|
151
|
+
}
|
152
|
+
|
153
|
+
// Configure and build
|
154
|
+
console.log('āļø Configuring build...');
|
155
|
+
execSync(`
|
156
|
+
cd "${sourceDir}" &&
|
157
|
+
./configure --prefix="${tmpDir}/install" --disable-man
|
158
|
+
`, { stdio: 'inherit' });
|
159
|
+
|
160
|
+
console.log('šļø Compiling bubblewrap...');
|
161
|
+
execSync(`
|
162
|
+
cd "${sourceDir}" &&
|
163
|
+
make -j$(nproc 2>/dev/null || echo 4)
|
164
|
+
`, { stdio: 'inherit' });
|
165
|
+
|
166
|
+
console.log('š¦ Installing...');
|
167
|
+
execSync(`
|
168
|
+
cd "${sourceDir}" &&
|
169
|
+
make install
|
170
|
+
`, { stdio: 'inherit' });
|
171
|
+
|
172
|
+
// Copy binary to final location
|
173
|
+
const builtBinary = path.join(tmpDir, 'install', 'bin', 'bwrap');
|
174
|
+
if (fs.existsSync(builtBinary)) {
|
175
|
+
fs.copyFileSync(builtBinary, binaryPath);
|
176
|
+
fs.chmodSync(binaryPath, 0o755);
|
177
|
+
console.log('ā
Bubblewrap built successfully!');
|
178
|
+
|
179
|
+
// Test the binary
|
180
|
+
const version = execSync(`"${binaryPath}" --version`, { encoding: 'utf8' });
|
181
|
+
console.log(`šÆ Built: ${version.trim()}`);
|
182
|
+
} else {
|
183
|
+
throw new Error('Built binary not found');
|
184
|
+
}
|
185
|
+
|
186
|
+
} finally {
|
187
|
+
// Cleanup
|
188
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
189
|
+
}
|
190
|
+
}
|
191
|
+
|
192
|
+
// Run the build
|
193
|
+
downloadAndBuild().catch(error => {
|
194
|
+
console.error('ā Build failed:', error.message);
|
195
|
+
console.log('š” SandboxBox will still work with system bubblewrap if available');
|
196
|
+
process.exit(0); // Don't fail npm install
|
197
|
+
});
|
@@ -105,7 +105,20 @@ async function downloadBubblewrap() {
|
|
105
105
|
|
106
106
|
} catch (downloadError) {
|
107
107
|
console.log('ā ļø Download failed, trying to compile from source...');
|
108
|
-
|
108
|
+
try {
|
109
|
+
await compileBubblewrap(binaryPath);
|
110
|
+
} catch (compileError) {
|
111
|
+
console.log('ā Both download and compilation failed');
|
112
|
+
console.log('');
|
113
|
+
console.log('š” Easy solutions:');
|
114
|
+
console.log(' 1. Install system bubblewrap: sudo apt-get install bubblewrap');
|
115
|
+
console.log(' 2. Install build tools: sudo apt-get install build-essential xz-utils');
|
116
|
+
console.log(' 3. Use a Linux system with bubblewrap pre-installed');
|
117
|
+
console.log('');
|
118
|
+
console.log('SandboxBox will work with system bubblewrap if available.');
|
119
|
+
console.log('Continuing without bundled binary...');
|
120
|
+
return; // Don't exit, just continue without bundled binary
|
121
|
+
}
|
109
122
|
}
|
110
123
|
}
|
111
124
|
|
@@ -161,12 +174,13 @@ async function compileBubblewrap(binaryPath) {
|
|
161
174
|
console.log(' sudo yum install bubblewrap # CentOS/RHEL');
|
162
175
|
console.log('');
|
163
176
|
console.log('Then try installing SandboxBox again.');
|
164
|
-
|
177
|
+
throw compileError; // Re-throw to let the caller handle it
|
165
178
|
}
|
166
179
|
}
|
167
180
|
|
168
181
|
// Run the download
|
169
182
|
downloadBubblewrap().catch(error => {
|
170
183
|
console.error('ā Setup failed:', error.message);
|
171
|
-
|
184
|
+
// Don't exit with error code 1, just warn and continue
|
185
|
+
console.log('ā¹ļø SandboxBox will use system bubblewrap if available');
|
172
186
|
});
|