seabox 0.1.0-beta.1 → 0.1.0-beta.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/README.md +45 -23
- package/bin/seabox.js +1 -1
- package/lib/bootstrap.js +46 -4
- package/lib/bytenode-hack.js +56 -0
- package/lib/config.js +1 -0
- package/lib/manifest.js +9 -1
- package/package.json +6 -1
package/README.md
CHANGED
|
@@ -13,6 +13,11 @@ A reusable tool for building Node.js Single Executable Applications (SEA) with n
|
|
|
13
13
|
- Automatic code signature removal before injection
|
|
14
14
|
- Simple configuration via package.json
|
|
15
15
|
|
|
16
|
+
## Use case
|
|
17
|
+
This tooling was created as an alternative to pkg, which is unfortunatly deprecated, and where forks were running foul of virus checkers. By using node's SEA, the executables are directly from nodejs's distribution source, and built using node's native Single Executable Application solution. Unfortunatly this does mean native modules embedded within the exe cannot run directly and must be extracted to a location on the disk on first run - This tooling automates that process for you, while providing arbitrary asset embedding. Embedded assets are _not_ extracted and access to them is handled by intercepting require and fs.
|
|
18
|
+
|
|
19
|
+
Note: **V8 snapshot includes and embedds the original source**, this is currently a limitation of Node's SEA tooling as far as I can tell; thus the snapshot is only useful for faster startup.
|
|
20
|
+
|
|
16
21
|
## Installation
|
|
17
22
|
|
|
18
23
|
```bash
|
|
@@ -63,7 +68,8 @@ Add a `sea` configuration to your `package.json`:
|
|
|
63
68
|
- **useCodeCache**: Enable V8 code cache (default: false)
|
|
64
69
|
- **encryptAssets**: Enable encryption for assets (default: false)
|
|
65
70
|
- **encryptExclude**: Patterns to exclude from encryption (e.g., `['*.txt']`)
|
|
66
|
-
- **
|
|
71
|
+
- **rebuild**: Automatically rebuild native modules for the target platform before building the SEA (default: false)
|
|
72
|
+
- **cacheLocation**: Custom cache directory for extracted binaries (default: `'./.sea-cache'`). Supports environment variable expansion (e.g., `'%LOCALAPPDATA%\\myapp-cache'` on Windows or `'$HOME/.cache/myapp'` on Unix)
|
|
67
73
|
|
|
68
74
|
## Usage
|
|
69
75
|
|
|
@@ -94,8 +100,12 @@ npm run build:exe
|
|
|
94
100
|
# Build using package.json configuration
|
|
95
101
|
npx seabox
|
|
96
102
|
|
|
103
|
+
# Build using a standalone config file (alternative to package.json)
|
|
104
|
+
npx seabox --config sea-config.json
|
|
105
|
+
|
|
97
106
|
# Verbose output
|
|
98
107
|
npx seabox --verbose
|
|
108
|
+
|
|
99
109
|
```
|
|
100
110
|
|
|
101
111
|
### Programmatic API
|
|
@@ -126,7 +136,35 @@ Native modules (`.node`, `.dll`, `.so`, `.dylib`) are automatically:
|
|
|
126
136
|
- Integrity-checked using SHA-256 hashes
|
|
127
137
|
- Loaded via custom module resolution
|
|
128
138
|
|
|
129
|
-
Cache
|
|
139
|
+
### Cache Location
|
|
140
|
+
|
|
141
|
+
By default, binaries are extracted to: `./.sea-cache/<appname>/<version>-<platform>-<arch>`
|
|
142
|
+
|
|
143
|
+
You can customize the cache location in your configuration:
|
|
144
|
+
|
|
145
|
+
```json
|
|
146
|
+
{
|
|
147
|
+
"sea": {
|
|
148
|
+
"cacheLocation": "%LOCALAPPDATA%\\myapp-cache"
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
The cache location supports environment variable expansion:
|
|
154
|
+
- **Windows**: `%LOCALAPPDATA%`, `%APPDATA%`, `%TEMP%`, etc.
|
|
155
|
+
- **Unix/Linux/macOS**: `$HOME`, `$TMPDIR`, `${XDG_CACHE_HOME}`, etc.
|
|
156
|
+
|
|
157
|
+
**Override at runtime**: Set the `SEACACHE` environment variable to override the configured location:
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
# Windows
|
|
161
|
+
set SEACACHE=C:\custom\cache\path
|
|
162
|
+
myapp.exe
|
|
163
|
+
|
|
164
|
+
# Unix/Linux/macOS
|
|
165
|
+
export SEACACHE=/custom/cache/path
|
|
166
|
+
./myapp
|
|
167
|
+
```
|
|
130
168
|
|
|
131
169
|
## Native Module Rebuilding
|
|
132
170
|
|
|
@@ -169,33 +207,17 @@ seabox supports optional AES-256-GCM encryption of embedded assets to protect yo
|
|
|
169
207
|
1. **Build Time**: A random 256-bit encryption key is generated
|
|
170
208
|
2. **Asset Encryption**: Non-binary assets are encrypted using AES-256-GCM
|
|
171
209
|
3. **Key Embedding**: The encryption key is obfuscated and embedded in the bootstrap code
|
|
172
|
-
4. **
|
|
210
|
+
4. **Key Obfuscation**: the bootstrap and key code are obfuscated, but not removed
|
|
173
211
|
5. **Runtime Decryption**: Assets are transparently decrypted when accessed
|
|
174
212
|
|
|
175
|
-
###
|
|
213
|
+
### Considerations
|
|
176
214
|
|
|
177
215
|
- **Binary files** (`.node`, `.dll`, `.so`, `.dylib`) are **never encrypted** as they must be extracted as-is
|
|
178
216
|
- The manifest (`sea-manifest.json`) is **not encrypted** to allow bootstrap initialization
|
|
217
|
+
- **V8 snapshot includes the original source**, this is currently a limitation of Node's SEA.
|
|
179
218
|
- Encryption provides **obfuscation**, not cryptographic security against determined attackers
|
|
180
|
-
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
### Best Practices
|
|
184
|
-
|
|
185
|
-
```json
|
|
186
|
-
{
|
|
187
|
-
"sea": {
|
|
188
|
-
"encryptAssets": true,
|
|
189
|
-
"useSnapshot": true, // Strongly recommended with encryption
|
|
190
|
-
"assets": [
|
|
191
|
-
"./out/**/*",
|
|
192
|
-
"!**/*.md", // Exclude documentation
|
|
193
|
-
"!**/public/**", // Exclude public assets
|
|
194
|
-
"!**/LICENSE" // Exclude license files
|
|
195
|
-
]
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
```
|
|
219
|
+
- The bootloader code, that includes the encryption key, is obfuscated in the source embedded by Node's SEA
|
|
220
|
+
|
|
199
221
|
|
|
200
222
|
## Platform Support
|
|
201
223
|
|
package/bin/seabox.js
CHANGED
package/lib/bootstrap.js
CHANGED
|
@@ -202,18 +202,56 @@ function getAsset(assetKey, encoding) {
|
|
|
202
202
|
}
|
|
203
203
|
|
|
204
204
|
|
|
205
|
+
/**
|
|
206
|
+
* Resolve environment variables in a path string.
|
|
207
|
+
* Supports %VAR% on Windows and $VAR or ${VAR} on Unix-like systems.
|
|
208
|
+
* @param {string} pathStr - Path string that may contain environment variables
|
|
209
|
+
* @returns {string} - Path with environment variables expanded
|
|
210
|
+
*/
|
|
211
|
+
function resolveEnvVars(pathStr) {
|
|
212
|
+
if (!pathStr) return pathStr;
|
|
213
|
+
|
|
214
|
+
// Replace Windows-style %VAR%
|
|
215
|
+
let resolved = pathStr.replace(/%([^%]+)%/g, (match, varName) => {
|
|
216
|
+
return process.env[varName] || match;
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
// Replace Unix-style $VAR and ${VAR}
|
|
220
|
+
resolved = resolved.replace(/\$\{([^}]+)\}/g, (match, varName) => {
|
|
221
|
+
return process.env[varName] || match;
|
|
222
|
+
});
|
|
223
|
+
resolved = resolved.replace(/\$([A-Za-z_][A-Za-z0-9_]*)/g, (match, varName) => {
|
|
224
|
+
return process.env[varName] || match;
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
return resolved;
|
|
228
|
+
}
|
|
229
|
+
|
|
205
230
|
/**
|
|
206
231
|
* Get the extraction cache directory for this application.
|
|
207
232
|
* @param {string} appName
|
|
208
233
|
* @param {string} appVersion
|
|
209
234
|
* @param {string} platform
|
|
210
235
|
* @param {string} arch
|
|
236
|
+
* @param {string} [configuredCacheLocation] - Optional configured cache location from manifest
|
|
211
237
|
* @returns {string}
|
|
212
238
|
*/
|
|
213
|
-
function getExtractionCacheDir(appName, appVersion, platform, arch) {
|
|
239
|
+
function getExtractionCacheDir(appName, appVersion, platform, arch, configuredCacheLocation) {
|
|
240
|
+
// Priority: SEACACHE env var > configured location > default
|
|
214
241
|
if(process.env.SEACACHE) return process.env.SEACACHE;
|
|
215
|
-
|
|
216
|
-
|
|
242
|
+
|
|
243
|
+
if (configuredCacheLocation) {
|
|
244
|
+
// Resolve environment variables in the configured path
|
|
245
|
+
const resolvedBase = resolveEnvVars(configuredCacheLocation);
|
|
246
|
+
// Make relative paths absolute (relative to cwd)
|
|
247
|
+
const absoluteBase = path.isAbsolute(resolvedBase) ? resolvedBase : path.resolve(process.cwd(), resolvedBase);
|
|
248
|
+
return path.join(absoluteBase, appName, `${appVersion}-${platform}-${arch}`);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Default behavior
|
|
252
|
+
const defaultBase = './.sea-cache';
|
|
253
|
+
const absoluteBase = path.resolve(process.cwd(), defaultBase);
|
|
254
|
+
return path.join(absoluteBase, appName, `${appVersion}-${platform}-${arch}`);
|
|
217
255
|
}
|
|
218
256
|
|
|
219
257
|
/**
|
|
@@ -400,6 +438,9 @@ function bootstrap() {
|
|
|
400
438
|
console.log(` App: ${manifest.appName} v${manifest.appVersion}`);
|
|
401
439
|
console.log(` Platform: ${manifest.platform}-${manifest.arch}`);
|
|
402
440
|
console.log(` Binaries to extract: ${manifest.binaries.length}`);
|
|
441
|
+
if (manifest.cacheLocation) {
|
|
442
|
+
console.log(` Configured cache location: ${manifest.cacheLocation}`);
|
|
443
|
+
}
|
|
403
444
|
}
|
|
404
445
|
|
|
405
446
|
// Filter binaries for current platform
|
|
@@ -415,7 +456,8 @@ function bootstrap() {
|
|
|
415
456
|
manifest.appName,
|
|
416
457
|
manifest.appVersion,
|
|
417
458
|
manifest.platform,
|
|
418
|
-
manifest.arch
|
|
459
|
+
manifest.arch,
|
|
460
|
+
manifest.cacheLocation
|
|
419
461
|
);
|
|
420
462
|
|
|
421
463
|
const nativeModuleMap = {};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/*MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2018 Osama Abbas
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.*/
|
|
22
|
+
|
|
23
|
+
module.exports.fixBytecode = function (bytecodeBuffer) {
|
|
24
|
+
if (!isBuffer(bytecodeBuffer)) {
|
|
25
|
+
throw new Error('bytecodeBuffer must be a buffer object.');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const dummyBytecode = compileCode('"ಠ_ಠ"');
|
|
29
|
+
const version = parseFloat(process.version.slice(1, 5));
|
|
30
|
+
|
|
31
|
+
if (process.version.startsWith('v8.8') || process.version.startsWith('v8.9')) {
|
|
32
|
+
// Node is v8.8.x or v8.9.x
|
|
33
|
+
dummyBytecode.subarray(16, 20).copy(bytecodeBuffer, 16);
|
|
34
|
+
dummyBytecode.subarray(20, 24).copy(bytecodeBuffer, 20);
|
|
35
|
+
} else if (version >= 12 && version <= 23) {
|
|
36
|
+
dummyBytecode.subarray(12, 16).copy(bytecodeBuffer, 12);
|
|
37
|
+
} else {
|
|
38
|
+
dummyBytecode.subarray(12, 16).copy(bytecodeBuffer, 12);
|
|
39
|
+
dummyBytecode.subarray(16, 20).copy(bytecodeBuffer, 16);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
module.exports.readSourceHash = function (bytecodeBuffer) {
|
|
44
|
+
if (!isBuffer(bytecodeBuffer)) {
|
|
45
|
+
throw new Error('bytecodeBuffer must be a buffer object.');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (process.version.startsWith('v8.8') || process.version.startsWith('v8.9')) {
|
|
49
|
+
// Node is v8.8.x or v8.9.x
|
|
50
|
+
// eslint-disable-next-line no-return-assign
|
|
51
|
+
return bytecodeBuffer.subarray(12, 16).reduce((sum, number, power) => sum += number * Math.pow(256, power), 0);
|
|
52
|
+
} else {
|
|
53
|
+
// eslint-disable-next-line no-return-assign
|
|
54
|
+
return bytecodeBuffer.subarray(8, 12).reduce((sum, number, power) => sum += number * Math.pow(256, power), 0);
|
|
55
|
+
}
|
|
56
|
+
};
|
package/lib/config.js
CHANGED
|
@@ -22,6 +22,7 @@ const path = require('path');
|
|
|
22
22
|
* @property {string[]} [encryptExclude] - Asset patterns to exclude from encryption
|
|
23
23
|
* @property {boolean} [rebuild] - Automatically rebuild native modules for target platform (default: false)
|
|
24
24
|
* @property {boolean} [verbose] - Enable diagnostic logging
|
|
25
|
+
* @property {string} [cacheLocation] - Cache directory for extracted binaries (default: './.sea-cache', supports env vars like '%LOCALAPPDATA%\\path')
|
|
25
26
|
*/
|
|
26
27
|
|
|
27
28
|
/**
|
package/lib/manifest.js
CHANGED
|
@@ -23,6 +23,7 @@ const path = require('path');
|
|
|
23
23
|
* @property {string} arch - Target architecture
|
|
24
24
|
* @property {BinaryManifestEntry[]} binaries - Binary extraction rules
|
|
25
25
|
* @property {string[]} allAssetKeys - All embedded asset keys
|
|
26
|
+
* @property {string} [cacheLocation] - Configured cache location (optional)
|
|
26
27
|
*/
|
|
27
28
|
|
|
28
29
|
/**
|
|
@@ -48,7 +49,7 @@ function generateManifest(assets, config, targetPlatform, targetArch) {
|
|
|
48
49
|
};
|
|
49
50
|
});
|
|
50
51
|
|
|
51
|
-
|
|
52
|
+
const manifest = {
|
|
52
53
|
appName: config._packageName || 'app',
|
|
53
54
|
appVersion: config._packageVersion || '1.0.0',
|
|
54
55
|
platform: targetPlatform,
|
|
@@ -56,6 +57,13 @@ function generateManifest(assets, config, targetPlatform, targetArch) {
|
|
|
56
57
|
binaries,
|
|
57
58
|
allAssetKeys: assets.map(a => a.assetKey)
|
|
58
59
|
};
|
|
60
|
+
|
|
61
|
+
// Include cacheLocation if configured
|
|
62
|
+
if (config.cacheLocation) {
|
|
63
|
+
manifest.cacheLocation = config.cacheLocation;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return manifest;
|
|
59
67
|
}
|
|
60
68
|
|
|
61
69
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "seabox",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.2",
|
|
4
4
|
"description": "Node.js Single Executable Application (SEA) builder tool with native and library extraction",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -28,6 +28,11 @@
|
|
|
28
28
|
],
|
|
29
29
|
"author": "Meirion Hughes",
|
|
30
30
|
"license": "MIT",
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "https://github.com/MeirionHughes/seabox"
|
|
34
|
+
},
|
|
35
|
+
"homepage": "https://github.com/MeirionHughes/seabox",
|
|
31
36
|
"dependencies": {
|
|
32
37
|
"adm-zip": "^0.5.16",
|
|
33
38
|
"glob": "^10.0.0",
|