Skip to content

Commit 6ae7dfb

Browse files
committed
fix update bug
1 parent 7e5cc21 commit 6ae7dfb

File tree

8 files changed

+180
-50
lines changed

8 files changed

+180
-50
lines changed

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@conet.project/conet-proxy",
33

4-
"version": "0.14.1",
4+
"version": "0.15.5",
55

66
"license": "UNLICENSED",
77
"files": [
@@ -13,7 +13,7 @@
1313
"scripts": {
1414
"lint": "echo 'no linter available'",
1515
"test": "echo 'no linter available'",
16-
"build": "tsc --project ./tsconfig.build.json ; cp -r src/localServer/workers build/localServer/workers ; cp src/index.d.ts build ; cp src/favicon.ico build/localServer/workers/ ",
16+
"build": "tsc --project ./tsconfig.build.json ; cp -r src/localServer/workers build/localServer/workers; cp src/index.d.ts build ; cp src/favicon.ico build/localServer/workers/ ",
1717
"clean": "rm -rf ./node_modules ./build",
1818
"local": "node build/localServer/index",
1919
"buildRun": "tsc --project ./tsconfig.build.json && cp src/favicon.ico build/localServer/workers/ && cp src/localServer/workers/utilities/*.js build/localServer/workers/utilities && cp src/index.d.ts build && node build/localServer/index",

src/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import {Daemon} from './localServer/localServer'
33
import { proxyServer as proxyServer_class } from './localServer/proxyServer'
44
const yargs = require('yargs')
5+
56
const argv = yargs(process.argv.slice(2))
67
.usage('Usage: yarn run seguro-gateway --port [number] --path [string]')
78
.help('h')
@@ -30,7 +31,7 @@ const argv = yargs(process.argv.slice(2))
3031
.argv
3132

3233
let PORT = 3001
33-
let PATH = ''
34+
let PATH = __dirname
3435

3536
export const launchDaemon = (port: number, path: string) => {
3637
new Daemon ( port, path )

src/localServer/define.d.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -868,4 +868,10 @@ interface assetOracle {
868868
type filterRule = {
869869
DOMAIN: string[]
870870
IP: string[]
871+
}
872+
873+
// 定义 update.json 的数据结构
874+
interface UpdateInfo {
875+
ver: string
876+
filename: string
871877
}

src/localServer/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
import {Daemon} from './localServer'
22
const port = parseInt( process.argv[2] ) || 3001
3-
new Daemon ( port, '' )
3+
new Daemon ( port, __dirname )

src/localServer/localServer.ts

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,10 @@ import {ethers} from 'ethers'
1313
import * as openpgp from 'openpgp'
1414
import os from 'node:os'
1515
import CONET_Guardian_NodeInfo_ABI from './CONET_Guardian_NodeInfo_ABI.json'
16-
import {runUpdater} from './updateProcess'
17-
import { app as electronApp } from 'electron'
16+
import {runUpdater, readUpdateInfo} from './updateProcess'
1817
import fs from 'node:fs'
1918

20-
const ver = '0.1.4'
19+
const ver = '0.1.5'
2120

2221
const getLocalNetworkIpaddress = () => {
2322
const interfaceAll = os.networkInterfaces()
@@ -173,15 +172,19 @@ const joinMetadata = (metadata: any ) => {
173172
let _proxyServer: proxyServer
174173

175174

176-
const startSilentPass = (vpnObj: Native_StartVPNObj) => {
175+
const startSilentPass = (vpnObj: Native_StartVPNObj, currentVer: UpdateInfo, reactFolder: string) => {
177176
logger(inspect(vpnObj, false, 3, true))
178177

179178
_proxyServer = new proxyServer((3002).toString(), vpnObj.entryNodes, vpnObj.exitNode, vpnObj.privateKey, true, '')
180-
runUpdater(vpnObj.entryNodes)
179+
runUpdater(vpnObj.entryNodes, currentVer, reactFolder)
181180
return true
182181
}
183182

184183

184+
185+
186+
187+
185188
const otherRespon = ( body: string| Buffer, _status: number ) => {
186189
const Ranges = ( _status === 200 ) ? 'Accept-Ranges: bytes\r\n' : ''
187190
const Content = ( _status === 200 ) ? `Content-Type: text/html; charset=utf-8\r\n` : 'Content-Type: text/html\r\n'
@@ -225,8 +228,8 @@ export class Daemon {
225228
private connect_peer_pool: any [] = []
226229
private worker_command_waiting_pool: Map<string, express.Response> = new Map()
227230
private logStram = ''
228-
229-
constructor ( private PORT = 3000, private reactBuildFolder: string ) {
231+
private currentVer:UpdateInfo|null
232+
constructor ( private PORT = 3000, private reactBuildFolder: string) {
230233
this.initialize()
231234
}
232235

@@ -253,26 +256,23 @@ export class Daemon {
253256
return ws.send ( JSON.stringify ( sendData ))
254257
}
255258

256-
private initialize = () => {
259+
private initialize = async () => {
257260
// --- 关键逻辑开始 ---
258261

259262
// 1. 定义默认路径(只读的应用包内部)
260263
const defaultPath = join(__dirname, 'workers')
261264

262265
// 2. 定义更新路径(可写的 userData 目录内部)
263-
const userDataPath = electronApp.getPath('userData');
264-
const updatedPath = join(userDataPath, 'workers');
266+
const userDataPath = this.reactBuildFolder
267+
const updatedPath = join(userDataPath, 'workers')
265268

266269
// 3. 检查更新路径是否存在,然后决定使用哪个路径
267270
// 如果 updatedPath 存在,就用它;否则,回退到 defaultPath。
268-
const staticFolder = fs.existsSync(updatedPath) ? updatedPath : defaultPath;
269-
270-
// 确保我们选择的目录确实存在(主要针对首次启动时 defaultPath 的情况)
271-
if (!fs.existsSync(staticFolder)) {
272-
// 如果连默认目录都不存在,可能需要从别处复制或创建
273-
logger(Colors.red(`CRITICAL ERROR: Static folder not found at ${staticFolder}`));
274-
// 在这种情况下,可以考虑从一个备用位置将默认 `workers` 内容复制到 `updatedPath`
275-
// fs.cpSync(join(__dirname, 'initial_workers'), updatedPath, { recursive: true });
271+
let staticFolder = fs.existsSync(updatedPath) ? updatedPath : defaultPath
272+
this.currentVer = await readUpdateInfo(staticFolder, '')
273+
if (!this.currentVer) {
274+
staticFolder = defaultPath
275+
logger(Colors.red(`updatedPath ERROR, go back to defaultPath ${defaultPath}`))
276276
}
277277

278278
// --- 关键逻辑结束 ---
@@ -499,8 +499,10 @@ export class Daemon {
499499
if (!vpnObj) {
500500
return res.status(400).send({ error: "No country selected" })
501501
}
502-
503-
startSilentPass (vpnObj)
502+
if (this.currentVer) {
503+
startSilentPass (vpnObj, this.currentVer, this.reactBuildFolder)
504+
}
505+
504506

505507
res.status(200).json({}).end()
506508

@@ -566,8 +568,6 @@ export class Daemon {
566568
}
567569

568570

569-
new Daemon(3001, '')
570-
571571

572572
// test
573573
// curl -v localhost:3001/getAllRegions

src/localServer/proxy-server.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,5 +184,4 @@ const test = () => {
184184
new ProxyServer(3002, layerMinus)
185185
}
186186

187-
test ()
188187

src/localServer/proxyServer.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -575,5 +575,3 @@ const test = () => {
575575

576576
new proxyServer('3003',entryNodes, egressNodes, privateKey, true, '')
577577
}
578-
579-
test ()

src/localServer/updateProcess.ts

Lines changed: 147 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,11 @@ import {join} from 'node:path'
33
import fs from 'node:fs'
44
import * as unzipper from 'unzipper'
55
import http from 'node:http'
6-
import currentVer from './workers/update.json'
76
import { inspect } from "node:util"
8-
import { app as electronApp } from 'electron'
9-
// 定义 update.json 的数据结构
10-
interface UpdateInfo {
11-
ver: string
12-
filename: string
13-
}
7+
import os from 'node:os'
8+
149
const MAX_REDIRECTS = 5 // 防止无限重定向
10+
const tempUpdatePath = join(os.tmpdir(), `conet-update-${Date.now()}`)
1511
/**
1612
* 辅助函数:下载文件并流式解压到指定路径
1713
* @param downloadUrl 文件的URL
@@ -98,10 +94,103 @@ const getRandomNode = (nodes: nodes_info[]): nodes_info => {
9894
return nodes[randomIndex]
9995
}
10096

97+
/**
98+
* 验证解压后的文件夹内容是否符合预期的文件结构。
99+
* @param folderPath 要检查的文件夹根路径
100+
* @returns 如果验证通过则返回 true,否则返回 false
101+
*/
102+
const validateUpdateContents = async (folderPath: string, ver: string): Promise<boolean> => {
103+
try {
104+
// 1. 检查根目录下的 index.html
105+
const hasIndexHtml = fs.existsSync(join(folderPath, 'index.html'))
106+
if (!hasIndexHtml) {
107+
logger('🔴 验证失败: 未找到 index.html')
108+
return false;
109+
}
110+
111+
// 2. 检查 static/js 目录下的 main.*.js
112+
const jsFolderPath = join(folderPath, 'static', 'js')
113+
if (!fs.existsSync(jsFolderPath)) {
114+
logger('🔴 验证失败: 未找到目录 static/js')
115+
return false;
116+
}
117+
const jsFiles = fs.readdirSync(jsFolderPath);
118+
const hasMainJs = jsFiles.some(file => /^main\..+\.js$/.test(file))
119+
if (!hasMainJs) {
120+
logger('🔴 验证失败: 在 static/js/ 中未找到 main.*.js 文件')
121+
return false;
122+
}
123+
124+
// 3. 检查 static/css 目录下的 main.*.css
125+
const cssFolderPath = join(folderPath, 'static', 'css')
126+
if (!fs.existsSync(cssFolderPath)) {
127+
logger('🔴 验证失败: 未找到目录 static/css')
128+
return false;
129+
}
130+
const cssFiles = fs.readdirSync(cssFolderPath)
131+
const hasMainCss = cssFiles.some(file => /^main\..+\.css$/.test(file))
132+
if (!hasMainCss) {
133+
logger('🔴 验证失败: 在 static/css/ 中未找到 main.*.css 文件')
134+
return false
135+
}
136+
137+
await readUpdateInfo(folderPath, ver)
138+
139+
logger('✅ 更新内容验证通过,文件结构正确!')
140+
return true
141+
} catch (error) {
142+
logger('🔴 验证过程中发生错误:', error)
143+
return false
144+
}
145+
}
146+
147+
/**
148+
* Reads and parses the update.json file from a specified folder.
149+
*
150+
* @param staticFolder The path to the directory containing update.json.
151+
* @returns A Promise that resolves to the UpdateInfo object, or null if an error occurs.
152+
*/
153+
export const readUpdateInfo = async (staticFolder: string, ver: string): Promise<UpdateInfo | null> => new Promise(executor => {
154+
// Construct the full path to the update.json file
155+
const filePath = join(staticFolder, 'update.json')
156+
157+
158+
// Read the file's content asynchronously
159+
fs.readFile(filePath, 'utf8', (err, fileContent) => {
160+
// Parse the JSON string into an object and cast it to the UpdateInfo type
161+
if (err) {
162+
logger(`readUpdateInfo get ${filePath} Error!`)
163+
164+
if (!ver) {
165+
return executor(null)
166+
}
167+
168+
const data: UpdateInfo = {
169+
ver,
170+
filename: `${ver}.zip`
171+
}
172+
fs.writeFileSync(filePath, JSON.stringify(data), 'utf8')
173+
return executor(data)
174+
}
175+
176+
try {
177+
const updateData: UpdateInfo = JSON.parse(fileContent)
178+
logger(`readUpdateInfo success`, inspect(updateData, false, 3, true))
179+
return executor(updateData)
180+
}catch (ex) {
181+
logger(`readUpdateInfo JSON.parse(fileContent) ${fileContent} Error!`)
182+
return executor(null)
183+
}
184+
185+
})
186+
187+
})
188+
189+
101190
/**
102191
* 主更新函数
103192
*/
104-
export const runUpdater = async (nodes: nodes_info[] ) => {
193+
export const runUpdater = async (nodes: nodes_info[], currentVer: UpdateInfo, reactFolder: string ) => {
105194

106195

107196
logger('🚀 开始执行动态节点更新程序...')
@@ -138,24 +227,61 @@ export const runUpdater = async (nodes: nodes_info[] ) => {
138227
const downloadUrl = `${baseApiUrl}${updateInfo.filename}`
139228
// 关键改动 2: 不再使用 __dirname,而是使用 userData 目录
140229
// 这确保了我们将文件解压到 Electron 应用的可写区域
141-
const userDataPath = electronApp.getPath('userData');
142-
const extractPath = join(userDataPath, 'workers');
230+
const userDataPath = reactFolder
231+
const extractPath = join(userDataPath, 'workers')
232+
// 定义最终的工作目录和临时的更新目录
233+
const finalWorkersPath = join(reactFolder, 'workers');
234+
// 1. 解压到临时目录
235+
fs.mkdirSync(tempUpdatePath, { recursive: true })
143236

144-
logger(`⏳ 正在从 ${downloadUrl} 下载并解压...`);
145-
logger(`将解压到可写目录: ${extractPath}`); // 日志信息更新,更准确
146237

238+
logger(`⏳ 正在从 ${downloadUrl} 下载并解压...`);
239+
240+
logger(`创建临时更新目录: ${tempUpdatePath}`)
147241
// 确保目标目录存在
148242
// 这个逻辑在这里依然有效,它会创建 userData/workers 目录(如果尚不存在)
149243
if (!fs.existsSync(extractPath)) {
150-
fs.mkdirSync(extractPath, { recursive: true });
244+
fs.mkdirSync(extractPath, { recursive: true })
151245
}
152246

153-
await downloadAndUnzip(downloadUrl, extractPath)
154-
logger(`🎉 成功下载并解压文件到 ${extractPath}`)
247+
await downloadAndUnzip(downloadUrl, tempUpdatePath)
248+
logger(`🎉 成功下载并解压文件到 ${tempUpdatePath}`)
249+
250+
// 2. 验证内容
251+
if (!(await validateUpdateContents(tempUpdatePath, currentVer.ver))) {
252+
throw new Error('下载的内容无效或不完整,已终止更新。')
253+
}
254+
255+
256+
// 3. 原子化替换
257+
logger('准备替换工作目录...')
258+
const backupPath = `${finalWorkersPath}_old_${Date.now()}`
259+
260+
// a. 如果旧目录存在,将其重命名为备份(这是一个原子操作)
261+
if (fs.existsSync(finalWorkersPath)) {
262+
fs.renameSync(finalWorkersPath, backupPath)
263+
logger(`旧目录已备份到: ${backupPath}`)
264+
}
265+
266+
// b. 将新的、已验证的临时目录重命名为工作目录(这是另一个原子操作)
267+
fs.renameSync(tempUpdatePath, finalWorkersPath);
268+
logger(`✅ 更新成功!工作目录已指向新版本: ${finalWorkersPath}`)
269+
270+
// c. 如果存在备份,则删除备份
271+
if (fs.existsSync(backupPath)) {
272+
fs.rmSync(backupPath, { recursive: true, force: true })
273+
logger(`旧的备份目录已清理。`)
274+
}
155275

156276
} catch (error) {
157277
console.error('❌ 更新过程中发生错误:', error instanceof Error ? error.message : error)
158-
}
278+
} finally {
279+
// 4. 清理:无论成功或失败,最后都尝试删除临时目录
280+
if (fs.existsSync(tempUpdatePath)) {
281+
fs.rmSync(tempUpdatePath, { recursive: true, force: true })
282+
logger(`临时更新目录已清理。`)
283+
}
284+
}
159285
}
160286
// 执行更新程序
161287

@@ -166,18 +292,18 @@ export const runUpdater = async (nodes: nodes_info[] ) => {
166292
* @returns 如果 newVer 比 oldVer 新,则返回 true;否则返回 false。
167293
*/
168294
function isNewerVersion(oldVer: string, newVer: string): boolean {
169-
const oldParts = oldVer.split('.').map(Number);
170-
const newParts = newVer.split('.').map(Number);
295+
const oldParts = oldVer.split('.').map(Number)
296+
const newParts = newVer.split('.').map(Number)
171297

172298
for (let i = 0; i < oldParts.length; i++) {
173299
if (newParts[i] > oldParts[i]) {
174-
return true;
300+
return true
175301
}
176302
if (newParts[i] < oldParts[i]) {
177-
return false;
303+
return false
178304
}
179305
}
180306

181-
return false; // 如果版本号完全相同,则不是更新的版本
307+
return false // 如果版本号完全相同,则不是更新的版本
182308
}
183309

0 commit comments

Comments
 (0)