ai-or-die 0.1.84 → 0.1.85
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/bin/ai-or-die.js +5 -4
- package/package.json +1 -1
- package/src/mesh-manager.js +18 -1
- package/src/utils/sidecar-installer.js +109 -0
package/bin/ai-or-die.js
CHANGED
|
@@ -89,10 +89,10 @@ async function main() {
|
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
// Handle authentication logic
|
|
92
|
-
// Tunnel mode disables auth — the tunnel controls access
|
|
93
|
-
//
|
|
92
|
+
// Tunnel mode disables auth — the tunnel controls access — even with --mesh.
|
|
93
|
+
// (--mesh alone keeps the Bearer token on; --tunnel always wins.)
|
|
94
94
|
let authToken = null;
|
|
95
|
-
let noAuth = options.disableAuth === true ||
|
|
95
|
+
let noAuth = options.disableAuth === true || options.tunnel === true;
|
|
96
96
|
|
|
97
97
|
if (!noAuth) {
|
|
98
98
|
if (options.auth) {
|
|
@@ -144,7 +144,8 @@ async function main() {
|
|
|
144
144
|
console.log(`Plan: ${options.plan}`);
|
|
145
145
|
console.log(`Aliases: Claude → "${serverOptions.claudeAlias}", Codex → "${serverOptions.codexAlias}", Copilot → "${serverOptions.copilotAlias}", Gemini → "${serverOptions.geminiAlias}", Terminal → "${serverOptions.terminalAlias}"`);
|
|
146
146
|
|
|
147
|
-
// Display authentication status
|
|
147
|
+
// Display authentication status. --tunnel disables auth (tunnel controls
|
|
148
|
+
// access) even with --mesh; --mesh alone keeps the Bearer token on.
|
|
148
149
|
if (options.tunnel) {
|
|
149
150
|
console.log('\n🌍 TUNNEL MODE — authentication disabled (tunnel controls access)');
|
|
150
151
|
} else if (noAuth) {
|
package/package.json
CHANGED
package/src/mesh-manager.js
CHANGED
|
@@ -53,12 +53,29 @@ class MeshManager {
|
|
|
53
53
|
async start() {
|
|
54
54
|
console.log('\n Connecting mesh (Tailscale userspace)...');
|
|
55
55
|
this.stopping = false;
|
|
56
|
-
if (!fs.existsSync(this.sidecar)) {
|
|
56
|
+
if (!fs.existsSync(this.sidecar)) {
|
|
57
|
+
if (!(await this._ensureSidecar())) { this._printMissing(); return; }
|
|
58
|
+
}
|
|
57
59
|
if (!this._authKey && !this._enrolled()) { this._printNotEnrolled(); return; }
|
|
58
60
|
try { fs.mkdirSync(this.stateDir, { recursive: true }); } catch (_) {}
|
|
59
61
|
await this._spawn();
|
|
60
62
|
}
|
|
61
63
|
|
|
64
|
+
/** Download + verify the sidecar from the matching release. Best-effort. */
|
|
65
|
+
async _ensureSidecar() {
|
|
66
|
+
try {
|
|
67
|
+
const { ensureSidecar } = require('./utils/sidecar-installer');
|
|
68
|
+
let version = '0.0.0';
|
|
69
|
+
try { version = require('../package.json').version; } catch (_) {}
|
|
70
|
+
console.log(' [mesh] fetching sidecar binary...');
|
|
71
|
+
await ensureSidecar(version, this.sidecar);
|
|
72
|
+
return fs.existsSync(this.sidecar);
|
|
73
|
+
} catch (e) {
|
|
74
|
+
if (this.dev) console.error(' [mesh] sidecar fetch failed:', e.message);
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
62
79
|
getStatus() {
|
|
63
80
|
return { running: this.proc !== null && !this.stopping && !!this.dnsName, publicUrl: this.dnsName ? `https://${this.dnsName}` : null };
|
|
64
81
|
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Sidecar installer — fetches the prebuilt aiordie-mesh tsnet binary for the
|
|
4
|
+
// current platform from the matching GitHub release and verifies it against the
|
|
5
|
+
// release's published SHA-256 checksums before use. Mirrors the download/verify
|
|
6
|
+
// shape of gguf-model-manager (resumable not needed — the binary is ~20MB).
|
|
7
|
+
//
|
|
8
|
+
// Asset convention (published by .github/workflows/release-on-main.yml):
|
|
9
|
+
// aiordie-mesh-<plat>-<arch>[.exe] plat: windows|linux|darwin arch: amd64|arm64
|
|
10
|
+
// aiordie-mesh-checksums.txt "<sha256> <assetname>" per line
|
|
11
|
+
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const fsp = require('fs').promises;
|
|
14
|
+
const path = require('path');
|
|
15
|
+
const os = require('os');
|
|
16
|
+
const crypto = require('crypto');
|
|
17
|
+
|
|
18
|
+
const REPO = 'animeshkundu/ai-or-die';
|
|
19
|
+
const PLAT = { win32: 'windows', linux: 'linux', darwin: 'darwin' };
|
|
20
|
+
const ARCH = { x64: 'amd64', arm64: 'arm64' };
|
|
21
|
+
|
|
22
|
+
function assetName() {
|
|
23
|
+
const plat = PLAT[process.platform];
|
|
24
|
+
const arch = ARCH[process.arch];
|
|
25
|
+
if (!plat || !arch) return null;
|
|
26
|
+
return `aiordie-mesh-${plat}-${arch}${process.platform === 'win32' ? '.exe' : ''}`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function sidecarPath() {
|
|
30
|
+
const localApp = process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local');
|
|
31
|
+
const base = process.platform === 'win32' ? path.join(localApp, 'ai-or-die') : path.join(os.homedir(), '.ai-or-die');
|
|
32
|
+
return path.join(base, 'bin', `aiordie-mesh${process.platform === 'win32' ? '.exe' : ''}`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function _sha256(file) {
|
|
36
|
+
return new Promise((resolve, reject) => {
|
|
37
|
+
const h = crypto.createHash('sha256');
|
|
38
|
+
const s = fs.createReadStream(file);
|
|
39
|
+
s.on('data', (c) => h.update(c));
|
|
40
|
+
s.on('end', () => resolve(h.digest('hex')));
|
|
41
|
+
s.on('error', reject);
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function _fetchText(url, ms = 15000) {
|
|
46
|
+
const ac = new AbortController();
|
|
47
|
+
const t = setTimeout(() => ac.abort(), ms);
|
|
48
|
+
try {
|
|
49
|
+
const r = await fetch(url, { signal: ac.signal });
|
|
50
|
+
if (!r.ok) throw new Error(`HTTP ${r.status}`);
|
|
51
|
+
return await r.text();
|
|
52
|
+
} finally { clearTimeout(t); }
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function _download(url, tmp, ms = 120000) {
|
|
56
|
+
const ac = new AbortController();
|
|
57
|
+
const t = setTimeout(() => ac.abort(), ms);
|
|
58
|
+
try {
|
|
59
|
+
const r = await fetch(url, { signal: ac.signal });
|
|
60
|
+
if (!r.ok || !r.body) throw new Error(`HTTP ${r.status}`);
|
|
61
|
+
// Exclusive create — never follow/overwrite a pre-planted file or symlink.
|
|
62
|
+
const out = fs.createWriteStream(tmp, { flags: 'wx' });
|
|
63
|
+
const reader = r.body.getReader();
|
|
64
|
+
for (;;) {
|
|
65
|
+
const { done, value } = await reader.read();
|
|
66
|
+
if (done) break;
|
|
67
|
+
await new Promise((res, rej) => out.write(value, (e) => (e ? rej(e) : res())));
|
|
68
|
+
}
|
|
69
|
+
await new Promise((res) => out.end(res));
|
|
70
|
+
} finally { clearTimeout(t); }
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Ensure the sidecar binary exists locally AND matches the release checksum;
|
|
74
|
+
// download + verify if missing or stale. Returns the path; throws on failure.
|
|
75
|
+
async function ensureSidecar(version, dest = sidecarPath()) {
|
|
76
|
+
const name = assetName();
|
|
77
|
+
if (!name) throw new Error(`unsupported platform ${process.platform}/${process.arch}`);
|
|
78
|
+
|
|
79
|
+
// Resolve the expected hash first so an existing file is also verified.
|
|
80
|
+
const baseUrl = `https://github.com/${REPO}/releases/download/v${version}`;
|
|
81
|
+
const sums = await _fetchText(`${baseUrl}/aiordie-mesh-checksums.txt`);
|
|
82
|
+
const line = sums.split('\n').find((l) => {
|
|
83
|
+
const f = l.trim().split(/\s+/)[1]; // exact filename column, not endsWith
|
|
84
|
+
return f === name;
|
|
85
|
+
});
|
|
86
|
+
if (!line) throw new Error(`no checksum for ${name} in release v${version}`);
|
|
87
|
+
const want = line.trim().split(/\s+/)[0].toLowerCase();
|
|
88
|
+
|
|
89
|
+
// Existing file: trust only if it matches; otherwise replace it.
|
|
90
|
+
if (fs.existsSync(dest)) {
|
|
91
|
+
if ((await _sha256(dest)).toLowerCase() === want) return dest;
|
|
92
|
+
try { await fsp.unlink(dest); } catch (_) {}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
await fsp.mkdir(path.dirname(dest), { recursive: true });
|
|
96
|
+
const tmp = `${dest}.${crypto.randomBytes(8).toString('hex')}.incomplete`;
|
|
97
|
+
try {
|
|
98
|
+
await _download(`${baseUrl}/${name}`, tmp);
|
|
99
|
+
// Verify BEFORE the bytes ever reach the runnable path (no TOCTOU window).
|
|
100
|
+
if ((await _sha256(tmp)).toLowerCase() !== want) throw new Error(`checksum mismatch for ${name}`);
|
|
101
|
+
if (process.platform !== 'win32') { try { await fsp.chmod(tmp, 0o755); } catch (_) {} }
|
|
102
|
+
await fsp.rename(tmp, dest);
|
|
103
|
+
} finally {
|
|
104
|
+
try { await fsp.unlink(tmp); } catch (_) {}
|
|
105
|
+
}
|
|
106
|
+
return dest;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
module.exports = { ensureSidecar, sidecarPath, assetName };
|