check.mjs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357
  1. import fs from "fs-extra";
  2. import zlib from "zlib";
  3. import path from "path";
  4. import AdmZip from "adm-zip";
  5. import fetch from "node-fetch";
  6. import proxyAgent from "https-proxy-agent";
  7. import { execSync } from "child_process";
  8. const cwd = process.cwd();
  9. const TEMP_DIR = path.join(cwd, "node_modules/.verge");
  10. const FORCE = process.argv.includes("--force");
  11. const SIDECAR_HOST = execSync("rustc -vV")
  12. .toString()
  13. .match(/(?<=host: ).+(?=\s*)/g)[0];
  14. /* ======= clash ======= */
  15. const CLASH_STORAGE_PREFIX = "https://release.dreamacro.workers.dev/";
  16. const CLASH_URL_PREFIX =
  17. "https://github.com/Dreamacro/clash/releases/download/premium/";
  18. const CLASH_LATEST_DATE = "2023.08.17";
  19. const CLASH_BACKUP_URL_PREFIX =
  20. "https://github.com/zhongfly/Clash-premium-backup/releases/download/";
  21. const CLASH_BACKUP_LATEST_DATE = "2023-09-05-gdcc8d87";
  22. //https://github.com/zhongfly/Clash-premium-backup/releases/download/2023-09-05-gdcc8d87/clash-windows-amd64-2023-09-05-gdcc8d87.zip
  23. //https://github.com/zhongfly/Clash-premium-backup/releases/download/2023-09-05-gdcc8d87/clash-windows-amd64-n2023-09-05-gdcc8d87.zip
  24. const CLASH_MAP = {
  25. "win32-x64": "clash-windows-amd64",
  26. "darwin-x64": "clash-darwin-amd64",
  27. "darwin-arm64": "clash-darwin-arm64",
  28. "linux-x64": "clash-linux-amd64",
  29. "linux-arm64": "clash-linux-arm64",
  30. };
  31. /* ======= clash meta ======= */
  32. const META_URL_PREFIX = `https://github.com/MetaCubeX/Clash.Meta/releases/download/`;
  33. const META_VERSION = "v1.16.0";
  34. const META_MAP = {
  35. "win32-x64": "clash.meta-windows-amd64-compatible",
  36. "darwin-x64": "clash.meta-darwin-amd64",
  37. "darwin-arm64": "clash.meta-darwin-arm64",
  38. "linux-x64": "clash.meta-linux-amd64-compatible",
  39. "linux-arm64": "clash.meta-linux-arm64",
  40. };
  41. /**
  42. * check available
  43. */
  44. const { platform, arch } = process;
  45. if (!CLASH_MAP[`${platform}-${arch}`]) {
  46. throw new Error(`clash unsupported platform "${platform}-${arch}"`);
  47. }
  48. if (!META_MAP[`${platform}-${arch}`]) {
  49. throw new Error(`clash meta unsupported platform "${platform}-${arch}"`);
  50. }
  51. function clash() {
  52. const name = CLASH_MAP[`${platform}-${arch}`];
  53. const isWin = platform === "win32";
  54. const urlExt = isWin ? "zip" : "gz";
  55. const downloadURL = `${CLASH_URL_PREFIX}${name}-${CLASH_LATEST_DATE}.${urlExt}`;
  56. const exeFile = `${name}${isWin ? ".exe" : ""}`;
  57. const zipFile = `${name}.${urlExt}`;
  58. return {
  59. name: "clash",
  60. targetFile: `clash-${SIDECAR_HOST}${isWin ? ".exe" : ""}`,
  61. exeFile,
  62. zipFile,
  63. downloadURL,
  64. };
  65. }
  66. function clashBackup() {
  67. const name = CLASH_MAP[`${platform}-${arch}`];
  68. const isWin = platform === "win32";
  69. const urlExt = isWin ? "zip" : "gz";
  70. const downloadURL = `${CLASH_BACKUP_URL_PREFIX}${CLASH_BACKUP_LATEST_DATE}/${name}-n${CLASH_BACKUP_LATEST_DATE}.${urlExt}`;
  71. const exeFile = `${name}${isWin ? ".exe" : ""}`;
  72. const zipFile = `${name}.${urlExt}`;
  73. return {
  74. name: "clash",
  75. targetFile: `clash-${SIDECAR_HOST}${isWin ? ".exe" : ""}`,
  76. exeFile,
  77. zipFile,
  78. downloadURL,
  79. };
  80. }
  81. function clashS3() {
  82. const name = CLASH_MAP[`${platform}-${arch}`];
  83. const isWin = platform === "win32";
  84. const urlExt = isWin ? "zip" : "gz";
  85. const downloadURL = `${CLASH_STORAGE_PREFIX}${CLASH_LATEST_DATE}/${name}-${CLASH_LATEST_DATE}.${urlExt}`;
  86. const exeFile = `${name}${isWin ? ".exe" : ""}`;
  87. const zipFile = `${name}.${urlExt}`;
  88. return {
  89. name: "clash",
  90. targetFile: `clash-${SIDECAR_HOST}${isWin ? ".exe" : ""}`,
  91. exeFile,
  92. zipFile,
  93. downloadURL,
  94. };
  95. }
  96. function clashMeta() {
  97. const name = META_MAP[`${platform}-${arch}`];
  98. const isWin = platform === "win32";
  99. const urlExt = isWin ? "zip" : "gz";
  100. const downloadURL = `${META_URL_PREFIX}${META_VERSION}/${name}-${META_VERSION}.${urlExt}`;
  101. const exeFile = `${name}${isWin ? ".exe" : ""}`;
  102. const zipFile = `${name}-${META_VERSION}.${urlExt}`;
  103. return {
  104. name: "clash-meta",
  105. targetFile: `clash-meta-${SIDECAR_HOST}${isWin ? ".exe" : ""}`,
  106. exeFile,
  107. zipFile,
  108. downloadURL,
  109. };
  110. }
  111. /**
  112. * download sidecar and rename
  113. */
  114. async function resolveSidecar(binInfo) {
  115. const { name, targetFile, zipFile, exeFile, downloadURL } = binInfo;
  116. const sidecarDir = path.join(cwd, "src-tauri", "sidecar");
  117. const sidecarPath = path.join(sidecarDir, targetFile);
  118. await fs.mkdirp(sidecarDir);
  119. if (!FORCE && (await fs.pathExists(sidecarPath))) return;
  120. const tempDir = path.join(TEMP_DIR, name);
  121. const tempZip = path.join(tempDir, zipFile);
  122. const tempExe = path.join(tempDir, exeFile);
  123. await fs.mkdirp(tempDir);
  124. try {
  125. if (!(await fs.pathExists(tempZip))) {
  126. await downloadFile(downloadURL, tempZip);
  127. }
  128. if (zipFile.endsWith(".zip")) {
  129. const zip = new AdmZip(tempZip);
  130. zip.getEntries().forEach((entry) => {
  131. console.log(`[DEBUG]: "${name}" entry name`, entry.entryName);
  132. });
  133. zip.extractAllTo(tempDir, true);
  134. await fs.rename(tempExe, sidecarPath);
  135. console.log(`[INFO]: "${name}" unzip finished`);
  136. } else {
  137. // gz
  138. const readStream = fs.createReadStream(tempZip);
  139. const writeStream = fs.createWriteStream(sidecarPath);
  140. await new Promise((resolve, reject) => {
  141. const onError = (error) => {
  142. console.error(`[ERROR]: "${name}" gz failed:`, error.message);
  143. reject(error);
  144. };
  145. readStream
  146. .pipe(zlib.createGunzip().on("error", onError))
  147. .pipe(writeStream)
  148. .on("finish", () => {
  149. console.log(`[INFO]: "${name}" gunzip finished`);
  150. execSync(`chmod 755 ${sidecarPath}`);
  151. console.log(`[INFO]: "${name}" chmod binary finished`);
  152. resolve();
  153. })
  154. .on("error", onError);
  155. });
  156. }
  157. } catch (err) {
  158. // 需要删除文件
  159. await fs.remove(sidecarPath);
  160. throw err;
  161. } finally {
  162. // delete temp dir
  163. await fs.remove(tempDir);
  164. }
  165. }
  166. /**
  167. * prepare clash core
  168. * if the core version is not updated in time, use S3 storage as a backup.
  169. */
  170. async function resolveClash() {
  171. try {
  172. return await resolveSidecar(clash());
  173. } catch {
  174. console.log(`[WARN]: clash core needs to be updated`);
  175. return await resolveSidecar(clashS3());
  176. }
  177. }
  178. /**
  179. * only Windows
  180. * get the wintun.dll (not required)
  181. */
  182. async function resolveWintun() {
  183. const { platform } = process;
  184. if (platform !== "win32") return;
  185. const url = "https://www.wintun.net/builds/wintun-0.14.1.zip";
  186. const tempDir = path.join(TEMP_DIR, "wintun");
  187. const tempZip = path.join(tempDir, "wintun.zip");
  188. const wintunPath = path.join(tempDir, "wintun/bin/amd64/wintun.dll");
  189. const targetPath = path.join(cwd, "src-tauri/resources", "wintun.dll");
  190. if (!FORCE && (await fs.pathExists(targetPath))) return;
  191. await fs.mkdirp(tempDir);
  192. if (!(await fs.pathExists(tempZip))) {
  193. await downloadFile(url, tempZip);
  194. }
  195. // unzip
  196. const zip = new AdmZip(tempZip);
  197. zip.extractAllTo(tempDir, true);
  198. if (!(await fs.pathExists(wintunPath))) {
  199. throw new Error(`path not found "${wintunPath}"`);
  200. }
  201. await fs.rename(wintunPath, targetPath);
  202. await fs.remove(tempDir);
  203. console.log(`[INFO]: resolve wintun.dll finished`);
  204. }
  205. /**
  206. * download the file to the resources dir
  207. */
  208. async function resolveResource(binInfo) {
  209. const { file, downloadURL } = binInfo;
  210. const resDir = path.join(cwd, "src-tauri/resources");
  211. const targetPath = path.join(resDir, file);
  212. if (!FORCE && (await fs.pathExists(targetPath))) return;
  213. await fs.mkdirp(resDir);
  214. await downloadFile(downloadURL, targetPath);
  215. console.log(`[INFO]: ${file} finished`);
  216. }
  217. /**
  218. * download file and save to `path`
  219. */
  220. async function downloadFile(url, path) {
  221. const options = {};
  222. const httpProxy =
  223. process.env.HTTP_PROXY ||
  224. process.env.http_proxy ||
  225. process.env.HTTPS_PROXY ||
  226. process.env.https_proxy;
  227. if (httpProxy) {
  228. options.agent = proxyAgent(httpProxy);
  229. }
  230. const response = await fetch(url, {
  231. ...options,
  232. method: "GET",
  233. headers: { "Content-Type": "application/octet-stream" },
  234. });
  235. const buffer = await response.arrayBuffer();
  236. await fs.writeFile(path, new Uint8Array(buffer));
  237. console.log(`[INFO]: download finished "${url}"`);
  238. }
  239. /**
  240. * main
  241. */
  242. const SERVICE_URL =
  243. "https://github.com/zzzgydi/clash-verge-service/releases/download/latest";
  244. const resolveService = () =>
  245. resolveResource({
  246. file: "clash-verge-service.exe",
  247. downloadURL: `${SERVICE_URL}/clash-verge-service.exe`,
  248. });
  249. const resolveInstall = () =>
  250. resolveResource({
  251. file: "install-service.exe",
  252. downloadURL: `${SERVICE_URL}/install-service.exe`,
  253. });
  254. const resolveUninstall = () =>
  255. resolveResource({
  256. file: "uninstall-service.exe",
  257. downloadURL: `${SERVICE_URL}/uninstall-service.exe`,
  258. });
  259. const resolveMmdb = () =>
  260. resolveResource({
  261. file: "Country.mmdb",
  262. downloadURL: `https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/country.mmdb`,
  263. });
  264. const resolveGeosite = () =>
  265. resolveResource({
  266. file: "geosite.dat",
  267. downloadURL: `https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geosite.dat`,
  268. });
  269. const resolveGeoIP = () =>
  270. resolveResource({
  271. file: "geoip.dat",
  272. downloadURL: `https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.dat`,
  273. });
  274. const tasks = [
  275. { name: "clash", func: () => resolveSidecar(clashBackup()), retry: 5 },
  276. { name: "clash-meta", func: () => resolveSidecar(clashMeta()), retry: 5 },
  277. { name: "wintun", func: resolveWintun, retry: 5, winOnly: true },
  278. { name: "service", func: resolveService, retry: 5, winOnly: true },
  279. { name: "install", func: resolveInstall, retry: 5, winOnly: true },
  280. { name: "uninstall", func: resolveUninstall, retry: 5, winOnly: true },
  281. { name: "mmdb", func: resolveMmdb, retry: 5 },
  282. { name: "geosite", func: resolveGeosite, retry: 5 },
  283. { name: "geoip", func: resolveGeoIP, retry: 5 },
  284. ];
  285. async function runTask() {
  286. const task = tasks.shift();
  287. if (!task) return;
  288. if (task.winOnly && process.platform !== "win32") return runTask();
  289. for (let i = 0; i < task.retry; i++) {
  290. try {
  291. await task.func();
  292. break;
  293. } catch (err) {
  294. console.error(`[ERROR]: task::${task.name} try ${i} ==`, err.message);
  295. if (i === task.retry - 1) throw err;
  296. }
  297. }
  298. return runTask();
  299. }
  300. runTask();
  301. runTask();