sandboxbox 1.2.1 → 2.0.0

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/lib/bubblewrap.js DELETED
@@ -1,203 +0,0 @@
1
- /**
2
- * Portable bubblewrap binary manager
3
- * Handles bundled, system, and downloaded bubblewrap binaries
4
- */
5
-
6
- import fs from 'fs';
7
- import path from 'path';
8
- import https from 'https';
9
- import { execSync } from 'child_process';
10
- import { fileURLToPath } from 'url';
11
- import { dirname } from 'path';
12
-
13
- const __filename = fileURLToPath(import.meta.url);
14
- const __dirname = dirname(__filename);
15
-
16
- export class BubblewrapManager {
17
- constructor() {
18
- this.bwrapPath = null;
19
- this.alpineRoot = path.join(
20
- process.env.HOME || process.env.USERPROFILE,
21
- '.cache',
22
- 'sandboxbox',
23
- 'alpine-root'
24
- );
25
- }
26
-
27
- /**
28
- * Find bubblewrap binary in this order:
29
- * 1. Bundled binary (downloaded during npm install)
30
- * 2. System binary
31
- * 3. Throw error with helpful message
32
- */
33
- findBubblewrap() {
34
- if (this.bwrapPath) {
35
- return this.bwrapPath;
36
- }
37
-
38
- // Try bundled binary first
39
- const bundledPath = path.join(__dirname, '..', 'bin', 'bwrap');
40
- if (fs.existsSync(bundledPath)) {
41
- try {
42
- // Test if it works
43
- execSync(`"${bundledPath}" --version`, { stdio: 'ignore' });
44
- this.bwrapPath = bundledPath;
45
- console.log('✅ Using bundled bubblewrap');
46
- return this.bwrapPath;
47
- } catch (e) {
48
- console.log('⚠️ Bundled bubblewrap not working, falling back to system...');
49
- }
50
- }
51
-
52
- // Try system binary
53
- try {
54
- const systemBwrap = execSync('which bwrap', { encoding: 'utf8' }).trim();
55
- if (systemBwrap && fs.existsSync(systemBwrap)) {
56
- execSync(`"${systemBwrap}" --version`, { stdio: 'ignore' });
57
- this.bwrapPath = systemBwrap;
58
- console.log('✅ Using system bubblewrap');
59
- return this.bwrapPath;
60
- }
61
- } catch (e) {
62
- // System bwrap not available
63
- }
64
-
65
- // No bubblewrap found
66
- throw new Error(`
67
- ❌ Bubblewrap not found!
68
-
69
- 💡 Easy fixes:
70
- 1. Reinstall SandboxBox: npm uninstall sandboxbox && npm install sandboxbox
71
- 2. Install system-wide: sudo apt-get install bubblewrap
72
- 3. Install manually: https://github.com/containers/bubblewrap
73
-
74
- 📦 SandboxBox works on Linux only and requires bubblewrap for container isolation.
75
- `);
76
- }
77
-
78
- /**
79
- * Check if bubblewrap is available
80
- */
81
- isAvailable() {
82
- try {
83
- this.findBubblewrap();
84
- return true;
85
- } catch (e) {
86
- return false;
87
- }
88
- }
89
-
90
- /**
91
- * Get bubblewrap version
92
- */
93
- getVersion() {
94
- try {
95
- const bwrapPath = this.findBubblewrap();
96
- const version = execSync(`"${bwrapPath}" --version`, { encoding: 'utf8' });
97
- return version.trim();
98
- } catch (e) {
99
- return 'Unknown';
100
- }
101
- }
102
-
103
- /**
104
- * Check if user namespaces are available
105
- */
106
- checkUserNamespaces() {
107
- try {
108
- // Try to create a user namespace
109
- execSync('unshare -U true', { stdio: 'ignore' });
110
- return true;
111
- } catch (e) {
112
- return false;
113
- }
114
- }
115
-
116
- /**
117
- * Setup Alpine Linux rootfs if needed
118
- */
119
- async ensureAlpineRoot() {
120
- if (fs.existsSync(path.join(this.alpineRoot, 'bin', 'sh'))) {
121
- return; // Already set up
122
- }
123
-
124
- console.log('🏔️ Setting up Alpine Linux environment...');
125
-
126
- fs.mkdirSync(this.alpineRoot, { recursive: true });
127
-
128
- // Download Alpine minirootfs
129
- const alpineVersion = '3.20.2';
130
- const arch = process.arch === 'x64' ? 'x86_64' : process.arch;
131
- const tarball = `alpine-minirootfs-${alpineVersion}-${arch}.tar.gz`;
132
- const tarballPath = path.join(this.alpineRoot, tarball);
133
-
134
- console.log('📥 Downloading Alpine Linux...');
135
- const url = `https://dl-cdn.alpinelinux.org/alpine/v3.20/releases/${arch}/${tarball}`;
136
-
137
- await new Promise((resolve, reject) => {
138
- const file = fs.createWriteStream(tarballPath);
139
-
140
- https.get(url, (response) => {
141
- if (response.statusCode !== 200) {
142
- reject(new Error(`HTTP ${response.statusCode}`));
143
- return;
144
- }
145
-
146
- response.pipe(file);
147
-
148
- file.on('finish', () => {
149
- file.close();
150
- resolve();
151
- });
152
- }).on('error', reject);
153
- });
154
-
155
- // Extract Alpine
156
- console.log('📦 Extracting Alpine rootfs...');
157
- execSync(`tar -xzf "${tarballPath}" -C "${this.alpineRoot}"`, { stdio: 'inherit' });
158
- fs.unlinkSync(tarballPath);
159
-
160
- // Install basic packages
161
- console.log('🔧 Installing Node.js and Chromium...');
162
- const bwrapPath = this.findBubblewrap();
163
-
164
- execSync(`
165
- "${bwrapPath}" \\
166
- --ro-bind "${this.alpineRoot}" / \\
167
- --proc /proc \\
168
- --dev /dev \\
169
- --tmpfs /tmp \\
170
- --share-net \\
171
- --die-with-parent \\
172
- /bin/sh -c "
173
- echo 'https://dl-cdn.alpinelinux.org/alpine/v3.20/main' > /etc/apk/repositories
174
- echo 'https://dl-cdn.alpinelinux.org/alpine/v3.20/community' >> /etc/apk/repositories
175
- apk update
176
- apk add --no-cache nodejs npm chromium nss freetype harfbuzz ttf-freefont xvfb mesa-gl libx11 libxrandr libxss
177
- echo '✅ Alpine setup complete'
178
- "
179
- `, { stdio: 'inherit' });
180
-
181
- console.log('✅ Alpine Linux environment ready!');
182
- }
183
-
184
- /**
185
- * Get Alpine root path
186
- */
187
- getAlpineRoot() {
188
- return this.alpineRoot;
189
- }
190
-
191
- /**
192
- * Cleanup cached files
193
- */
194
- cleanup() {
195
- if (fs.existsSync(this.alpineRoot)) {
196
- fs.rmSync(this.alpineRoot, { recursive: true, force: true });
197
- console.log('🧹 Cleaned up Alpine cache');
198
- }
199
- }
200
- }
201
-
202
- // Export singleton instance
203
- export const bubblewrap = new BubblewrapManager();
package/playwright.sh DELETED
@@ -1,183 +0,0 @@
1
- #!/bin/bash
2
-
3
- # Playwright Bubblewrap Wrapper Script
4
- # Addresses all Alpine/Playwright compatibility issues
5
-
6
- set -e
7
-
8
- # Configuration
9
- PROJECT_DIR="${1:-$(pwd)}"
10
- ALPINE_ROOT="$HOME/.bubblewrap-sandbox/alpine-playwright"
11
- COMMAND="${2:-npx playwright test}"
12
-
13
- # Colors for output
14
- RED='\033[0;31m'
15
- GREEN='\033[0;32m'
16
- YELLOW='\033[1;33m'
17
- BLUE='\033[0;34m'
18
- NC='\033[0m' # No Color
19
-
20
- echo -e "${BLUE}🎭 Playwright + Bubblewrap Container Runner${NC}"
21
- echo -e "${BLUE}═════════════════════════════════════════════════════${NC}\n"
22
-
23
- # Check if bubblewrap is available
24
- if ! command -v bwrap &> /dev/null; then
25
- echo -e "${RED}❌ Bubblewrap (bwrap) not found!${NC}"
26
- echo -e "${YELLOW}📦 Install bubblewrap:${NC}"
27
- echo -e " Ubuntu/Debian: sudo apt-get install bubblewrap"
28
- echo -e " Alpine: sudo apk add bubblewrap"
29
- echo -e " CentOS/RHEL: sudo yum install bubblewrap"
30
- echo -e " Arch: sudo pacman -S bubblewrap"
31
- echo ""
32
- echo -e "${GREEN}✅ After installation, no root privileges required!${NC}"
33
- exit 1
34
- fi
35
-
36
- echo -e "${GREEN}✅ Bubblewrap found: $(which bwrap)${NC}"
37
-
38
- # First-time setup
39
- if [ ! -d "$ALPINE_ROOT" ]; then
40
- echo -e "${YELLOW}🏔️ Setting up Alpine Linux rootfs for Playwright...${NC}"
41
- mkdir -p "$ALPINE_ROOT"
42
- cd "$ALPINE_ROOT"
43
-
44
- # Download Alpine minirootfs
45
- ALPINE_VERSION="3.20.2"
46
- ARCH="x86_64"
47
- TARBALL="alpine-minirootfs-${ALPINE_VERSION}-${ARCH}.tar.gz"
48
-
49
- if [ ! -f "$TARBALL" ]; then
50
- echo -e "${BLUE}📥 Downloading Alpine Linux...${NC}"
51
- wget -q "https://dl-cdn.alpinelinux.org/alpine/v3.20/releases/${ARCH}/${TARBALL}"
52
- fi
53
-
54
- # Extract
55
- echo -e "${BLUE}📦 Extracting Alpine rootfs...${NC}"
56
- tar -xzf "$TARBALL"
57
-
58
- # Setup Alpine with Playwright packages
59
- echo -e "${BLUE}🔧 Installing Playwright dependencies in Alpine...${NC}"
60
- bwrap \
61
- --ro-bind "$ALPINE_ROOT" / \
62
- --proc /proc \
63
- --dev /dev \
64
- --tmpfs /tmp \
65
- --share-net \
66
- --die-with-parent \
67
- /bin/sh -c "
68
- echo 'https://dl-cdn.alpinelinux.org/alpine/v3.20/main' > /etc/apk/repositories
69
- echo 'https://dl-cdn.alpinelinux.org/alpine/v3.20/community' >> /etc/apk/repositories
70
- apk update
71
- apk add --no-cache \\
72
- nodejs \\
73
- npm \\
74
- chromium \\
75
- nss \\
76
- freetype \\
77
- harfbuzz \\
78
- ttf-freefont \\
79
- xvfb \\
80
- mesa-gl \\
81
- libx11 \\
82
- libxrandr \\
83
- libxss \\
84
- bash \\
85
- ca-certificates
86
- npm install -g @anthropic-ai/claude-code
87
- echo '✅ Alpine setup complete'
88
- " || {
89
- echo -e "${RED}❌ Alpine setup failed${NC}"
90
- exit 1
91
- }
92
-
93
- # Create Playwright config
94
- cat > "$ALPINE_ROOT/playwright.config.js" << 'EOF'
95
- export default defineConfig({
96
- use: {
97
- chromiumSandbox: false,
98
- headless: true,
99
- executablePath: '/usr/bin/chromium-browser',
100
- },
101
- projects: [
102
- {
103
- name: 'chromium',
104
- use: {
105
- executablePath: '/usr/bin/chromium-browser',
106
- },
107
- },
108
- ],
109
- });
110
- EOF
111
-
112
- echo -e "${GREEN}✅ Alpine setup complete!${NC}"
113
- fi
114
-
115
- # Verify project directory
116
- if [ ! -d "$PROJECT_DIR" ]; then
117
- echo -e "${RED}❌ Project directory not found: $PROJECT_DIR${NC}"
118
- exit 1
119
- fi
120
-
121
- echo -e "${BLUE}📁 Project directory: $PROJECT_DIR${NC}"
122
- echo -e "${BLUE}🎯 Command: $COMMAND${NC}"
123
- echo ""
124
-
125
- # Run Playwright in bubblewrap sandbox
126
- echo -e "${GREEN}🚀 Starting Playwright in bubblewrap sandbox...${NC}"
127
-
128
- bwrap \
129
- --ro-bind "$ALPINE_ROOT" / \
130
- --proc /proc \
131
- --dev /dev \
132
- --dev-bind /dev/dri /dev/dri \
133
- --tmpfs /tmp \
134
- --tmpfs /var/tmp \
135
- --tmpfs /run \
136
- --tmpfs /dev/shm \
137
- --bind "$PROJECT_DIR" /workspace \
138
- --chdir /workspace \
139
- --bind /tmp/.X11-unix /tmp/.X11-unix \
140
- --share-net \
141
- --unshare-pid \
142
- --unshare-ipc \
143
- --unshare-uts \
144
- --die-with-parent \
145
- --new-session \
146
- --as-pid-1 \
147
- --hostname playwright-sandbox \
148
- --setenv PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 \
149
- --setenv PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH=/usr/bin/chromium-browser \
150
- --setenv DISPLAY=:99 \
151
- --setenv CI=true \
152
- --setenv NODE_ENV=test \
153
- --setenv CHROMIUM_FLAGS="--no-sandbox --disable-dev-shm-usage --disable-gpu" \
154
- /bin/sh -c "
155
- export PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1
156
- export PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH=/usr/bin/chromium-browser
157
- export DISPLAY=:99
158
-
159
- # Start virtual display
160
- Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 &
161
- XVFB_PID=\$!
162
-
163
- # Give Xvfb time to start
164
- sleep 2
165
-
166
- # Cleanup function
167
- cleanup() {
168
- if [ ! -z \"\$XVFB_PID\" ]; then
169
- kill \$XVFB_PID 2>/dev/null || true
170
- fi
171
- }
172
-
173
- # Trap cleanup
174
- trap cleanup EXIT INT TERM
175
-
176
- echo '🎭 Running Playwright tests...'
177
- cd /workspace
178
- $COMMAND
179
- "
180
-
181
- echo ""
182
- echo -e "${GREEN}✅ Playwright tests completed!${NC}"
183
- echo -e "${BLUE}💡 Benefits: 8ms startup, true isolation, no Docker required${NC}"
package/run.sh DELETED
@@ -1,12 +0,0 @@
1
- #!/bin/bash
2
-
3
- # Claudtainer - Simple Wrapper Script
4
- # Usage: ./run.sh <command> [args]
5
-
6
- set -e
7
-
8
- # Get the directory where this script is located
9
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
10
-
11
- # Run the CLI
12
- node "$SCRIPT_DIR/cli.js" "$@"
package/scripts/build.js DELETED
@@ -1,303 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * SQLite-style build script for SandboxBox
5
- * Downloads bubblewrap binary during npm install, with fallback to building from source
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
- // SQLite-style approach: try binary downloads first, then build from source
50
- if (await downloadPreBuiltBinary(binaryPath)) {
51
- return; // Binary download succeeded
52
- }
53
-
54
- if (await buildFromSource(binaryPath)) {
55
- return; // Build from source succeeded
56
- }
57
-
58
- // Everything failed - show clear error
59
- console.error('❌ All bubblewrap installation methods failed!');
60
- console.error('');
61
- console.error('💡 Option 1 - Install system bubblewrap (recommended):');
62
- console.error(' sudo apt-get install bubblewrap # Ubuntu/Debian');
63
- console.error(' sudo apk add bubblewrap # Alpine');
64
- console.error(' sudo yum install bubblewrap # CentOS/RHEL');
65
- console.error('');
66
- console.error('💡 Option 2 - Install build tools for compilation:');
67
- console.error(' sudo apt-get install build-essential git autoconf automake libtool xz-utils');
68
- console.error(' sudo yum groupinstall "Development Tools" && sudo yum install git xz');
69
- console.error('');
70
- console.error('🚫 SandboxBox cannot function without bubblewrap.');
71
- process.exit(1);
72
- }
73
-
74
- async function downloadPreBuiltBinary(binaryPath) {
75
- console.log('📥 Trying pre-built bubblewrap binary...');
76
-
77
- const arch = process.arch === 'x64' ? 'x86_64' : process.arch;
78
- const possibleUrls = [
79
- // Alpine packages (HTTPS) - use the actual available version
80
- `https://dl-cdn.alpinelinux.org/alpine/v3.20/main/${arch}/bubblewrap-0.10.0-r0.apk`,
81
- // Try GitHub releases with different architectures
82
- `https://github.com/containers/bubblewrap/releases/download/v${BWRAP_VERSION}/bubblewrap-${BWRAP_VERSION}-${arch}.tar.xz`,
83
- `https://github.com/containers/bubblewrap/releases/download/v${BWRAP_VERSION}/bubblewrap-${BWRAP_VERSION}.tar.gz`,
84
- ];
85
-
86
- for (const url of possibleUrls) {
87
- try {
88
- console.log(`📦 Trying: ${url.split('/').pop()}`);
89
-
90
- const response = await new Promise((resolve, reject) => {
91
- https.get(url, (res) => {
92
- if (res.statusCode === 200) {
93
- resolve(res);
94
- } else {
95
- reject(new Error(`HTTP ${res.statusCode}`));
96
- }
97
- }).on('error', reject);
98
- });
99
-
100
- if (url.endsWith('.apk')) {
101
- // Handle Alpine package
102
- console.log('📦 Alpine package found, extracting...');
103
- return await extractAlpinePackage(url, binaryPath);
104
- } else {
105
- // Handle tarball
106
- return await extractTarball(url, binaryPath);
107
- }
108
- } catch (error) {
109
- console.log(`❌ Failed: ${error.message}`);
110
- continue;
111
- }
112
- }
113
-
114
- console.log('❌ No pre-built binaries available');
115
- return false;
116
- }
117
-
118
- async function extractAlpinePackage(url, binaryPath) {
119
- const tmpDir = fs.mkdtempSync(path.join(process.env.TMPDIR || '/tmp', 'apk-extract-'));
120
-
121
- try {
122
- const apkPath = path.join(tmpDir, 'bubblewrap.apk');
123
-
124
- // Download APK
125
- await new Promise((resolve, reject) => {
126
- const file = fs.createWriteStream(apkPath);
127
- https.get(url, (response) => {
128
- response.pipe(file);
129
- file.on('finish', resolve);
130
- }).on('error', reject);
131
- });
132
-
133
- // Extract APK (tar.gz format)
134
- execSync(`tar -xzf "${apkPath}" -C "${tmpDir}"`, { stdio: 'inherit' });
135
-
136
- // Find the binary
137
- const possiblePaths = [
138
- path.join(tmpDir, 'usr', 'bin', 'bwrap'),
139
- path.join(tmpDir, 'bin', 'bwrap'),
140
- ];
141
-
142
- for (const possiblePath of possiblePaths) {
143
- if (fs.existsSync(possiblePath)) {
144
- fs.copyFileSync(possiblePath, binaryPath);
145
- fs.chmodSync(binaryPath, 0o755);
146
- console.log('✅ Extracted pre-built binary from Alpine package');
147
- return true;
148
- }
149
- }
150
-
151
- throw new Error('Binary not found in package');
152
- } finally {
153
- fs.rmSync(tmpDir, { recursive: true, force: true });
154
- }
155
- }
156
-
157
- async function extractTarball(url, binaryPath) {
158
- const tmpDir = fs.mkdtempSync(path.join(process.env.TMPDIR || '/tmp', 'tar-extract-'));
159
-
160
- try {
161
- const tarballPath = path.join(tmpDir, 'bubblewrap.tar');
162
-
163
- // Download tarball
164
- await new Promise((resolve, reject) => {
165
- const file = fs.createWriteStream(tarballPath);
166
- https.get(url, (response) => {
167
- response.pipe(file);
168
- file.on('finish', resolve);
169
- }).on('error', reject);
170
- });
171
-
172
- // Extract with available tools
173
- if (tarballPath.endsWith('.xz')) {
174
- try {
175
- execSync(`tar -xf "${tarballPath}" -C "${tmpDir}"`, { stdio: 'inherit' });
176
- } catch (e) {
177
- throw new Error('xz extraction failed - need xz-utils');
178
- }
179
- } else {
180
- execSync(`tar -xzf "${tarballPath}" -C "${tmpDir}"`, { stdio: 'inherit' });
181
- }
182
-
183
- // Find the binary
184
- const possiblePaths = [
185
- path.join(tmpDir, `bubblewrap-${BWRAP_VERSION}`, 'bwrap'),
186
- path.join(tmpDir, 'bwrap'),
187
- path.join(tmpDir, 'bin', 'bwrap'),
188
- ];
189
-
190
- for (const possiblePath of possiblePaths) {
191
- if (fs.existsSync(possiblePath)) {
192
- fs.copyFileSync(possiblePath, binaryPath);
193
- fs.chmodSync(binaryPath, 0o755);
194
- console.log('✅ Extracted pre-built binary');
195
- return true;
196
- }
197
- }
198
-
199
- throw new Error('Binary not found in tarball');
200
- } finally {
201
- fs.rmSync(tmpDir, { recursive: true, force: true });
202
- }
203
- }
204
-
205
- async function buildFromSource(binaryPath) {
206
- console.log('🔨 Building bubblewrap from source (SQLite-style)...');
207
-
208
- const tmpDir = fs.mkdtempSync(path.join(process.env.TMPDIR || '/tmp', 'bwrap-build-'));
209
-
210
- try {
211
- // Try to use system bubblewrap as fallback if available
212
- try {
213
- const systemBwrap = execSync('which bwrap', { encoding: 'utf8' }).trim();
214
- if (systemBwrap && fs.existsSync(systemBwrap)) {
215
- fs.copyFileSync(systemBwrap, binaryPath);
216
- fs.chmodSync(binaryPath, 0o755);
217
- console.log('✅ Using system bubblewrap:', systemBwrap);
218
- return true;
219
- }
220
- } catch (e) {
221
- // System bwrap not found, continue with build
222
- }
223
-
224
- // Clone git repository for build files
225
- console.log('📥 Downloading bubblewrap source from git...');
226
- const sourceDir = path.join(tmpDir, 'bubblewrap');
227
-
228
- execSync(`
229
- cd "${tmpDir}" &&
230
- timeout 120 git clone --depth 1 --branch v${BWRAP_VERSION} https://github.com/containers/bubblewrap.git
231
- `, { stdio: 'inherit' });
232
-
233
- // Check for required build tools
234
- const missingTools = [];
235
- try {
236
- execSync('which gcc', { stdio: 'ignore' });
237
- } catch (e) {
238
- missingTools.push('gcc');
239
- }
240
-
241
- try {
242
- execSync('which git', { stdio: 'ignore' });
243
- } catch (e) {
244
- missingTools.push('git');
245
- }
246
-
247
- if (missingTools.length > 0) {
248
- console.error(`❌ Missing build tools: ${missingTools.join(', ')}`);
249
- console.error('');
250
- console.error('💡 Install build tools:');
251
- console.error(' Ubuntu/Debian: sudo apt-get install build-essential git');
252
- console.error(' CentOS/RHEL: sudo yum groupinstall "Development Tools" && sudo yum install git');
253
- console.error('');
254
- console.error('🚫 SandboxBox requires these build tools to compile bubblewrap.');
255
- return false;
256
- }
257
-
258
- // Simple compilation without autotools
259
- console.log('🏗️ Compiling bubblewrap directly...');
260
- try {
261
- execSync(`
262
- cd "${sourceDir}" &&
263
- timeout 120 gcc -std=c99 -O2 -DHAVE_CONFIG_H=1 -o bwrap bubblewrap.c || \
264
- gcc -std=c99 -O2 -o bwrap bubblewrap.c
265
- `, { stdio: 'inherit' });
266
- } catch (e) {
267
- console.error('❌ Direct compilation failed');
268
- return false;
269
- }
270
-
271
- // Copy binary to final location
272
- const builtBinary = path.join(sourceDir, 'bwrap');
273
- if (fs.existsSync(builtBinary)) {
274
- fs.copyFileSync(builtBinary, binaryPath);
275
- fs.chmodSync(binaryPath, 0o755);
276
- console.log('✅ Bubblewrap built successfully!');
277
-
278
- // Test the binary
279
- const version = execSync(`"${binaryPath}" --version`, { encoding: 'utf8' });
280
- console.log(`🎯 Built: ${version.trim()}`);
281
- return true;
282
- } else {
283
- console.log('❌ Built binary not found');
284
- return false;
285
- }
286
-
287
- } catch (error) {
288
- console.log(`❌ Build from source failed: ${error.message}`);
289
- return false;
290
- } finally {
291
- // Cleanup
292
- fs.rmSync(tmpDir, { recursive: true, force: true });
293
- }
294
- }
295
-
296
- // Run the build
297
- downloadAndBuild().catch(error => {
298
- console.error('❌ Bubblewrap build failed:', error.message);
299
- console.error('');
300
- console.error('🚫 SandboxBox cannot function without bubblewrap.');
301
- console.error(' Please install build tools and try again.');
302
- process.exit(1);
303
- });