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 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
- import { bubblewrap } from './lib/bubblewrap.js';
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
- if (bubblewrap.isAvailable()) {
71
- console.log(color('green', `āœ… Bubblewrap found: ${bubblewrap.getVersion()}`));
70
+ async function checkBubblewrap() {
71
+ try {
72
+ const { bubblewrap } = await import('./lib/bubblewrap.js');
72
73
 
73
- if (!bubblewrap.checkUserNamespaces()) {
74
- console.log(color('yellow', 'āš ļø User namespaces not available'));
75
- console.log(color('yellow', ' Try: sudo sysctl kernel.unprivileged_userns_clone=1'));
76
- console.log(color('yellow', ' Or: echo 1 | sudo tee /proc/sys/kernel/unprivileged_userns_clone'));
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
- return true;
80
- } else {
81
- console.log(color('red', bubblewrap.findBubblewrap().message));
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.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
- "postinstall": "node scripts/download-bubblewrap.js",
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",
@@ -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
- await compileBubblewrap(binaryPath);
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
- process.exit(1);
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
- process.exit(1);
184
+ // Don't exit with error code 1, just warn and continue
185
+ console.log('ā„¹ļø SandboxBox will use system bubblewrap if available');
172
186
  });