@@ -0,0 +1,421 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Build standalone distributable binaries for the BridgeSwarm native host.
|
||||
*
|
||||
* Uses bare-pack + bare-build (same approach as holesail-browser) to produce
|
||||
* self-contained executables with no Node/npm required on the end-user machine.
|
||||
*
|
||||
* Usage:
|
||||
* node scripts/build-distributable.js # current host only
|
||||
* node scripts/build-distributable.js --all # all platforms
|
||||
* node scripts/build-distributable.js --host darwin-arm64 --host linux-x64
|
||||
* node scripts/build-distributable.js --package # also create .zip archives
|
||||
*
|
||||
* Output under releases/:
|
||||
* bridge-swarm-host-darwin-arm64.zip, …-darwin-x64.zip,
|
||||
* …-linux-arm64.zip, …-linux-x64.zip, …-win32-x64.zip
|
||||
*/
|
||||
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const os = require('os');
|
||||
const { execSync } = require('child_process');
|
||||
const { pathToFileURL } = require('url');
|
||||
|
||||
const ROOT = path.join(__dirname, '..');
|
||||
const NATIVE_HOST_DIR = path.join(ROOT, 'native-host');
|
||||
const RELEASES_DIR = path.join(ROOT, 'releases');
|
||||
const ENTRY = path.join(NATIVE_HOST_DIR, 'index.mjs');
|
||||
const HOST_NAME = 'bridge-swarm-host';
|
||||
|
||||
const ALL_HOSTS = [
|
||||
'darwin-arm64',
|
||||
'darwin-x64',
|
||||
'linux-arm64',
|
||||
'linux-x64',
|
||||
'win32-x64',
|
||||
];
|
||||
|
||||
const BUILTINS = [];
|
||||
|
||||
function getCurrentHost() {
|
||||
const platform = os.platform();
|
||||
const arch = os.arch() === 'arm64' ? 'arm64' : 'x64';
|
||||
return `${platform}-${arch}`;
|
||||
}
|
||||
|
||||
function parseArgs() {
|
||||
const args = process.argv.slice(2);
|
||||
const hosts = [];
|
||||
let all = false;
|
||||
let doPackage = false;
|
||||
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
if (args[i] === '--all') all = true;
|
||||
else if (args[i] === '--package') doPackage = true;
|
||||
else if (args[i] === '--host' && args[i + 1]) hosts.push(args[++i]);
|
||||
}
|
||||
|
||||
if (all) return { hosts: ALL_HOSTS, doPackage };
|
||||
if (hosts.length > 0) return { hosts, doPackage };
|
||||
return { hosts: [getCurrentHost()], doPackage };
|
||||
}
|
||||
|
||||
function getPlatformModule(host) {
|
||||
const bareBuildDir = path.dirname(require.resolve('bare-build'));
|
||||
switch (host) {
|
||||
case 'darwin-arm64':
|
||||
case 'darwin-x64':
|
||||
return require(path.join(bareBuildDir, 'lib/platform/apple'));
|
||||
case 'linux-arm64':
|
||||
case 'linux-x64':
|
||||
return require(path.join(bareBuildDir, 'lib/platform/linux'));
|
||||
case 'win32-x64':
|
||||
case 'win32-arm64':
|
||||
return require(path.join(bareBuildDir, 'lib/platform/windows'));
|
||||
default:
|
||||
throw new Error(`Unknown host '${host}'`);
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeBundleKeysToWindows(bundle) {
|
||||
const next = new bundle.constructor();
|
||||
next._id = bundle._id;
|
||||
const keyMap = {};
|
||||
for (const key of bundle.keys()) {
|
||||
const newKey = key.replace(/\//g, '\\');
|
||||
keyMap[key] = newKey;
|
||||
const content = bundle.read(key);
|
||||
const mode = bundle.mode(key);
|
||||
const opts = { mode };
|
||||
if (key === bundle.main) opts.main = true;
|
||||
if (bundle.addons && bundle.addons.includes(key)) opts.addon = true;
|
||||
if (bundle.assets && bundle.assets.includes(key)) opts.asset = true;
|
||||
const res = bundle.resolutions && bundle.resolutions[key];
|
||||
if (res) opts.imports = transformResolutionKeys(res, keyMap);
|
||||
next.write(newKey, content, opts);
|
||||
}
|
||||
for (const [alias, key] of Object.entries(bundle.imports || {})) {
|
||||
next._imports[alias] = keyMap[key] ?? key.replace(/\//g, '\\');
|
||||
}
|
||||
return next;
|
||||
}
|
||||
|
||||
function transformResolutionKeys(obj, keyMap) {
|
||||
if (typeof obj === 'string') return keyMap[obj] ?? obj.replace(/\//g, '\\');
|
||||
if (obj && typeof obj === 'object' && !Buffer.isBuffer(obj)) {
|
||||
const out = {};
|
||||
for (const [k, v] of Object.entries(obj)) {
|
||||
out[k] = transformResolutionKeys(v, keyMap);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
function patchBareBuildSignForLinux() {
|
||||
if (os.platform() === 'darwin') return;
|
||||
try {
|
||||
execSync('which codesign', { stdio: 'ignore' });
|
||||
return;
|
||||
} catch (_) {}
|
||||
|
||||
const bareBuildDir = path.dirname(require.resolve('bare-build'));
|
||||
const signPath = path.join(bareBuildDir, 'lib/platform/apple/sign.js');
|
||||
if (!fs.existsSync(signPath)) return;
|
||||
const current = fs.readFileSync(signPath, 'utf8');
|
||||
if (current.includes('PATCHED_NO_CODESIGN')) return;
|
||||
fs.writeFileSync(
|
||||
signPath,
|
||||
`// PATCHED_NO_CODESIGN: codesign not available on this platform (Linux cross-build)
|
||||
module.exports = async function sign() {}
|
||||
`
|
||||
);
|
||||
console.log(' Patched bare-build/apple/sign.js → no-op (codesign not available on Linux)');
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure .json entries are valid and add runtime.bundle pathname variants
|
||||
* (needed so the embedded Bare runtime can resolve package.json files).
|
||||
*/
|
||||
function patchBundle(bundle) {
|
||||
const bundleKeys = typeof bundle.keys === 'function' ? [...bundle.keys()] : Object.keys(bundle.files || {});
|
||||
function resolveKey(pathSuffix) {
|
||||
const normalized = pathSuffix.replace(/^\/+/, '').replace(/\\/g, '/');
|
||||
const withSlash = '/' + normalized;
|
||||
if (bundleKeys.includes(withSlash)) return withSlash;
|
||||
if (bundleKeys.includes(normalized)) return normalized;
|
||||
return withSlash;
|
||||
}
|
||||
|
||||
function resolveJsonDiskPath(keyNorm) {
|
||||
const inNativeHost = path.join(NATIVE_HOST_DIR, keyNorm);
|
||||
if (fs.existsSync(inNativeHost)) return inNativeHost;
|
||||
if (keyNorm.startsWith('node_modules' + path.sep) || keyNorm.startsWith('node_modules/')) {
|
||||
const inRoot = path.join(ROOT, keyNorm.replace(/\//g, path.sep));
|
||||
if (fs.existsSync(inRoot)) return inRoot;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
let jsonFixed = 0;
|
||||
let keysToProcess = typeof bundle.keys === 'function' ? [...bundle.keys()] : Object.keys(bundle.files || {});
|
||||
for (const key of keysToProcess) {
|
||||
if (!key.endsWith('.json')) continue;
|
||||
let content = bundle.read(key);
|
||||
if (!content || content.length === 0) {
|
||||
const altKey = key.startsWith('/') ? key.slice(1) : '/' + key.replace(/^\/+/, '');
|
||||
content = bundle.read(altKey);
|
||||
if (content && content.length > 0) bundle.write(key, content);
|
||||
}
|
||||
const isEmpty = !content || content.length === 0;
|
||||
const invalidJson =
|
||||
content &&
|
||||
content.length > 0 &&
|
||||
(() => {
|
||||
try {
|
||||
JSON.parse(content.toString());
|
||||
return false;
|
||||
} catch (_) {
|
||||
return true;
|
||||
}
|
||||
})();
|
||||
if (isEmpty || invalidJson) {
|
||||
const keyNorm = key.replace(/^\/+/, '').replace(/\//g, path.sep);
|
||||
const diskPath = resolveJsonDiskPath(keyNorm);
|
||||
if (diskPath) {
|
||||
content = fs.readFileSync(diskPath);
|
||||
bundle.write(key, content);
|
||||
jsonFixed++;
|
||||
}
|
||||
}
|
||||
if (content && content.length > 0) {
|
||||
const keyNoLead = key.replace(/^\/+/, '');
|
||||
const prefixSlash = 'runtime.bundle/' + keyNoLead;
|
||||
const prefixLead = '/runtime.bundle/' + keyNoLead;
|
||||
if (prefixSlash !== key) bundle.write(prefixSlash, content);
|
||||
if (prefixLead !== key) bundle.write(prefixLead, content);
|
||||
}
|
||||
}
|
||||
|
||||
// Touch resolveKey so unused-lint tooling doesn't complain if tree-shaken later
|
||||
void resolveKey;
|
||||
|
||||
if (jsonFixed > 0) console.log(` Patched ${jsonFixed} empty/invalid .json entries`);
|
||||
console.log(' Added runtime.bundle pathname variants for .json entries');
|
||||
}
|
||||
|
||||
function getHostFromBuiltPath(filePath, platformHosts = null) {
|
||||
const normalized = path.relative(RELEASES_DIR, filePath).replace(/\\/g, '/');
|
||||
const lower = normalized.toLowerCase();
|
||||
if (lower.endsWith('.exe') || lower.includes('win32-x64')) return 'win32-x64';
|
||||
if (lower.includes('linux-arm64')) return 'linux-arm64';
|
||||
if (lower.includes('linux-x64')) return 'linux-x64';
|
||||
if (lower.includes('darwin-arm64')) return 'darwin-arm64';
|
||||
if (lower.includes('darwin-x64')) return 'darwin-x64';
|
||||
if (platformHosts && platformHosts.length > 0) {
|
||||
if (lower.includes('arm64/') || lower.includes('aarch64/')) {
|
||||
const arm = platformHosts.find((h) => h.includes('arm64') || h.includes('aarch64'));
|
||||
if (arm) return arm;
|
||||
}
|
||||
if (lower.includes('x86_64/')) {
|
||||
const x64 = platformHosts.find((h) => h.includes('x64'));
|
||||
if (x64) return x64;
|
||||
}
|
||||
}
|
||||
if (lower.includes('aarch64/')) return 'darwin-arm64';
|
||||
if (lower.includes('x86_64/')) return 'darwin-x64';
|
||||
if (lower.includes('arm64/')) return 'linux-arm64';
|
||||
if (normalized === HOST_NAME || normalized.startsWith(HOST_NAME + '/')) return 'darwin-arm64';
|
||||
return null;
|
||||
}
|
||||
|
||||
async function createZipArchives(builtEntries) {
|
||||
let archiver;
|
||||
try {
|
||||
archiver = require('archiver');
|
||||
} catch {
|
||||
console.warn(' Skipping zip archives: archiver not installed');
|
||||
return;
|
||||
}
|
||||
|
||||
const byHost = new Map();
|
||||
const hostOrder = ['darwin-arm64', 'darwin-x64', 'linux-arm64', 'linux-x64', 'win32-x64'];
|
||||
let fallbackIdx = 0;
|
||||
|
||||
for (const { file, platformHosts } of builtEntries) {
|
||||
const rel = path.relative(RELEASES_DIR, file).replace(/\\/g, '/').toLowerCase();
|
||||
const hasArchSubdir =
|
||||
rel.includes('aarch64/') ||
|
||||
rel.includes('x86_64/') ||
|
||||
rel.includes('arm64/') ||
|
||||
rel.includes('linux-') ||
|
||||
rel.includes('win32') ||
|
||||
rel.endsWith('.exe');
|
||||
if (!hasArchSubdir && platformHosts && platformHosts.length > 1) {
|
||||
for (const h of platformHosts) {
|
||||
if (!byHost.has(h)) byHost.set(h, file);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
let host = getHostFromBuiltPath(file, platformHosts);
|
||||
if (!host) host = hostOrder[fallbackIdx++] || 'unknown';
|
||||
const isArchSpecific =
|
||||
rel.includes('/') &&
|
||||
(rel.includes('aarch64') ||
|
||||
rel.includes('x86_64') ||
|
||||
rel.includes('arm64') ||
|
||||
rel.includes('linux-') ||
|
||||
rel.includes('win32') ||
|
||||
rel.endsWith('.exe'));
|
||||
const current = byHost.get(host);
|
||||
const currentRel = current ? path.relative(RELEASES_DIR, current).replace(/\\/g, '/').toLowerCase() : '';
|
||||
if (!current || (isArchSpecific && !currentRel.includes('/'))) {
|
||||
byHost.set(host, file);
|
||||
}
|
||||
}
|
||||
|
||||
for (const [host, file] of byHost) {
|
||||
const zipName = `${HOST_NAME}-${host}.zip`;
|
||||
const zipPath = path.join(RELEASES_DIR, zipName);
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
const output = fs.createWriteStream(zipPath);
|
||||
const archive = archiver('zip', { zlib: { level: 9 } });
|
||||
output.on('close', resolve);
|
||||
archive.on('error', reject);
|
||||
archive.pipe(output);
|
||||
if (fs.statSync(file).isDirectory()) {
|
||||
archive.directory(file, false);
|
||||
} else {
|
||||
archive.file(file, { name: path.basename(file) });
|
||||
}
|
||||
archive.finalize();
|
||||
});
|
||||
|
||||
console.log(' Packaged:', zipName);
|
||||
}
|
||||
|
||||
const missing = hostOrder.filter((h) => !byHost.has(h));
|
||||
if (missing.length > 0) {
|
||||
console.warn(' Warning: no build output for:', missing.join(', '));
|
||||
console.warn(' Run on Linux (e.g. CI) with --all --package to get all platform zips.');
|
||||
}
|
||||
}
|
||||
|
||||
async function build(hosts, doPackage) {
|
||||
patchBareBuildSignForLinux();
|
||||
|
||||
// Ensure HRPC spec is mirrored into native-host before packing
|
||||
console.log('Building HRPC spec...');
|
||||
execSync('npm run build:hrpc', { cwd: ROOT, stdio: 'inherit' });
|
||||
|
||||
if (!fs.existsSync(path.join(NATIVE_HOST_DIR, 'node_modules'))) {
|
||||
console.log('Installing native-host dependencies...');
|
||||
execSync('npm install', { cwd: NATIVE_HOST_DIR, stdio: 'inherit' });
|
||||
}
|
||||
|
||||
if (!fs.existsSync(path.join(NATIVE_HOST_DIR, 'spec', 'hrpc', 'index.js'))) {
|
||||
throw new Error('native-host/spec/hrpc missing after build:hrpc');
|
||||
}
|
||||
|
||||
fs.mkdirSync(RELEASES_DIR, { recursive: true });
|
||||
|
||||
const pack = require('bare-pack');
|
||||
const { readModule, listPrefix } = require('bare-pack/fs');
|
||||
const traverse = require('bare-module-traverse');
|
||||
const bundleId = require('bare-bundle-id');
|
||||
|
||||
const pkg = require(path.join(NATIVE_HOST_DIR, 'package.json'));
|
||||
|
||||
console.log(`\nBuilding ${HOST_NAME} v${pkg.version}`);
|
||||
console.log(`Targets: ${hosts.join(', ')}`);
|
||||
console.log(`Entry: ${ENTRY}`);
|
||||
console.log(`Output: ${RELEASES_DIR}\n`);
|
||||
|
||||
const unixHosts = hosts.filter((h) => !h.startsWith('win32'));
|
||||
const winHosts = hosts.filter((h) => h.startsWith('win32'));
|
||||
const hasWindows = winHosts.length > 0;
|
||||
const hasUnix = unixHosts.length > 0;
|
||||
|
||||
const built = [];
|
||||
const builtEntries = [];
|
||||
const platformLabels = new Map();
|
||||
platformLabels.set(getPlatformModule('darwin-arm64'), 'Apple (darwin)');
|
||||
platformLabels.set(getPlatformModule('linux-arm64'), 'Linux');
|
||||
platformLabels.set(getPlatformModule('win32-x64'), 'Windows');
|
||||
|
||||
async function buildAndEmit(bundleHosts, platformHostsList, normalizeForWindows) {
|
||||
if (bundleHosts.length === 0) return;
|
||||
console.log(' Bundling module graph' + (normalizeForWindows ? ' (Windows bundle)' : '') + '...');
|
||||
let bundle = await pack(
|
||||
pathToFileURL(ENTRY),
|
||||
{
|
||||
hosts: bundleHosts,
|
||||
linked: false,
|
||||
resolve: traverse.resolve.bare,
|
||||
builtins: BUILTINS,
|
||||
},
|
||||
readModule,
|
||||
listPrefix
|
||||
);
|
||||
bundle = bundle.unmount(pathToFileURL(NATIVE_HOST_DIR + '/'));
|
||||
patchBundle(bundle);
|
||||
if (normalizeForWindows) {
|
||||
bundle = normalizeBundleKeysToWindows(bundle);
|
||||
console.log(' Normalized bundle keys to Windows path form');
|
||||
}
|
||||
bundle.id = bundleId(bundle).toString('hex');
|
||||
console.log(` Bundle size: ${(bundle.toBuffer().length / 1024 / 1024).toFixed(1)} MB`);
|
||||
|
||||
const groups = new Map();
|
||||
for (const h of platformHostsList) {
|
||||
const platform = getPlatformModule(h);
|
||||
if (!groups.has(platform)) groups.set(platform, []);
|
||||
groups.get(platform).push(h);
|
||||
}
|
||||
for (const [platform, platformHosts] of groups) {
|
||||
const label = platformLabels.get(platform) || 'Unknown';
|
||||
console.log(` Building ${label} (${platformHosts.join(', ')})...`);
|
||||
let count = 0;
|
||||
for await (const file of platform(NATIVE_HOST_DIR, bundle, null, {
|
||||
name: HOST_NAME,
|
||||
version: pkg.version,
|
||||
description: pkg.description,
|
||||
hosts: platformHosts,
|
||||
out: RELEASES_DIR,
|
||||
standalone: true,
|
||||
})) {
|
||||
console.log(' Built:', path.relative(ROOT, file));
|
||||
built.push(file);
|
||||
builtEntries.push({ file, platformHosts });
|
||||
count++;
|
||||
}
|
||||
console.log(` -> ${count} artifact(s)`);
|
||||
}
|
||||
}
|
||||
|
||||
if (hasWindows && hasUnix) {
|
||||
await buildAndEmit(unixHosts, unixHosts, false);
|
||||
await buildAndEmit(winHosts, winHosts, true);
|
||||
} else if (hasWindows) {
|
||||
await buildAndEmit(winHosts, winHosts, true);
|
||||
} else {
|
||||
await buildAndEmit(hosts, hosts, false);
|
||||
}
|
||||
|
||||
if (doPackage) {
|
||||
await createZipArchives(builtEntries);
|
||||
}
|
||||
|
||||
console.log('\nDone.');
|
||||
return built;
|
||||
}
|
||||
|
||||
const { hosts, doPackage } = parseArgs();
|
||||
|
||||
build(hosts, doPackage).catch((err) => {
|
||||
console.error('\nBuild failed:', err.message || err);
|
||||
if (err.cause) console.error('Cause:', err.cause);
|
||||
process.exitCode = 1;
|
||||
});
|
||||
+22
-13
@@ -10,39 +10,48 @@ const { execSync } = require('child_process');
|
||||
|
||||
const nativeHostDir = path.join(__dirname, '..', 'native-host');
|
||||
const launcherPath = path.join(nativeHostDir, 'bridge-swarm-host');
|
||||
|
||||
// Find node path
|
||||
const nodeBin = process.execPath;
|
||||
|
||||
// Try to find bare - check common locations
|
||||
let bareBin = null;
|
||||
try {
|
||||
bareBin = execSync('which bare', { encoding: 'utf8' }).trim();
|
||||
} catch (e) {}
|
||||
function isExecutable(p) {
|
||||
try {
|
||||
fs.accessSync(p, fs.constants.X_OK);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Prefer the Bare package installed with native-host (pinned runtime).
|
||||
let bareBin = path.join(nativeHostDir, 'node_modules', 'bare', 'bin', 'bare');
|
||||
if (!isExecutable(bareBin)) {
|
||||
bareBin = null;
|
||||
try {
|
||||
bareBin = execSync('which bare', { encoding: 'utf8' }).trim();
|
||||
} catch {}
|
||||
}
|
||||
|
||||
if (!bareBin) {
|
||||
// Try common paths on macOS
|
||||
for (const p of ['/opt/homebrew/bin/bare', '/usr/local/bin/bare']) {
|
||||
try {
|
||||
fs.accessSync(p, fs.constants.X_OK);
|
||||
if (isExecutable(p)) {
|
||||
bareBin = p;
|
||||
break;
|
||||
} catch (e) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!bareBin) {
|
||||
// Fall back to node's sibling
|
||||
bareBin = nodeBin.replace(/node$/, 'bare');
|
||||
}
|
||||
|
||||
// bare's npm bin is a Node script that spawns bare-runtime
|
||||
const launcherContent = `#!/usr/bin/env bash
|
||||
DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
exec "${bareBin}" "$DIR/index.mjs" "$@"
|
||||
exec "${nodeBin}" "${bareBin}" "$DIR/index.mjs" "$@"
|
||||
`;
|
||||
|
||||
fs.writeFileSync(launcherPath, launcherContent);
|
||||
fs.chmodSync(launcherPath, '755');
|
||||
|
||||
console.log('Native host launcher generated at:', launcherPath);
|
||||
console.log('Using node:', nodeBin);
|
||||
console.log('Using bare:', bareBin);
|
||||
|
||||
+15
-1
@@ -106,4 +106,18 @@ rpcNs.register({
|
||||
|
||||
HRPCBuilder.toDisk(builder);
|
||||
|
||||
console.log('hrpc spec built: spec/hyperschema/, spec/hrpc/');
|
||||
// Mirror into native-host so bare-pack / distributable builds can require('./spec/hrpc')
|
||||
const HOST_SPEC = path.join(REPO_ROOT, 'native-host', 'spec');
|
||||
function copyDir(src, dest) {
|
||||
fs.mkdirSync(dest, { recursive: true });
|
||||
for (const name of fs.readdirSync(src)) {
|
||||
const from = path.join(src, name);
|
||||
const to = path.join(dest, name);
|
||||
if (fs.statSync(from).isDirectory()) copyDir(from, to);
|
||||
else fs.copyFileSync(from, to);
|
||||
}
|
||||
}
|
||||
copyDir(SCHEMA_DIR, path.join(HOST_SPEC, 'hyperschema'));
|
||||
copyDir(HRPC_DIR, path.join(HOST_SPEC, 'hrpc'));
|
||||
|
||||
console.log('hrpc spec built: spec/hyperschema/, spec/hrpc/, native-host/spec/');
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
# BridgeSwarm — install from a local clone (dev / from-source).
|
||||
# For end users prefer: irm .../scripts/install.ps1 | iex (release artifacts)
|
||||
$ErrorActionPreference = "Stop"
|
||||
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
$RepoRoot = (Resolve-Path (Join-Path $ScriptDir "..")).Path
|
||||
|
||||
Write-Host "BridgeSwarm – Install from source" -ForegroundColor Cyan
|
||||
Write-Host "================================="
|
||||
|
||||
if (-not (Get-Command node -ErrorAction SilentlyContinue)) {
|
||||
Write-Host "Error: Node.js is required. Install from https://nodejs.org" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "1. Installing native host..."
|
||||
$HostDir = Join-Path $RepoRoot "native-host"
|
||||
Set-Location $HostDir
|
||||
npm install --no-fund --no-audit 2>$null
|
||||
if ($LASTEXITCODE -ne 0) { npm install }
|
||||
|
||||
Set-Location $RepoRoot
|
||||
npm install --no-fund --no-audit 2>$null
|
||||
if ($LASTEXITCODE -ne 0) { npm install }
|
||||
npm run build
|
||||
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
||||
|
||||
$nodeCmd = Get-Command node -ErrorAction SilentlyContinue
|
||||
$nodePath = if ($nodeCmd) { $nodeCmd.Source } else { "node" }
|
||||
$localBare = Join-Path $HostDir "node_modules\bare\bin\bare"
|
||||
$bareCmd = Get-Command bare -ErrorAction SilentlyContinue
|
||||
$barePath = if (Test-Path $localBare) { $localBare } elseif ($bareCmd) { $bareCmd.Source } else { "bare" }
|
||||
|
||||
$batContent = "@echo off`r`nset `"DIR=%~dp0`"`r`n`"$nodePath`" `"$barePath`" `"%DIR%index.mjs`" %*"
|
||||
$batContent | Set-Content (Join-Path $HostDir "bridge-swarm-host.bat") -Encoding ASCII
|
||||
$HostPath = Join-Path $HostDir "bridge-swarm-host.bat"
|
||||
|
||||
$manifestPath = Join-Path $RepoRoot "com.bridgeswarm.json"
|
||||
$manifest = Get-Content $manifestPath -Raw | ConvertFrom-Json
|
||||
$manifest.path = $HostPath
|
||||
|
||||
$manifestFile = Join-Path $env:LOCALAPPDATA "bridge-swarm\com.bridgeswarm.json"
|
||||
$manifestDir = Split-Path $manifestFile
|
||||
if (-not (Test-Path $manifestDir)) { New-Item -ItemType Directory -Path $manifestDir -Force | Out-Null }
|
||||
$manifest | ConvertTo-Json -Depth 4 | Set-Content $manifestFile -Encoding UTF8
|
||||
|
||||
$chromeKey = "HKCU:\Software\Google\Chrome\NativeMessagingHosts\com.bridgeswarm"
|
||||
New-Item -Path $chromeKey -Force | Out-Null
|
||||
Set-ItemProperty -Path $chromeKey -Name "(Default)" -Value $manifestFile
|
||||
$ffKey = "HKCU:\Software\Mozilla\NativeMessagingHosts\com.bridgeswarm"
|
||||
New-Item -Path $ffKey -Force | Out-Null
|
||||
Set-ItemProperty -Path $ffKey -Name "(Default)" -Value $manifestFile
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "2. Preparing extension..."
|
||||
$ExtDir = Join-Path $RepoRoot "extension"
|
||||
try { Set-Clipboard -Value $ExtDir } catch {}
|
||||
Start-Process "chrome://extensions" -ErrorAction SilentlyContinue
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "Done. Load unpacked extension from: $ExtDir"
|
||||
Write-Host "Host bat: $HostPath"
|
||||
Executable
+95
@@ -0,0 +1,95 @@
|
||||
#!/usr/bin/env bash
|
||||
# BridgeSwarm — install from a local git clone (dev / from-source).
|
||||
# End users should use the release installer instead:
|
||||
# curl -fsSL https://git.ssh.surf/snxraven/BridgeSwarm/raw/branch/main/scripts/web-installer.sh | bash
|
||||
#
|
||||
# Usage: ./scripts/install-from-source.sh or npm run setup
|
||||
set -e
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
cd "$REPO_ROOT"
|
||||
|
||||
echo "BridgeSwarm – Install from source"
|
||||
echo "================================="
|
||||
|
||||
# Require Node.js
|
||||
if ! command -v node >/dev/null 2>&1; then
|
||||
echo "Error: Node.js is required. Install from https://nodejs.org and run this script again."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 1. Native host (pulls Bare via the local `bare` npm dependency)
|
||||
echo ""
|
||||
echo "1. Installing native host..."
|
||||
HOST_DIR="$REPO_ROOT/native-host"
|
||||
cd "$HOST_DIR"
|
||||
npm install --no-fund --no-audit 2>/dev/null || npm install
|
||||
|
||||
if [[ ! -x "$HOST_DIR/node_modules/bare/bin/bare" ]] && ! command -v bare >/dev/null 2>&1; then
|
||||
echo "Warning: Bare runtime not found after install. Check native-host dependency \`bare\`."
|
||||
echo "Continuing; native host may not work until Bare is available."
|
||||
fi
|
||||
|
||||
|
||||
# Build hrpc spec (generated code for native host) and protomux bundle (from repo root)
|
||||
echo ""
|
||||
echo "1b. Building hrpc spec and Protomux bundle..."
|
||||
cd "$REPO_ROOT"
|
||||
npm install --no-fund --no-audit 2>/dev/null || npm install
|
||||
node scripts/build-hrpc.js
|
||||
npm run build:host
|
||||
npm run build:protomux
|
||||
cd "$HOST_DIR"
|
||||
|
||||
HOST_PATH="$HOST_DIR/bridge-swarm-host"
|
||||
|
||||
CHROME_DIR="$HOME/.config/google-chrome/NativeMessagingHosts"
|
||||
CHROMIUM_DIR="$HOME/.config/chromium/NativeMessagingHosts"
|
||||
FIREFOX_DIR="$HOME/.mozilla/native-messaging-hosts"
|
||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
CHROME_DIR="$HOME/Library/Application Support/Google/Chrome/NativeMessagingHosts"
|
||||
CHROMIUM_DIR="$HOME/Library/Application Support/Chromium/NativeMessagingHosts"
|
||||
FIREFOX_DIR="$HOME/Library/Application Support/Mozilla/NativeMessagingHosts"
|
||||
fi
|
||||
|
||||
MANIFEST_NAME="com.bridgeswarm"
|
||||
MANIFEST_CONTENT=$(sed "s|ABSOLUTE_PATH_TO_NATIVE_HOST|$HOST_PATH|g" "$REPO_ROOT/com.bridgeswarm.json")
|
||||
for dir in "$CHROME_DIR" "$CHROMIUM_DIR" "$FIREFOX_DIR"; do
|
||||
mkdir -p "$dir" 2>/dev/null && echo "$MANIFEST_CONTENT" > "$dir/${MANIFEST_NAME}.json" && echo " Native host manifest: $dir"
|
||||
done
|
||||
|
||||
# 2. Extension
|
||||
echo ""
|
||||
echo "2. Preparing extension..."
|
||||
EXT_DIR="$REPO_ROOT/extension"
|
||||
if [[ ! -f "$EXT_DIR/manifest.json" ]]; then
|
||||
echo "Error: extension/manifest.json not found."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if command -v pbcopy >/dev/null 2>&1; then
|
||||
echo "$EXT_DIR" | pbcopy
|
||||
elif command -v xclip >/dev/null 2>&1; then
|
||||
echo -n "$EXT_DIR" | xclip -selection clipboard 2>/dev/null || true
|
||||
elif command -v xsel >/dev/null 2>&1; then
|
||||
echo -n "$EXT_DIR" | xsel --clipboard 2>/dev/null || true
|
||||
fi
|
||||
|
||||
open_page() {
|
||||
local url="$1" app="$2"
|
||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
open -a "$app" "$url" 2>/dev/null && return 0
|
||||
fi
|
||||
command -v xdg-open >/dev/null 2>&1 && xdg-open "$url" 2>/dev/null && return 0
|
||||
return 1
|
||||
}
|
||||
open_page "chrome://extensions" "Google Chrome" || \
|
||||
open_page "chrome://extensions" "Chromium" || \
|
||||
open_page "chrome://extensions" "Microsoft Edge" || true
|
||||
|
||||
echo ""
|
||||
echo "Done."
|
||||
echo ""
|
||||
echo "Next step: In the browser tab that opened, click 'Load unpacked' and paste this path:"
|
||||
echo " $EXT_DIR"
|
||||
echo "(Path is in your clipboard.) Then restart the browser."
|
||||
@@ -14,9 +14,10 @@ if (-not (Test-Path (Join-Path $HostDir "node_modules"))) {
|
||||
}
|
||||
|
||||
$nodePath = (Get-Command node -ErrorAction SilentlyContinue).Source
|
||||
$barePath = (Get-Command bare -ErrorAction SilentlyContinue).Source
|
||||
$localBare = Join-Path $HostDir "node_modules\bare\bin\bare"
|
||||
$bareCmd = Get-Command bare -ErrorAction SilentlyContinue
|
||||
if (-not $nodePath) { $nodePath = "node" }
|
||||
if (-not $barePath) { $barePath = "bare" }
|
||||
$barePath = if (Test-Path $localBare) { $localBare } elseif ($bareCmd) { $bareCmd.Source } else { "bare" }
|
||||
|
||||
$batContent = "@echo off`r`nset `"DIR=%~dp0`"`r`n`"$nodePath`" `"$barePath`" `"%DIR%index.mjs`" %*"
|
||||
Set-Content (Join-Path $HostDir "bridge-swarm-host.bat") -Value $batContent -Encoding ASCII
|
||||
|
||||
@@ -13,15 +13,16 @@ else
|
||||
echo " node_modules exists, skipping npm install"
|
||||
fi
|
||||
|
||||
BARE_PATH="$(which bare 2>/dev/null)" || true
|
||||
# Prefer Bare bundled with native-host (npm package `bare` → bare-runtime).
|
||||
BARE_PATH=""
|
||||
LOCAL_BARE="$HOST_DIR/node_modules/bare/bin/bare"
|
||||
[[ -x "$LOCAL_BARE" ]] && BARE_PATH="$LOCAL_BARE"
|
||||
[[ -z "$BARE_PATH" ]] && BARE_PATH="$(which bare 2>/dev/null)" || true
|
||||
[[ -z "$BARE_PATH" ]] && for c in /opt/homebrew/bin/bare /usr/local/bin/bare; do [[ -x "$c" ]] && BARE_PATH="$c" && break; done
|
||||
NODE_PATH=""
|
||||
if [[ -n "$BARE_PATH" ]]; then
|
||||
BARE_DIR="${BARE_PATH%/*}"
|
||||
for c in "$BARE_DIR/node" "$(which node 2>/dev/null)" /opt/homebrew/bin/node /usr/local/bin/node; do
|
||||
[[ -x "$c" ]] && NODE_PATH="$c" && break
|
||||
done
|
||||
fi
|
||||
for c in "$(which node 2>/dev/null)" /opt/homebrew/bin/node /usr/local/bin/node; do
|
||||
[[ -x "$c" ]] && NODE_PATH="$c" && break
|
||||
done
|
||||
[[ -z "$NODE_PATH" ]] && NODE_PATH="node"
|
||||
[[ -z "$BARE_PATH" ]] && BARE_PATH="bare"
|
||||
|
||||
|
||||
+148
-74
@@ -1,87 +1,161 @@
|
||||
# Unified installer for BridgeSwarm (extension + native host).
|
||||
# Run from anywhere: .\scripts\install.ps1 or cd bridge-swarm; .\scripts\install.ps1
|
||||
# BridgeSwarm Installer (Windows)
|
||||
# Downloads the native host binary and extension from the latest Gitea release.
|
||||
#
|
||||
# Usage (PowerShell as your normal user):
|
||||
# irm https://git.ssh.surf/snxraven/BridgeSwarm/raw/branch/main/scripts/install.ps1 | iex
|
||||
$ErrorActionPreference = "Stop"
|
||||
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
$RepoRoot = (Resolve-Path (Join-Path $ScriptDir "..")).Path
|
||||
|
||||
Write-Host "BridgeSwarm – Install" -ForegroundColor Cyan
|
||||
Write-Host "============================="
|
||||
$ReleaseBase = "https://git.ssh.surf/snxraven/BridgeSwarm/releases/download/latest-main"
|
||||
$InstallDir = "$env:LOCALAPPDATA\bridgeswarm"
|
||||
$Downloads = "$env:USERPROFILE\Downloads"
|
||||
$ManifestName = "com.bridgeswarm"
|
||||
$ExtVersion = "1.0.0"
|
||||
$ChromeExtId = "fmcenppcipeikpnpopolicnllljclmmi"
|
||||
$FirefoxExtId = "[email protected]"
|
||||
|
||||
# Require Node.js
|
||||
if (-not (Get-Command node -ErrorAction SilentlyContinue)) {
|
||||
Write-Host "Error: Node.js is required. Install from https://nodejs.org and run this script again." -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
$HostZip = "bridge-swarm-host-win32-x64.zip"
|
||||
$ExtZip = "BridgeSwarm-$ExtVersion.zip"
|
||||
$ExtXpi = "BridgeSwarm-$ExtVersion.xpi"
|
||||
|
||||
# Ensure bare is available
|
||||
if (-not (Get-Command bare -ErrorAction SilentlyContinue)) {
|
||||
Write-Host "Installing Bare runtime (required for native host)..."
|
||||
npm install -g bare 2>$null
|
||||
if (-not (Get-Command bare -ErrorAction SilentlyContinue)) {
|
||||
Write-Host "Warning: Could not install bare. Run: npm install -g bare" -ForegroundColor Yellow
|
||||
Write-Host ""
|
||||
Write-Host "BridgeSwarm Installer" -ForegroundColor Cyan
|
||||
Write-Host "=====================" -ForegroundColor Cyan
|
||||
Write-Host "Install : $InstallDir"
|
||||
Write-Host ""
|
||||
|
||||
# ── Stop running host ──────────────────────────────────────────────────────────
|
||||
Write-Host "Stopping any running native host..."
|
||||
Get-Process | Where-Object { $_.Path -like "*bridge-swarm-host*" } | Stop-Process -Force -ErrorAction SilentlyContinue
|
||||
|
||||
# ── Preserve storage ───────────────────────────────────────────────────────────
|
||||
$StashDir = Join-Path $env:TEMP "bridgeswarm-stash-$([System.Guid]::NewGuid().ToString('N'))"
|
||||
$StashStorage = Join-Path $StashDir "bridge-swarm-storage"
|
||||
$HadPrevious = $false
|
||||
|
||||
if (Test-Path $InstallDir) {
|
||||
Write-Host "Preserving existing storage..."
|
||||
New-Item -ItemType Directory -Path $StashDir -Force | Out-Null
|
||||
$StorageSrc = Join-Path $InstallDir "bridge-swarm-storage"
|
||||
if (Test-Path $StorageSrc) {
|
||||
Copy-Item $StorageSrc $StashStorage -Recurse -Force
|
||||
Write-Host " Saved: bridge-swarm-storage"
|
||||
$HadPrevious = $true
|
||||
}
|
||||
}
|
||||
|
||||
# 1. Native host
|
||||
Write-Host ""
|
||||
Write-Host "1. Installing native host..."
|
||||
$HostDir = Join-Path $RepoRoot "native-host"
|
||||
Set-Location $HostDir
|
||||
npm install --no-fund --no-audit 2>$null
|
||||
if ($LASTEXITCODE -ne 0) { npm install }
|
||||
# ── Remove previous installation ───────────────────────────────────────────────
|
||||
Write-Host "Removing any previous installation..."
|
||||
if (Test-Path $InstallDir) { Remove-Item $InstallDir -Recurse -Force }
|
||||
|
||||
# Windows: use .bat that runs bare; Chrome may have limited PATH so use full path if we can find it
|
||||
$bareCmd = Get-Command bare -ErrorAction SilentlyContinue
|
||||
$nodeCmd = Get-Command node -ErrorAction SilentlyContinue
|
||||
$nodePath = if ($nodeCmd) { $nodeCmd.Source } else { "node" }
|
||||
$barePath = if ($bareCmd) { $bareCmd.Source } else { "bare" }
|
||||
|
||||
$batContent = "@echo off`r`nset `"DIR=%~dp0`"`r`n`"$nodePath`" `"$barePath`" `"%DIR%index.mjs`" %*"
|
||||
$batContent | Set-Content (Join-Path $HostDir "bridge-swarm-host.bat") -Encoding ASCII
|
||||
$HostPath = Join-Path $HostDir "bridge-swarm-host.bat"
|
||||
|
||||
$manifestPath = Join-Path $RepoRoot "com.bridgeswarm.json"
|
||||
$manifest = Get-Content $manifestPath -Raw | ConvertFrom-Json
|
||||
$manifest.path = $HostPath
|
||||
|
||||
$manifestFile = Join-Path $env:LOCALAPPDATA "bridge-swarm\com.bridgeswarm.json"
|
||||
$manifestDir = Split-Path $manifestFile
|
||||
if (-not (Test-Path $manifestDir)) { New-Item -ItemType Directory -Path $manifestDir -Force | Out-Null }
|
||||
$manifest | ConvertTo-Json -Depth 4 | Set-Content $manifestFile -Encoding UTF8
|
||||
|
||||
$chromeKey = "HKCU:\Software\Google\Chrome\NativeMessagingHosts\com.bridgeswarm"
|
||||
New-Item -Path $chromeKey -Force | Out-Null
|
||||
Set-ItemProperty -Path $chromeKey -Name "(Default)" -Value $manifestFile
|
||||
$ffKey = "HKCU:\Software\Mozilla\NativeMessagingHosts\com.bridgeswarm"
|
||||
New-Item -Path $ffKey -Force | Out-Null
|
||||
Set-ItemProperty -Path $ffKey -Name "(Default)" -Value $manifestFile
|
||||
Write-Host " Native host manifest: $manifestFile"
|
||||
|
||||
# 2. Extension
|
||||
Write-Host ""
|
||||
Write-Host "2. Preparing extension..."
|
||||
$ExtDir = (Resolve-Path (Join-Path $RepoRoot "extension")).Path
|
||||
if (-not (Test-Path (Join-Path $ExtDir "manifest.json"))) {
|
||||
Write-Host "Error: extension/manifest.json not found." -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
Set-Clipboard -Value $ExtDir
|
||||
|
||||
$chromePaths = @(
|
||||
"${env:ProgramFiles}\Google\Chrome\Application\chrome.exe",
|
||||
"${env:ProgramFiles(x86)}\Google\Chrome\Application\chrome.exe",
|
||||
"${env:LocalAppData}\Google\Chrome\Application\chrome.exe"
|
||||
$RegPaths = @(
|
||||
"HKCU:\Software\Google\Chrome\NativeMessagingHosts\$ManifestName",
|
||||
"HKCU:\Software\Chromium\NativeMessagingHosts\$ManifestName",
|
||||
"HKCU:\Software\Mozilla\NativeMessagingHosts\$ManifestName"
|
||||
)
|
||||
foreach ($p in $chromePaths) {
|
||||
if (Test-Path $p) {
|
||||
Start-Process $p -ArgumentList "chrome://extensions"
|
||||
break
|
||||
}
|
||||
foreach ($p in $RegPaths) {
|
||||
if (Test-Path $p) { Remove-Item $p -Force -ErrorAction SilentlyContinue }
|
||||
}
|
||||
|
||||
# ── Download host ──────────────────────────────────────────────────────────────
|
||||
New-Item -ItemType Directory -Path $InstallDir -Force | Out-Null
|
||||
New-Item -ItemType Directory -Path $Downloads -Force | Out-Null
|
||||
|
||||
Write-Host "Downloading native host..."
|
||||
$TmpZip = "$env:TEMP\$HostZip"
|
||||
Invoke-WebRequest "$ReleaseBase/$HostZip" -OutFile $TmpZip
|
||||
Expand-Archive -Path $TmpZip -DestinationPath $InstallDir -Force
|
||||
Remove-Item $TmpZip
|
||||
|
||||
$HostBin = Get-ChildItem -Path $InstallDir -Recurse -Filter "bridge-swarm-host.exe" | Select-Object -First 1 -ExpandProperty FullName
|
||||
if (-not $HostBin) {
|
||||
Write-Host "Error: binary not found in $HostZip" -ForegroundColor Red; exit 1
|
||||
}
|
||||
Write-Host " Binary: $HostBin"
|
||||
|
||||
# Launcher bat that sets storage path
|
||||
$HostDir = Split-Path $HostBin -Parent
|
||||
$Launcher = Join-Path $HostDir "run-bridge-swarm-host.bat"
|
||||
$Bat = @"
|
||||
@echo off
|
||||
set "DIR=%~dp0"
|
||||
if not defined BRIDGE_SWARM_STORAGE set "BRIDGE_SWARM_STORAGE=%DIR%bridge-swarm-storage"
|
||||
"%DIR%bridge-swarm-host.exe" %*
|
||||
"@
|
||||
Set-Content -Path $Launcher -Value $Bat -Encoding ASCII
|
||||
$HostBin = $Launcher
|
||||
|
||||
# ── Restore storage ────────────────────────────────────────────────────────────
|
||||
if ($HadPrevious -and (Test-Path $StashStorage)) {
|
||||
Write-Host "Restoring storage..."
|
||||
Copy-Item $StashStorage (Join-Path $InstallDir "bridge-swarm-storage") -Recurse -Force
|
||||
Write-Host " Restored: bridge-swarm-storage"
|
||||
}
|
||||
if (Test-Path $StashDir) { Remove-Item $StashDir -Recurse -Force -ErrorAction SilentlyContinue }
|
||||
|
||||
# ── Extension ──────────────────────────────────────────────────────────────────
|
||||
Write-Host "Cleaning up old extension files in Downloads..."
|
||||
Get-ChildItem "$Downloads\BridgeSwarm-*.zip","$Downloads\BridgeSwarm-*.xpi" -ErrorAction SilentlyContinue | ForEach-Object {
|
||||
Remove-Item $_.FullName -Force
|
||||
Write-Host " Removed: $($_.Name)"
|
||||
}
|
||||
|
||||
Write-Host "Downloading extension..."
|
||||
Invoke-WebRequest "$ReleaseBase/$ExtZip" -OutFile (Join-Path $Downloads $ExtZip)
|
||||
Write-Host " Saved: $Downloads\$ExtZip"
|
||||
try {
|
||||
Invoke-WebRequest "$ReleaseBase/$ExtXpi" -OutFile (Join-Path $Downloads $ExtXpi)
|
||||
} catch {}
|
||||
|
||||
# ── Native messaging manifests ─────────────────────────────────────────────────
|
||||
Write-Host "Installing native messaging manifest..."
|
||||
|
||||
$ManifestChrome = @{
|
||||
name = "com.bridgeswarm"
|
||||
description = "BridgeSwarm native host (Bare / Hyperswarm)"
|
||||
path = $HostBin
|
||||
type = "stdio"
|
||||
allowed_origins = @("chrome-extension://$ChromeExtId/")
|
||||
} | ConvertTo-Json -Depth 4
|
||||
|
||||
$ManifestFirefox = @{
|
||||
name = "com.bridgeswarm"
|
||||
description = "BridgeSwarm native host (Bare / Hyperswarm)"
|
||||
path = $HostBin
|
||||
type = "stdio"
|
||||
allowed_extensions = @($FirefoxExtId)
|
||||
} | ConvertTo-Json -Depth 4
|
||||
|
||||
$ManifestDir = Join-Path $env:LOCALAPPDATA "bridgeswarm"
|
||||
New-Item -ItemType Directory -Path $ManifestDir -Force | Out-Null
|
||||
$ChromeManifestFile = Join-Path $ManifestDir "com.bridgeswarm.json"
|
||||
$FirefoxManifestFile = Join-Path $ManifestDir "com.bridgeswarm.firefox.json"
|
||||
Set-Content -Path $ChromeManifestFile -Value $ManifestChrome -Encoding UTF8
|
||||
Set-Content -Path $FirefoxManifestFile -Value $ManifestFirefox -Encoding UTF8
|
||||
|
||||
New-Item -Path "HKCU:\Software\Google\Chrome\NativeMessagingHosts\$ManifestName" -Force | Out-Null
|
||||
Set-ItemProperty -Path "HKCU:\Software\Google\Chrome\NativeMessagingHosts\$ManifestName" -Name "(Default)" -Value $ChromeManifestFile
|
||||
New-Item -Path "HKCU:\Software\Chromium\NativeMessagingHosts\$ManifestName" -Force -ErrorAction SilentlyContinue | Out-Null
|
||||
Set-ItemProperty -Path "HKCU:\Software\Chromium\NativeMessagingHosts\$ManifestName" -Name "(Default)" -Value $ChromeManifestFile -ErrorAction SilentlyContinue
|
||||
New-Item -Path "HKCU:\Software\Mozilla\NativeMessagingHosts\$ManifestName" -Force | Out-Null
|
||||
Set-ItemProperty -Path "HKCU:\Software\Mozilla\NativeMessagingHosts\$ManifestName" -Name "(Default)" -Value $FirefoxManifestFile
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "Done." -ForegroundColor Green
|
||||
Write-Host "=====================" -ForegroundColor Cyan
|
||||
Write-Host "Installation complete!"
|
||||
Write-Host ""
|
||||
Write-Host "Next steps:"
|
||||
Write-Host ""
|
||||
Write-Host " Chrome / Edge:"
|
||||
Write-Host " 1. Open chrome://extensions"
|
||||
Write-Host " 2. Enable Developer mode"
|
||||
Write-Host " 3. Drag & drop $Downloads\$ExtZip onto the page"
|
||||
Write-Host " 4. Extension ID should be: $ChromeExtId"
|
||||
Write-Host ""
|
||||
Write-Host " Firefox:"
|
||||
Write-Host " about:debugging → Load Temporary Add-on → $Downloads\$ExtZip"
|
||||
Write-Host " (or Install From File with $Downloads\$ExtXpi on Nightly/Dev Edition)"
|
||||
Write-Host ""
|
||||
Write-Host " Then restart your browser."
|
||||
Write-Host ""
|
||||
Write-Host " Install dir: $InstallDir"
|
||||
Write-Host ""
|
||||
Write-Host "Next step: In the browser tab that opened, click 'Load unpacked' and paste this path:"
|
||||
Write-Host " $ExtDir"
|
||||
Write-Host "(Path is in your clipboard.) Then restart the browser."
|
||||
|
||||
+215
-81
@@ -1,98 +1,232 @@
|
||||
#!/usr/bin/env bash
|
||||
# Unified installer for BridgeSwarm (extension + native host).
|
||||
# Run from anywhere: ./scripts/install.sh or cd bridge-swarm && ./scripts/install.sh
|
||||
set -e
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
cd "$REPO_ROOT"
|
||||
# BridgeSwarm Installer (macOS / Linux)
|
||||
# Downloads the native host binary and extension from the latest Gitea release.
|
||||
#
|
||||
# Usage:
|
||||
# curl -fsSL https://git.ssh.surf/snxraven/BridgeSwarm/raw/branch/main/scripts/web-installer.sh | bash
|
||||
# # or:
|
||||
# curl -fsSL https://git.ssh.surf/snxraven/BridgeSwarm/raw/branch/main/scripts/install.sh | bash
|
||||
set -euo pipefail
|
||||
|
||||
echo "BridgeSwarm – Install"
|
||||
echo "============================="
|
||||
RELEASE_BASE="https://git.ssh.surf/snxraven/BridgeSwarm/releases/download/latest-main"
|
||||
INSTALL_DIR="$HOME/.bridgeswarm"
|
||||
MANIFEST_NAME="com.bridgeswarm"
|
||||
DOWNLOADS="$HOME/Downloads"
|
||||
EXT_VERSION="1.0.0"
|
||||
CHROME_EXT_ID="fmcenppcipeikpnpopolicnllljclmmi"
|
||||
FIREFOX_EXT_ID="[email protected]"
|
||||
|
||||
# Require Node.js
|
||||
if ! command -v node >/dev/null 2>&1; then
|
||||
echo "Error: Node.js is required. Install from https://nodejs.org and run this script again."
|
||||
exit 1
|
||||
# ── Detect platform ────────────────────────────────────────────────────────────
|
||||
OS="$(uname -s | tr '[:upper:]' '[:lower:]')"
|
||||
ARCH="$(uname -m)"
|
||||
[[ "$ARCH" == "x86_64" ]] && ARCH="x64"
|
||||
[[ "$ARCH" == "aarch64" || "$ARCH" == "arm64" ]] && ARCH="arm64"
|
||||
[[ "$OS" == "darwin" ]] && PLATFORM="darwin"
|
||||
[[ "$OS" == "linux" ]] && PLATFORM="linux"
|
||||
|
||||
if [[ -z "${PLATFORM:-}" ]]; then
|
||||
echo "Unsupported OS: $OS" >&2; exit 1
|
||||
fi
|
||||
|
||||
# Ensure bare is available (required for native host)
|
||||
if ! command -v bare >/dev/null 2>&1; then
|
||||
echo "Installing Bare runtime (required for native host)..."
|
||||
if npm install -g bare 2>/dev/null; then
|
||||
echo "Bare installed."
|
||||
else
|
||||
echo "Warning: Could not install bare. Run: npm install -g bare"
|
||||
echo "Continuing; native host may not work until bare is installed."
|
||||
HOST_ZIP="bridge-swarm-host-${PLATFORM}-${ARCH}.zip"
|
||||
EXT_ZIP="BridgeSwarm-${EXT_VERSION}.zip"
|
||||
EXT_XPI="BridgeSwarm-${EXT_VERSION}.xpi"
|
||||
|
||||
echo ""
|
||||
echo "BridgeSwarm Installer"
|
||||
echo "====================="
|
||||
echo "Platform : ${PLATFORM}-${ARCH}"
|
||||
echo "Install : ${INSTALL_DIR}"
|
||||
echo ""
|
||||
|
||||
# ── Stop any running native host ───────────────────────────────────────────────
|
||||
echo "Stopping any running native host..."
|
||||
pkill -f "bridgeswarm/native-host/index.mjs" 2>/dev/null || true
|
||||
pkill -f "bridge-swarm-host" 2>/dev/null || true
|
||||
pkill -f "\.bridgeswarm" 2>/dev/null || true
|
||||
|
||||
# ── Preserve user data ─────────────────────────────────────────────────────────
|
||||
STASH_DIR="$(mktemp -d)"
|
||||
STASH_STORAGE="${STASH_DIR}/bridge-swarm-storage"
|
||||
HAD_PREVIOUS=false
|
||||
|
||||
if [[ -d "$INSTALL_DIR" ]]; then
|
||||
echo "Preserving existing storage..."
|
||||
if [[ -d "${INSTALL_DIR}/bridge-swarm-storage" ]]; then
|
||||
cp -a "${INSTALL_DIR}/bridge-swarm-storage" "$STASH_STORAGE"
|
||||
echo " Saved: bridge-swarm-storage"
|
||||
HAD_PREVIOUS=true
|
||||
fi
|
||||
fi
|
||||
|
||||
# 1. Native host
|
||||
echo ""
|
||||
echo "1. Installing native host..."
|
||||
HOST_DIR="$REPO_ROOT/native-host"
|
||||
cd "$HOST_DIR"
|
||||
npm install --no-fund --no-audit 2>/dev/null || npm install
|
||||
# ── Remove previous installation ───────────────────────────────────────────────
|
||||
echo "Removing any previous installation..."
|
||||
rm -rf "$INSTALL_DIR"
|
||||
|
||||
|
||||
# Build hrpc spec (generated code for native host) and protomux bundle (from repo root)
|
||||
echo ""
|
||||
echo "1b. Building hrpc spec and Protomux bundle..."
|
||||
cd "$REPO_ROOT"
|
||||
npm install --no-fund --no-audit 2>/dev/null || npm install
|
||||
node scripts/build-hrpc.js
|
||||
npm run build:host
|
||||
npm run build:protomux
|
||||
cd "$HOST_DIR"
|
||||
|
||||
HOST_PATH="$HOST_DIR/bridge-swarm-host"
|
||||
|
||||
CHROME_DIR="$HOME/.config/google-chrome/NativeMessagingHosts"
|
||||
CHROMIUM_DIR="$HOME/.config/chromium/NativeMessagingHosts"
|
||||
FIREFOX_DIR="$HOME/.mozilla/native-messaging-hosts"
|
||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
CHROME_DIR="$HOME/Library/Application Support/Google/Chrome/NativeMessagingHosts"
|
||||
CHROMIUM_DIR="$HOME/Library/Application Support/Chromium/NativeMessagingHosts"
|
||||
FIREFOX_DIR="$HOME/Library/Application Support/Mozilla/NativeMessagingHosts"
|
||||
fi
|
||||
|
||||
MANIFEST_NAME="com.bridgeswarm"
|
||||
MANIFEST_CONTENT=$(sed "s|ABSOLUTE_PATH_TO_NATIVE_HOST|$HOST_PATH|g" "$REPO_ROOT/com.bridgeswarm.json")
|
||||
for dir in "$CHROME_DIR" "$CHROMIUM_DIR" "$FIREFOX_DIR"; do
|
||||
mkdir -p "$dir" 2>/dev/null && echo "$MANIFEST_CONTENT" > "$dir/${MANIFEST_NAME}.json" && echo " Native host manifest: $dir"
|
||||
for dir in \
|
||||
"$HOME/.config/google-chrome/NativeMessagingHosts" \
|
||||
"$HOME/.config/chromium/NativeMessagingHosts" \
|
||||
"$HOME/.mozilla/native-messaging-hosts" \
|
||||
"$HOME/Library/Application Support/Google/Chrome/NativeMessagingHosts" \
|
||||
"$HOME/Library/Application Support/Chromium/NativeMessagingHosts" \
|
||||
"$HOME/Library/Application Support/Mozilla/NativeMessagingHosts"; do
|
||||
rm -f "${dir}/${MANIFEST_NAME}.json" 2>/dev/null || true
|
||||
done
|
||||
|
||||
# 2. Extension
|
||||
echo ""
|
||||
echo "2. Preparing extension..."
|
||||
EXT_DIR="$REPO_ROOT/extension"
|
||||
if [[ ! -f "$EXT_DIR/manifest.json" ]]; then
|
||||
echo "Error: extension/manifest.json not found."
|
||||
exit 1
|
||||
fi
|
||||
# ── Download host ──────────────────────────────────────────────────────────────
|
||||
mkdir -p "$INSTALL_DIR"
|
||||
mkdir -p "$DOWNLOADS"
|
||||
|
||||
if command -v pbcopy >/dev/null 2>&1; then
|
||||
echo "$EXT_DIR" | pbcopy
|
||||
elif command -v xclip >/dev/null 2>&1; then
|
||||
echo -n "$EXT_DIR" | xclip -selection clipboard 2>/dev/null || true
|
||||
elif command -v xsel >/dev/null 2>&1; then
|
||||
echo -n "$EXT_DIR" | xsel --clipboard 2>/dev/null || true
|
||||
fi
|
||||
echo "Downloading native host..."
|
||||
curl -fsSL "${RELEASE_BASE}/${HOST_ZIP}" -o "/tmp/${HOST_ZIP}"
|
||||
unzip -q -o "/tmp/${HOST_ZIP}" -d "$INSTALL_DIR"
|
||||
rm "/tmp/${HOST_ZIP}"
|
||||
|
||||
open_page() {
|
||||
local url="$1" app="$2"
|
||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
open -a "$app" "$url" 2>/dev/null && return 0
|
||||
HOST_BIN="$(find "$INSTALL_DIR" -type f \( -name "bridge-swarm-host" -o -name "bridge-swarm-host.exe" \) | head -1)"
|
||||
if [[ -z "$HOST_BIN" ]]; then
|
||||
echo "Error: binary not found in ${HOST_ZIP}" >&2; exit 1
|
||||
fi
|
||||
chmod +x "$HOST_BIN"
|
||||
|
||||
# ── Restore storage ────────────────────────────────────────────────────────────
|
||||
if [[ "$HAD_PREVIOUS" == "true" && -d "$STASH_STORAGE" ]]; then
|
||||
echo "Restoring storage..."
|
||||
cp -a "$STASH_STORAGE" "${INSTALL_DIR}/bridge-swarm-storage"
|
||||
echo " Restored: bridge-swarm-storage"
|
||||
fi
|
||||
rm -rf "$STASH_DIR"
|
||||
|
||||
# On macOS: clear quarantine, ad-hoc sign, extract/sign native addons
|
||||
if [[ "$PLATFORM" == "darwin" ]]; then
|
||||
echo " Clearing quarantine and signing..."
|
||||
/usr/bin/xattr -rd com.apple.quarantine "$INSTALL_DIR" 2>/dev/null || true
|
||||
|
||||
HOST_DIR="$(dirname "$HOST_BIN")"
|
||||
ADDON_TMPDIR="${HOST_DIR}/tmp"
|
||||
mkdir -p "$ADDON_TMPDIR"
|
||||
|
||||
ENTITLEMENTS_PLIST="${HOST_DIR}/entitlements.plist"
|
||||
printf '%s\n' '<?xml version="1.0" encoding="UTF-8"?>' '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">' '<plist version="1.0"><dict><key>com.apple.security.cs.disable-library-validation</key><true/></dict></plist>' > "$ENTITLEMENTS_PLIST"
|
||||
codesign --force --sign - --entitlements "$ENTITLEMENTS_PLIST" "$HOST_BIN" 2>/dev/null || true
|
||||
|
||||
LAUNCHER="${HOST_DIR}/run-bridge-swarm-host.sh"
|
||||
printf '%s\n' '#!/bin/bash' 'DIR="$(cd "$(dirname "$0")" && pwd)"' 'export TMPDIR="${DIR}/tmp"' 'export BRIDGE_SWARM_STORAGE="${BRIDGE_SWARM_STORAGE:-${DIR}/bridge-swarm-storage}"' 'exec "${DIR}/bridge-swarm-host" "$@"' > "$LAUNCHER"
|
||||
chmod +x "$LAUNCHER"
|
||||
|
||||
echo " Extracting native addons (--extract-addons)..."
|
||||
"$LAUNCHER" --extract-addons 2>/dev/null || true
|
||||
sleep 2
|
||||
|
||||
SIGNED=0
|
||||
if [[ -d "$ADDON_TMPDIR" ]]; then
|
||||
/usr/bin/xattr -rd com.apple.quarantine "$ADDON_TMPDIR" 2>/dev/null || true
|
||||
while IFS= read -r -d '' f; do
|
||||
codesign --force --sign - "$f" 2>/dev/null && SIGNED=$((SIGNED + 1)) || true
|
||||
done < <(find "$ADDON_TMPDIR" \( -name "*.bare" -o -name "*.dylib" \) -print0 2>/dev/null)
|
||||
fi
|
||||
command -v xdg-open >/dev/null 2>&1 && xdg-open "$url" 2>/dev/null && return 0
|
||||
return 1
|
||||
}
|
||||
open_page "chrome://extensions" "Google Chrome" || \
|
||||
open_page "chrome://extensions" "Chromium" || \
|
||||
open_page "chrome://extensions" "Microsoft Edge" || true
|
||||
echo " Signed ${SIGNED} native addons; main binary has library-validation disabled"
|
||||
HOST_BIN="$LAUNCHER"
|
||||
else
|
||||
# Linux launcher sets storage path next to the binary
|
||||
HOST_DIR="$(dirname "$HOST_BIN")"
|
||||
LAUNCHER="${HOST_DIR}/run-bridge-swarm-host.sh"
|
||||
printf '%s\n' '#!/bin/bash' 'DIR="$(cd "$(dirname "$0")" && pwd)"' 'export BRIDGE_SWARM_STORAGE="${BRIDGE_SWARM_STORAGE:-${DIR}/bridge-swarm-storage}"' 'exec "${DIR}/bridge-swarm-host" "$@"' > "$LAUNCHER"
|
||||
chmod +x "$LAUNCHER"
|
||||
HOST_BIN="$LAUNCHER"
|
||||
fi
|
||||
|
||||
echo " Binary: $HOST_BIN"
|
||||
|
||||
# ── Extension downloads ────────────────────────────────────────────────────────
|
||||
echo "Cleaning up old extension files in Downloads..."
|
||||
for f in "${DOWNLOADS}"/BridgeSwarm-*.zip "${DOWNLOADS}"/BridgeSwarm-*.xpi; do
|
||||
[[ -f "$f" ]] && rm -f "$f" && echo " Removed: $f" || true
|
||||
done
|
||||
|
||||
echo "Downloading extension..."
|
||||
curl -fsSL "${RELEASE_BASE}/${EXT_ZIP}" -o "${DOWNLOADS}/${EXT_ZIP}"
|
||||
echo " Saved: ${DOWNLOADS}/${EXT_ZIP}"
|
||||
curl -fsSL "${RELEASE_BASE}/${EXT_XPI}" -o "${DOWNLOADS}/${EXT_XPI}" 2>/dev/null || true
|
||||
|
||||
# ── Native messaging manifests ─────────────────────────────────────────────────
|
||||
echo "Installing native messaging manifest..."
|
||||
|
||||
MANIFEST_CHROME=$(cat <<JSON
|
||||
{
|
||||
"name": "com.bridgeswarm",
|
||||
"description": "BridgeSwarm native host (Bare / Hyperswarm)",
|
||||
"path": "${HOST_BIN}",
|
||||
"type": "stdio",
|
||||
"allowed_origins": ["chrome-extension://${CHROME_EXT_ID}/"]
|
||||
}
|
||||
JSON
|
||||
)
|
||||
|
||||
MANIFEST_FIREFOX=$(cat <<JSON
|
||||
{
|
||||
"name": "com.bridgeswarm",
|
||||
"description": "BridgeSwarm native host (Bare / Hyperswarm)",
|
||||
"path": "${HOST_BIN}",
|
||||
"type": "stdio",
|
||||
"allowed_extensions": ["${FIREFOX_EXT_ID}"]
|
||||
}
|
||||
JSON
|
||||
)
|
||||
|
||||
if [[ "$PLATFORM" == "darwin" ]]; then
|
||||
CHROME_DIRS=(
|
||||
"$HOME/Library/Application Support/Google/Chrome/NativeMessagingHosts"
|
||||
"$HOME/Library/Application Support/Chromium/NativeMessagingHosts"
|
||||
)
|
||||
FIREFOX_DIRS=(
|
||||
"$HOME/Library/Application Support/Mozilla/NativeMessagingHosts"
|
||||
)
|
||||
else
|
||||
CHROME_DIRS=(
|
||||
"$HOME/.config/google-chrome/NativeMessagingHosts"
|
||||
"$HOME/.config/chromium/NativeMessagingHosts"
|
||||
)
|
||||
FIREFOX_DIRS=(
|
||||
"$HOME/.mozilla/native-messaging-hosts"
|
||||
)
|
||||
fi
|
||||
|
||||
for dir in "${CHROME_DIRS[@]}"; do
|
||||
mkdir -p "$dir"
|
||||
echo "$MANIFEST_CHROME" > "${dir}/${MANIFEST_NAME}.json"
|
||||
echo " Wrote: ${dir}/${MANIFEST_NAME}.json"
|
||||
done
|
||||
|
||||
for dir in "${FIREFOX_DIRS[@]}"; do
|
||||
mkdir -p "$dir"
|
||||
echo "$MANIFEST_FIREFOX" > "${dir}/${MANIFEST_NAME}.json"
|
||||
echo " Wrote: ${dir}/${MANIFEST_NAME}.json"
|
||||
done
|
||||
|
||||
# ── Done ───────────────────────────────────────────────────────────────────────
|
||||
echo ""
|
||||
echo "Done."
|
||||
echo "====================="
|
||||
echo "Installation complete!"
|
||||
echo ""
|
||||
echo "Next steps:"
|
||||
echo ""
|
||||
echo " Chrome / Edge:"
|
||||
echo " 1. Open chrome://extensions"
|
||||
echo " 2. Enable Developer mode"
|
||||
echo " 3. Drag & drop ${DOWNLOADS}/${EXT_ZIP} onto the page"
|
||||
echo " (or Load unpacked after extracting)"
|
||||
echo " 4. Extension ID should be: ${CHROME_EXT_ID}"
|
||||
echo ""
|
||||
echo " Firefox (regular):"
|
||||
echo " 1. Open about:debugging → This Firefox"
|
||||
echo " 2. Load Temporary Add-on… → select ${DOWNLOADS}/${EXT_ZIP}"
|
||||
echo ""
|
||||
echo " Firefox Developer Edition / Nightly (permanent):"
|
||||
echo " 1. about:config → xpinstall.signatures.required = false"
|
||||
echo " 2. about:addons → gear → Install Add-on From File → ${DOWNLOADS}/${EXT_XPI}"
|
||||
echo ""
|
||||
echo " Then restart your browser."
|
||||
echo ""
|
||||
echo " Install dir: ${INSTALL_DIR}"
|
||||
echo " Update later: run the same install command again"
|
||||
echo ""
|
||||
echo "Next step: In the browser tab that opened, click 'Load unpacked' and paste this path:"
|
||||
echo " $EXT_DIR"
|
||||
echo "(Path is in your clipboard.) Then restart the browser."
|
||||
|
||||
+16
-12
@@ -1,13 +1,17 @@
|
||||
#!/usr/bin/env node
|
||||
'use strict';
|
||||
var path = require('path');
|
||||
var spawn = require('child_process').spawn;
|
||||
var isWin = process.platform === 'win32';
|
||||
var script = path.join(__dirname, isWin ? 'install.ps1' : 'install.sh');
|
||||
var child = spawn(isWin ? 'powershell' : '/bin/sh', isWin ? ['-ExecutionPolicy', 'Bypass', '-File', script] : [script], {
|
||||
stdio: 'inherit',
|
||||
cwd: path.join(__dirname, '..'),
|
||||
});
|
||||
child.on('exit', function (code, sig) {
|
||||
process.exit(code !== null ? code : sig ? 1 : 0);
|
||||
});
|
||||
/**
|
||||
* Cross-platform local setup for a git clone (from-source).
|
||||
* End users should use the release installers (install.sh / install.ps1).
|
||||
*/
|
||||
const { spawnSync } = require('child_process');
|
||||
const path = require('path');
|
||||
|
||||
const isWin = process.platform === 'win32';
|
||||
const script = isWin ? 'install-from-source.ps1' : 'install-from-source.sh';
|
||||
const scriptPath = path.join(__dirname, script);
|
||||
|
||||
const result = isWin
|
||||
? spawnSync('powershell', ['-ExecutionPolicy', 'Bypass', '-File', scriptPath], { stdio: 'inherit' })
|
||||
: spawnSync('bash', [scriptPath], { stdio: 'inherit' });
|
||||
|
||||
process.exit(result.status == null ? 1 : result.status);
|
||||
|
||||
Regular → Executable
+4
-190
@@ -1,193 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
# BridgeSwarm — Web Installer (macOS / Linux)
|
||||
# Downloads the native host binary and extension from the latest Gitea release.
|
||||
#
|
||||
# Usage:
|
||||
# curl -fsSL https://ssh.surf/bridgeswarm/install.sh -o install.sh && bash install.sh
|
||||
#
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
echo ""
|
||||
echo "╔══════════════════════════════════════════════════════════════════╗"
|
||||
echo "║ 🌉 BridgeSwarm Installer ║"
|
||||
echo "╚══════════════════════════════════════════════════════════════════╝"
|
||||
echo ""
|
||||
|
||||
cat << 'EOF'
|
||||
This installer will set up BridgeSwarm on your computer:
|
||||
|
||||
• Native Messaging Host → Enables browser ↔ network communication
|
||||
• Browser Extension → Packaged to ~/Downloads for manual install
|
||||
• Fixed Extension ID → No random IDs, stable across updates
|
||||
|
||||
What's installed where:
|
||||
• Software: ~/.bridgeswarm/
|
||||
• Extension: ~/Downloads/BridgeSwarm-*.zip (Chrome) / *.xpi (Firefox)
|
||||
• Browser manifests: ~/.config/.../NativeMessagingHosts/ (Linux)
|
||||
~/Library/Application Support/.../NativeMessagingHosts/ (macOS)
|
||||
%LOCALAPPDATA%/bridge-swarm/ (Windows)
|
||||
|
||||
EOF
|
||||
|
||||
echo ""
|
||||
|
||||
if [ -t 1 ]; then
|
||||
echo -n "Continue with installation? [y/N]: "
|
||||
read -r answer
|
||||
if [[ "${answer,,}" != "y" && "${answer,,}" != "yes" ]]; then
|
||||
echo "Installation cancelled."
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
|
||||
# 1. Install Node.js if missing
|
||||
if ! command -v node >/dev/null 2>&1 || ! command -v npm >/dev/null 2>&1; then
|
||||
echo "📦 Installing Node.js..."
|
||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
command -v brew >/dev/null 2>&1 || /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
|
||||
brew install node
|
||||
elif grep -qiE 'debian|ubuntu' /etc/os-release 2>/dev/null; then
|
||||
curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -
|
||||
sudo apt-get update && sudo apt-get install -y nodejs
|
||||
elif grep -qi fedora /etc/os-release 2>/dev/null || command -v dnf >/dev/null; then
|
||||
sudo dnf install -y nodejs
|
||||
else
|
||||
echo "⚠️ Please install Node.js manually from https://nodejs.org"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# 2. git
|
||||
if ! command -v git >/dev/null 2>&1; then
|
||||
echo "📦 Installing git..."
|
||||
[[ "$OSTYPE" == "darwin"* ]] && brew install git || sudo apt install -y git || sudo dnf install -y git || true
|
||||
fi
|
||||
|
||||
# 3. Clone / update to persistent location
|
||||
INSTALL_DIR="$HOME/.bridgeswarm"
|
||||
mkdir -p "$INSTALL_DIR"
|
||||
cd "$INSTALL_DIR" || exit 1
|
||||
|
||||
if [ -d ".git" ]; then
|
||||
echo "📥 Updating existing installation..."
|
||||
git pull --ff-only origin main || { cd ..; rm -rf "$INSTALL_DIR"; git clone https://git.ssh.surf/snxraven/BridgeSwarm.git "$INSTALL_DIR"; cd "$INSTALL_DIR"; }
|
||||
else
|
||||
echo "📥 Cloning BridgeSwarm..."
|
||||
git clone https://git.ssh.surf/snxraven/BridgeSwarm.git .
|
||||
fi
|
||||
|
||||
# 4. Build
|
||||
echo "🔨 Building bundles and codegen..."
|
||||
npm ci --no-audit --prefer-offline --no-fund
|
||||
cd native-host && npm ci --no-audit --prefer-offline --no-fund || npm install --no-audit --no-fund
|
||||
cd ..
|
||||
npm run build
|
||||
|
||||
# 5. Package the extension
|
||||
echo "📦 Packaging extension..."
|
||||
npm run pack
|
||||
|
||||
# 6. Copy to ~/Downloads
|
||||
DOWNLOADS_DIR="$HOME/Downloads"
|
||||
mkdir -p "$DOWNLOADS_DIR"
|
||||
ZIP_FILE=$(ls releases/BridgeSwarm-*.zip 2>/dev/null | head -1)
|
||||
XPI_FILE=$(ls releases/BridgeSwarm-*.xpi 2>/dev/null | head -1)
|
||||
|
||||
if [ -n "$ZIP_FILE" ]; then
|
||||
cp "$ZIP_FILE" "$DOWNLOADS_DIR/"
|
||||
echo "✅ Extension saved to ~/Downloads/$(basename "$ZIP_FILE")"
|
||||
fi
|
||||
if [ -n "$XPI_FILE" ]; then
|
||||
cp "$XPI_FILE" "$DOWNLOADS_DIR/"
|
||||
echo "✅ Firefox extension saved to ~/Downloads/$(basename "$XPI_FILE")"
|
||||
fi
|
||||
|
||||
# 7. Install native host + manifest
|
||||
echo "🔧 Installing native messaging host..."
|
||||
|
||||
HOST_WRAPPER="$INSTALL_DIR/native-host/bridge-swarm-host"
|
||||
chmod +x "$HOST_WRAPPER" 2>/dev/null || true
|
||||
|
||||
MANIFEST_NAME="com.bridgeswarm"
|
||||
MANIFEST_TEMPLATE="$INSTALL_DIR/com.bridgeswarm.json"
|
||||
MANIFEST_CONTENT=$(sed "s|ABSOLUTE_PATH_TO_NATIVE_HOST|$HOST_WRAPPER|g" "$MANIFEST_TEMPLATE")
|
||||
|
||||
CHROME_DIRS=(
|
||||
"$HOME/.config/google-chrome/NativeMessagingHosts"
|
||||
"$HOME/.config/chromium/NativeMessagingHosts"
|
||||
"$HOME/Library/Application Support/Google/Chrome/NativeMessagingHosts"
|
||||
"$HOME/Library/Application Support/Chromium/NativeMessagingHosts"
|
||||
)
|
||||
FIREFOX_DIR="$HOME/.mozilla/native-messaging-hosts"
|
||||
|
||||
for dir in "${CHROME_DIRS[@]}" "$FIREFOX_DIR"; do
|
||||
mkdir -p "$dir" 2>/dev/null
|
||||
echo "$MANIFEST_CONTENT" > "$dir/$MANIFEST_NAME.json"
|
||||
echo " ✅ Installed: $dir/$MANIFEST_NAME.json"
|
||||
done
|
||||
|
||||
# 8. Try open browser
|
||||
echo ""
|
||||
echo "🌐 Opening browser extension page..."
|
||||
|
||||
open_page() {
|
||||
local url="$1" app="$2"
|
||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
open -a "$app" "$url" 2>/dev/null && return 0
|
||||
fi
|
||||
command -v xdg-open >/dev/null 2>&1 && xdg-open "$url" 2>/dev/null && return 0
|
||||
return 1
|
||||
}
|
||||
|
||||
open_page "chrome://extensions" "Google Chrome" || \
|
||||
open_page "chrome://extensions" "Chromium" || \
|
||||
open_page "chrome://extensions" "Microsoft Edge" || {
|
||||
echo "⚠️ Could not auto-open browser. Please open manually:"
|
||||
echo " Chrome/Edge: chrome://extensions/"
|
||||
echo " Firefox: about:addons"
|
||||
}
|
||||
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo " ✅ Installation Complete!"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
|
||||
cat << INSTALL_EOF
|
||||
|
||||
📋 NEXT STEPS - Install the Extension
|
||||
|
||||
Chrome / Edge:
|
||||
1. In the browser that opened, enable "Developer mode" (top right)
|
||||
2. Click "Unpack extension"
|
||||
3. Extract ~/Downloads/BridgeSwarm-1.0.0.zip to a folder
|
||||
4. Click "Load unpacked" and select the extracted folder
|
||||
5. Your extension ID should be: fmcenppcipeikpnpopolicnllljclmmi
|
||||
|
||||
Firefox:
|
||||
1. Go to about:addons
|
||||
2. Click the gear icon → "Install Add-on From File"
|
||||
3. Select ~/Downloads/BridgeSwarm-1.0.0.xpi
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
🧪 Test it:
|
||||
• Open: $INSTALL_DIR/examples/chat/index.html
|
||||
• In browser console: BridgeSwarm → should show the constructor
|
||||
|
||||
📁 Files:
|
||||
• Extension: ~/Downloads/BridgeSwarm-1.0.0.zip
|
||||
• Software: ~/.bridgeswarm/
|
||||
|
||||
🔄 To update later: run the same install command again
|
||||
|
||||
INSTALL_EOF
|
||||
|
||||
echo ""
|
||||
echo "📍 Extension file path copied to clipboard!"
|
||||
echo "$DOWNLOADS_DIR/BridgeSwarm-1.0.0.zip" | pbcopy 2>/dev/null || \
|
||||
echo "$DOWNLOADS_DIR/BridgeSwarm-1.0.0.zip" | xclip -sel clip 2>/dev/null || \
|
||||
echo "$DOWNLOADS_DIR/BridgeSwarm-1.0.0.zip" | xsel -ib 2>/dev/null || true
|
||||
# curl -fsSL https://git.ssh.surf/snxraven/BridgeSwarm/raw/branch/main/scripts/web-installer.sh | bash
|
||||
exec bash <(curl -fsSL https://git.ssh.surf/snxraven/BridgeSwarm/raw/branch/main/scripts/install.sh)
|
||||
|
||||
Reference in New Issue
Block a user