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/Dockerfile +80 -24
- package/README.md +180 -99
- package/cli.js +125 -160
- package/package.json +11 -12
- package/BUBBLEWRAP-REALITY.md +0 -210
- package/USAGE.md +0 -111
- package/container.js +0 -608
- package/debug-cli.js +0 -15
- package/lib/bubblewrap.js +0 -203
- package/playwright.sh +0 -183
- package/run.sh +0 -12
- package/scripts/build.js +0 -303
- package/scripts/download-bubblewrap.js +0 -186
- package/test-cli.js +0 -72
- package/test-project/Dockerfile.sandboxbox +0 -20
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
|
-
});
|