-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpostinstall.js
More file actions
150 lines (123 loc) · 4.71 KB
/
postinstall.js
File metadata and controls
150 lines (123 loc) · 4.71 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
#!/usr/bin/env node
"use strict";
const { execSync } = require("child_process");
const crypto = require("crypto");
const fs = require("fs");
const path = require("path");
const https = require("https");
const http = require("http");
const VERSION = require("./package.json").version;
const REPO = "GeiserX/cashpilot-mcp";
const BIN_NAME = process.platform === "win32" ? "cashpilot-mcp.exe" : "cashpilot-mcp";
const BIN_DIR = path.join(__dirname, "bin");
const BIN_PATH = path.join(BIN_DIR, BIN_NAME);
function getPlatformArch() {
const platform = process.platform;
const arch = process.arch;
const platformMap = { darwin: "darwin", linux: "linux", win32: "windows" };
const archMap = { x64: "amd64", arm64: "arm64" };
const p = platformMap[platform];
const a = archMap[arch];
if (!p || !a) {
throw new Error(`Unsupported platform: ${platform}-${arch}`);
}
return { platform: p, arch: a };
}
function getAssetName() {
const { platform, arch } = getPlatformArch();
const ext = platform === "windows" ? "zip" : "tar.gz";
return `cashpilot-mcp_${VERSION}_${platform}_${arch}.${ext}`;
}
const MAX_REDIRECTS = 10;
const REQUEST_TIMEOUT_MS = 30_000;
function downloadFile(url, redirectCount = 0) {
return new Promise((resolve, reject) => {
if (redirectCount > MAX_REDIRECTS) {
return reject(new Error(`Too many redirects (>${MAX_REDIRECTS})`));
}
const get = url.startsWith("https") ? https.get : http.get;
const req = get(url, { timeout: REQUEST_TIMEOUT_MS }, (res) => {
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
return downloadFile(res.headers.location, redirectCount + 1).then(resolve, reject);
}
if (res.statusCode !== 200) {
return reject(new Error(`Download failed: HTTP ${res.statusCode}`));
}
const chunks = [];
res.on("data", (chunk) => chunks.push(chunk));
res.on("end", () => resolve(Buffer.concat(chunks)));
res.on("error", reject);
});
req.on("timeout", () => {
req.destroy();
reject(new Error(`Request timed out after ${REQUEST_TIMEOUT_MS}ms`));
});
req.on("error", reject);
});
}
async function verifyChecksum(buffer, assetName, releaseUrlBase) {
const checksumsUrl = `${releaseUrlBase}/checksums.txt`;
console.log("Downloading checksums.txt for verification...");
const checksumsBuffer = await downloadFile(checksumsUrl);
const checksumsText = checksumsBuffer.toString("utf-8");
const actualHash = crypto.createHash("sha256").update(buffer).digest("hex");
const expectedLine = checksumsText
.split("\n")
.find((line) => line.endsWith(" " + assetName) || line.endsWith(" " + assetName));
if (!expectedLine) {
throw new Error(
`Checksum verification failed: no entry for ${assetName} in checksums.txt`
);
}
const expectedHash = expectedLine.trim().split(/\s+/)[0];
if (actualHash !== expectedHash) {
throw new Error(
`Checksum mismatch for ${assetName}:\n expected: ${expectedHash}\n actual: ${actualHash}`
);
}
console.log("SHA-256 checksum verified.");
}
async function extract(buffer, assetName) {
fs.mkdirSync(BIN_DIR, { recursive: true });
if (assetName.endsWith(".zip")) {
// .zip extraction: use PowerShell on Windows, unzip on Unix
const tmpZip = path.join(BIN_DIR, "tmp.zip");
fs.writeFileSync(tmpZip, buffer);
if (process.platform === "win32") {
execSync(
`powershell -NoProfile -Command "Expand-Archive -Force -Path '${tmpZip}' -DestinationPath '${BIN_DIR}'"`,
{ stdio: "ignore" }
);
} else {
execSync(`unzip -o "${tmpZip}" "${BIN_NAME}" -d "${BIN_DIR}"`, { stdio: "ignore" });
}
fs.unlinkSync(tmpZip);
} else {
const tmpTar = path.join(BIN_DIR, "tmp.tar.gz");
fs.writeFileSync(tmpTar, buffer);
execSync(`tar -xzf "${tmpTar}" -C "${BIN_DIR}" "${BIN_NAME}"`, { stdio: "ignore" });
fs.unlinkSync(tmpTar);
}
if (process.platform !== "win32") {
fs.chmodSync(BIN_PATH, 0o755);
}
}
async function main() {
if (fs.existsSync(BIN_PATH)) {
console.log("cashpilot-mcp binary already exists, skipping download.");
return;
}
const assetName = getAssetName();
const releaseUrlBase = `https://github.com/${REPO}/releases/download/v${VERSION}`;
const url = `${releaseUrlBase}/${assetName}`;
console.log(`Downloading cashpilot-mcp v${VERSION} for ${process.platform}-${process.arch}...`);
const buffer = await downloadFile(url);
await verifyChecksum(buffer, assetName, releaseUrlBase);
console.log("Extracting binary...");
await extract(buffer, assetName);
console.log(`Installed cashpilot-mcp to ${BIN_PATH}`);
}
main().catch((err) => {
console.error(`Failed to install cashpilot-mcp: ${err.message}`);
process.exit(1);
});