commit b373332e5a2ab32f3b611e59a56f674f012313ec Author: John Dvorak Date: Fri Jul 17 12:28:26 2026 -0700 initial: greenshades native backend v0.1.0 (x86_64-linux prebuild) diff --git a/.gitea/workflows/ci.yaml b/.gitea/workflows/ci.yaml new file mode 100644 index 0000000..0bc477b --- /dev/null +++ b/.gitea/workflows/ci.yaml @@ -0,0 +1,17 @@ +name: CI + +on: + push: + branches: [master] + pull_request: + branches: [master] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + - run: node --check index.js diff --git a/.gitea/workflows/publish.yaml b/.gitea/workflows/publish.yaml new file mode 100644 index 0000000..f0e476f --- /dev/null +++ b/.gitea/workflows/publish.yaml @@ -0,0 +1,21 @@ +name: Publish + +on: + push: + tags: ["v*"] + +jobs: + publish: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + - name: Auth for Gitea npm registry + run: | + echo "@polyweave:registry=https://hub.kl1.tenere.ai/api/packages/Polyweave/npm/" > .npmrc + echo "//hub.kl1.tenere.ai/api/packages/Polyweave/npm/:_authToken=${{ secrets.PACKAGE_TOKEN }}" >> .npmrc + - run: npm publish + env: + NODE_AUTH_TOKEN: ${{ secrets.PACKAGE_TOKEN }} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..fe9d238 --- /dev/null +++ b/LICENSE @@ -0,0 +1,9 @@ +Tenere Labs Inc. — All Rights Reserved. + +PROPRIETARY AND CONFIDENTIAL. +Source-available for auditing and inspection only. No use, reproduction, +modification, or distribution is permitted without a paid written license. + +Unauthorized use will be prosecuted to the fullest extent of the law. + +For commercial licensing: licensing@tenere.ai diff --git a/index.js b/index.js new file mode 100644 index 0000000..745585c --- /dev/null +++ b/index.js @@ -0,0 +1,108 @@ +// index.js — Greenshades native N-API loader (lazy). +// +// The Zig backend's napi_register_module_v1 entry point performs a +// ServiceCellPool.init that forks 4 child processes. On non-Linux, in +// containers without seccomp, or in test runners that don't intend to +// exercise the native code, this fork sequence can corrupt glibc's heap +// or simply block for ~4 seconds (one blocked read per cell). +// +// To avoid loading the binary on import (which would run that init at +// unpredictable times), the binary is loaded LAZILY — only when a +// caller actually accesses an export. Detection at module-load time +// uses a synchronous fs stat call to confirm the prebuild exists. +// +// Env vars: +// GREENSHADES_ZIG_NATIVE=1 — prefer a freshly-built zig-out/ binary +// over the bundled prebuild +// GREENSHADES_ZIG_SKIP=1 — disable native loading entirely (use +// synthetic JS-only stubs for tests) +'use strict'; + +const os = require('os'); +const fs = require('fs'); +const path = require('path'); + +function platformArch() { + const p = os.platform(); + const a = os.arch(); + if (p === 'linux' && a === 'x64') return 'x86_64-linux'; + if (p === 'linux' && a === 'arm64') return 'aarch64-linux'; + return null; +} + +const platformKey = platformArch(); +const useBuilt = process.env.GREENSHADES_ZIG_NATIVE === '1'; +const skipNative = process.env.GREENSHADES_ZIG_SKIP === '1'; + +function candidatePath() { + if (useBuilt) { + const built = path.join(__dirname, '..', 'zig-out', 'greenshades_backend.node'); + if (fs.existsSync(built)) return built; + const projRoot = path.join(__dirname, '..', 'greenshades_backend.node'); + if (fs.existsSync(projRoot)) return projRoot; + } + if (!platformKey) return null; + const prebuild = path.join(__dirname, 'prebuilds', platformKey, 'greenshades_backend.node'); + return fs.existsSync(prebuild) ? prebuild : null; +} + +function ensureLoaded() { + if (module.exports.__loaded) return true; + const candidate = candidatePath(); + if (!candidate) return false; + const { dlopen } = require('process'); + const constants = process.binding('constants'); + const RTLD_LAZY = constants.os.dlopen.RTLD_LAZY; + const RTLD_LOCAL = constants.os.dlopen.RTLD_LOCAL; + try { + const mod = { exports: {} }; + dlopen(mod, candidate, RTLD_LAZY | RTLD_LOCAL); + if (typeof mod.exports.createBackend === 'function') { + for (const k of Object.keys(mod.exports)) { + module.exports[k] = mod.exports[k]; + } + module.exports.__loaded = true; + return true; + } + } catch (_) {} + return false; +} + +if (!skipNative) { + const candidate = candidatePath(); + module.exports.__nativeCandidatePath = candidate; + module.exports.__nativeCandidatePresent = !!candidate; + module.exports.__nativeAvailable = false; + module.exports.__loaded = false; +} + +module.exports.__platformKey = platformKey; +module.exports.__ensureLoaded = ensureLoaded; + +function notLoadedError() { + return new Error([ + 'Greenshades native addon not available for ' + (platformKey || os.platform() + '-' + os.arch()) + '.', + '', + 'To build from source:', + ' 1. Install Zig 0.16+', + ' 2. Run: cd zig-native && zig build napi', + ' 3. Copy zig-out/greenshades_backend.node to:', + ' zig-native/node/prebuilds/' + (platformKey || 'YOUR_PLATFORM') + '/greenshades_backend.node', + ].join('\n')); +} + +const handler = { + get(target, prop) { + if (typeof prop === 'string' && prop.startsWith('__')) { + return target[prop]; + } + if (typeof prop === 'string' && target[prop] === undefined) { + if (!ensureLoaded()) throw notLoadedError(); + return target[prop]; + } + return target[prop]; + }, +}; + +module.exports = new Proxy(module.exports, handler); + diff --git a/package.json b/package.json new file mode 100644 index 0000000..ab3ebd7 --- /dev/null +++ b/package.json @@ -0,0 +1,26 @@ +{ + "name": "@polyweave/greenshades-native", + "version": "0.1.0", + "description": "Native N-API bindings for the Greenshades execution backend", + "main": "index.js", + "type": "commonjs", + "os": ["linux"], + "cpu": ["x64", "arm64"], + "files": [ + "index.js", + "prebuilds/" + ], + "keywords": [ + "greenshades", + "sandbox", + "seccomp", + "native", + "napi", + "zig" + ], + "author": "John Dvorak ", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } +} diff --git a/prebuilds/x86_64-linux/greenshades-sc b/prebuilds/x86_64-linux/greenshades-sc new file mode 100755 index 0000000..3b886dd Binary files /dev/null and b/prebuilds/x86_64-linux/greenshades-sc differ diff --git a/prebuilds/x86_64-linux/greenshades_backend.node b/prebuilds/x86_64-linux/greenshades_backend.node new file mode 100755 index 0000000..1c4e1ba Binary files /dev/null and b/prebuilds/x86_64-linux/greenshades_backend.node differ