sandboxbox 1.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/BUBBLEWRAP-REALITY.md +210 -0
- package/Dockerfile +39 -0
- package/README.md +150 -0
- package/USAGE.md +111 -0
- package/cli.js +219 -0
- package/container.js +471 -0
- package/lib/bubblewrap.js +203 -0
- package/package.json +38 -0
- package/playwright.sh +183 -0
- package/run.sh +12 -0
- package/scripts/download-bubblewrap.js +172 -0
- package/test-project/Dockerfile.sandboxbox +20 -0
@@ -0,0 +1,203 @@
|
|
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/package.json
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
{
|
2
|
+
"name": "sandboxbox",
|
3
|
+
"version": "1.0.0",
|
4
|
+
"description": "Zero-privilege container runner with Playwright support",
|
5
|
+
"type": "module",
|
6
|
+
"main": "index.js",
|
7
|
+
"bin": {
|
8
|
+
"sandboxbox": "./cli.js"
|
9
|
+
},
|
10
|
+
"scripts": {
|
11
|
+
"postinstall": "node scripts/download-bubblewrap.js",
|
12
|
+
"start": "node cli.js",
|
13
|
+
"setup": "node cli.js setup",
|
14
|
+
"build": "node cli.js build",
|
15
|
+
"run": "node cli.js run"
|
16
|
+
},
|
17
|
+
"keywords": [
|
18
|
+
"containers",
|
19
|
+
"playwright",
|
20
|
+
"bubblewrap",
|
21
|
+
"dockerless",
|
22
|
+
"sandbox",
|
23
|
+
"isolation",
|
24
|
+
"zero-privilege",
|
25
|
+
"userspace"
|
26
|
+
],
|
27
|
+
"author": "",
|
28
|
+
"license": "MIT",
|
29
|
+
"dependencies": {},
|
30
|
+
"repository": {
|
31
|
+
"type": "git",
|
32
|
+
"url": "https://github.com/AnEntrypoint/sandboxbox.git"
|
33
|
+
},
|
34
|
+
"homepage": "https://github.com/AnEntrypoint/sandboxbox#readme",
|
35
|
+
"bugs": {
|
36
|
+
"url": "https://github.com/AnEntrypoint/sandboxbox/issues"
|
37
|
+
}
|
38
|
+
}
|
package/playwright.sh
ADDED
@@ -0,0 +1,183 @@
|
|
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
ADDED
@@ -0,0 +1,12 @@
|
|
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" "$@"
|
@@ -0,0 +1,172 @@
|
|
1
|
+
#!/usr/bin/env node
|
2
|
+
|
3
|
+
/**
|
4
|
+
* Download bubblewrap binary during npm install
|
5
|
+
* Makes SandboxBox completely self-contained
|
6
|
+
*/
|
7
|
+
|
8
|
+
import https from 'https';
|
9
|
+
import fs from 'fs';
|
10
|
+
import path from 'path';
|
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.8.0';
|
20
|
+
|
21
|
+
console.log('📦 Setting up SandboxBox with bubblewrap...');
|
22
|
+
|
23
|
+
async function downloadBubblewrap() {
|
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 download on non-Linux platforms
|
32
|
+
if (process.platform !== 'linux') {
|
33
|
+
console.log('ℹ️ Skipping bubblewrap download on non-Linux platform');
|
34
|
+
console.log(' SandboxBox works on Linux only');
|
35
|
+
return;
|
36
|
+
}
|
37
|
+
|
38
|
+
// Try to use system bubblewrap first (fastest option)
|
39
|
+
try {
|
40
|
+
const systemBwrap = execSync('which bwrap', { encoding: 'utf8' }).trim();
|
41
|
+
if (systemBwrap && fs.existsSync(systemBwrap)) {
|
42
|
+
fs.copyFileSync(systemBwrap, binaryPath);
|
43
|
+
fs.chmodSync(binaryPath, 0o755);
|
44
|
+
console.log('✅ Using system bubblewrap:', systemBwrap);
|
45
|
+
return;
|
46
|
+
}
|
47
|
+
} catch (e) {
|
48
|
+
// System bwrap not found, continue with download
|
49
|
+
}
|
50
|
+
|
51
|
+
// Try to download pre-built binary
|
52
|
+
const arch = process.arch === 'x64' ? 'x86_64' : process.arch;
|
53
|
+
const binaryUrl = `https://github.com/containers/bubblewrap/releases/download/v${BWRAP_VERSION}/bubblewrap-${BWRAP_VERSION}.${arch}.tar.xz`;
|
54
|
+
|
55
|
+
try {
|
56
|
+
console.log('📥 Downloading bubblewrap binary...');
|
57
|
+
|
58
|
+
// Download with fallback
|
59
|
+
await new Promise((resolve, reject) => {
|
60
|
+
const file = fs.createWriteStream(path.join(BINARY_DIR, `bubblewrap-${BWRAP_VERSION}.tar.xz`));
|
61
|
+
|
62
|
+
https.get(binaryUrl, (response) => {
|
63
|
+
if (response.statusCode !== 200) {
|
64
|
+
reject(new Error(`HTTP ${response.statusCode}: ${response.statusMessage}`));
|
65
|
+
return;
|
66
|
+
}
|
67
|
+
|
68
|
+
response.pipe(file);
|
69
|
+
|
70
|
+
file.on('finish', () => {
|
71
|
+
file.close();
|
72
|
+
console.log('📦 Extracting bubblewrap...');
|
73
|
+
|
74
|
+
// Extract binary
|
75
|
+
try {
|
76
|
+
execSync(`tar -xf "${path.join(BINARY_DIR, `bubblewrap-${BWRAP_VERSION}.tar.xz`)}" -C "${BINARY_DIR}" --strip-components=1`, { stdio: 'inherit' });
|
77
|
+
|
78
|
+
// Move binary to expected location
|
79
|
+
const extractedBinary = path.join(BINARY_DIR, 'bin', 'bwrap');
|
80
|
+
if (fs.existsSync(extractedBinary)) {
|
81
|
+
fs.renameSync(extractedBinary, binaryPath);
|
82
|
+
} else {
|
83
|
+
// Try alternative extraction pattern
|
84
|
+
const altBinary = path.join(BINARY_DIR, 'bubblewrap-0.8.0', 'bwrap');
|
85
|
+
if (fs.existsSync(altBinary)) {
|
86
|
+
fs.renameSync(altBinary, binaryPath);
|
87
|
+
}
|
88
|
+
}
|
89
|
+
|
90
|
+
// Set executable permissions
|
91
|
+
fs.chmodSync(binaryPath, 0o755);
|
92
|
+
|
93
|
+
// Cleanup
|
94
|
+
fs.rmSync(path.join(BINARY_DIR, `bubblewrap-${BWRAP_VERSION}.tar.xz`), { force: true });
|
95
|
+
fs.rmSync(path.join(BINARY_DIR, 'bubblewrap-0.8.0'), { recursive: true, force: true });
|
96
|
+
|
97
|
+
console.log('✅ Bubblewrap downloaded successfully');
|
98
|
+
resolve();
|
99
|
+
} catch (extractError) {
|
100
|
+
reject(new Error(`Failed to extract: ${extractError.message}`));
|
101
|
+
}
|
102
|
+
});
|
103
|
+
}).on('error', reject);
|
104
|
+
});
|
105
|
+
|
106
|
+
} catch (downloadError) {
|
107
|
+
console.log('⚠️ Download failed, trying to compile from source...');
|
108
|
+
await compileBubblewrap(binaryPath);
|
109
|
+
}
|
110
|
+
}
|
111
|
+
|
112
|
+
async function compileBubblewrap(binaryPath) {
|
113
|
+
try {
|
114
|
+
console.log('🔨 Compiling bubblewrap from source...');
|
115
|
+
|
116
|
+
const tmpDir = fs.mkdtempSync(path.join(process.env.TMPDIR || '/tmp', 'bwrap-build-'));
|
117
|
+
|
118
|
+
try {
|
119
|
+
// Download source
|
120
|
+
execSync(`
|
121
|
+
cd "${tmpDir}" &&
|
122
|
+
wget -q https://github.com/containers/bubblewrap/releases/download/v${BWRAP_VERSION}/bubblewrap-${BWRAP_VERSION}.tar.xz &&
|
123
|
+
tar -xf bubblewrap-${BWRAP_VERSION}.tar.xz
|
124
|
+
`, { stdio: 'inherit' });
|
125
|
+
|
126
|
+
// Build dependencies check
|
127
|
+
try {
|
128
|
+
execSync('which gcc', { stdio: 'ignore' });
|
129
|
+
} catch (e) {
|
130
|
+
console.log('❌ GCC not found. Please install build-essential:');
|
131
|
+
console.log(' Ubuntu/Debian: sudo apt-get install build-essential');
|
132
|
+
console.log(' CentOS/RHEL: sudo yum groupinstall "Development Tools"');
|
133
|
+
console.log(' Or install bubblewrap system-wide: sudo apt-get install bubblewrap');
|
134
|
+
process.exit(1);
|
135
|
+
}
|
136
|
+
|
137
|
+
// Compile
|
138
|
+
execSync(`
|
139
|
+
cd "${tmpDir}/bubblewrap-${BWRAP_VERSION}" &&
|
140
|
+
./configure --prefix="${tmpDir}/install" &&
|
141
|
+
make -j$(nproc 2>/dev/null || echo 4) &&
|
142
|
+
make install
|
143
|
+
`, { stdio: 'inherit' });
|
144
|
+
|
145
|
+
// Copy binary
|
146
|
+
fs.copyFileSync(path.join(tmpDir, 'install', 'bin', 'bwrap'), binaryPath);
|
147
|
+
fs.chmodSync(binaryPath, 0o755);
|
148
|
+
|
149
|
+
console.log('✅ Bubblewrap compiled successfully');
|
150
|
+
|
151
|
+
} finally {
|
152
|
+
// Cleanup
|
153
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
154
|
+
}
|
155
|
+
|
156
|
+
} catch (compileError) {
|
157
|
+
console.log('❌ Failed to compile bubblewrap');
|
158
|
+
console.log('💡 Please install bubblewrap system-wide:');
|
159
|
+
console.log(' sudo apt-get install bubblewrap # Ubuntu/Debian');
|
160
|
+
console.log(' sudo apk add bubblewrap # Alpine');
|
161
|
+
console.log(' sudo yum install bubblewrap # CentOS/RHEL');
|
162
|
+
console.log('');
|
163
|
+
console.log('Then try installing SandboxBox again.');
|
164
|
+
process.exit(1);
|
165
|
+
}
|
166
|
+
}
|
167
|
+
|
168
|
+
// Run the download
|
169
|
+
downloadBubblewrap().catch(error => {
|
170
|
+
console.error('❌ Setup failed:', error.message);
|
171
|
+
process.exit(1);
|
172
|
+
});
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# Sample Dockerfile for SandboxBox
|
2
|
+
FROM alpine
|
3
|
+
|
4
|
+
# Install Node.js and test dependencies
|
5
|
+
RUN apk add --no-cache nodejs npm
|
6
|
+
|
7
|
+
# Set working directory
|
8
|
+
WORKDIR /app
|
9
|
+
|
10
|
+
# Copy package files (if they exist)
|
11
|
+
COPY package*.json ./
|
12
|
+
|
13
|
+
# Install dependencies (if package.json exists)
|
14
|
+
RUN if [ -f package.json ]; then npm install; fi
|
15
|
+
|
16
|
+
# Copy application code
|
17
|
+
COPY . .
|
18
|
+
|
19
|
+
# Default command - run tests or start app
|
20
|
+
CMD ["npm", "test"]
|