webtun 1.4.2 → 1.4.5
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/README.md +44 -30
- package/bin/webtun.js +16 -22
- package/package.json +7 -2
- package/postinstall.js +148 -61
- package/public/index.html +267 -18
- package/server.js +349 -136
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|

|
|
7
7
|

|
|
8
8
|
|
|
9
|
-
**Access your
|
|
9
|
+
**Access your server from any browser — no VPN, no SSH client, no installing anything.**
|
|
10
10
|
|
|
11
11
|
[](install.sh)
|
|
12
12
|
[](https://github.com/unn-known1/webtun/releases/latest)
|
|
@@ -97,6 +97,17 @@ webtun
|
|
|
97
97
|
| `--help` | Show help |
|
|
98
98
|
| `--version` | Show version |
|
|
99
99
|
|
|
100
|
+
**Environment Variables:**
|
|
101
|
+
| Variable | Description |
|
|
102
|
+
|----------|-------------|
|
|
103
|
+
| `PORT` | Server port (default: 3000) |
|
|
104
|
+
| `HOST` | Bind address (default: 0.0.0.0) |
|
|
105
|
+
| `PIN` | Authentication PIN (empty = no auth) |
|
|
106
|
+
| `SHELL` | Shell to use (default: PowerShell on Windows, bash/sh elsewhere) |
|
|
107
|
+
| `WORKSPACE_ROOT` | Root directory for file operations (default: ~) |
|
|
108
|
+
| `TRUST_PROXY` | Set to `true` if behind a reverse proxy (default: loopback only) |
|
|
109
|
+
| `WEBTUN_SHELL` | Override shell on Windows (e.g., `/usr/bin/bash` for Git Bash) |
|
|
110
|
+
|
|
100
111
|
**Examples:**
|
|
101
112
|
```bash
|
|
102
113
|
npx webtun # Start on port 3000
|
|
@@ -148,35 +159,35 @@ npm start
|
|
|
148
159
|
## Architecture
|
|
149
160
|
|
|
150
161
|
```
|
|
151
|
-
|
|
152
|
-
│
|
|
153
|
-
│
|
|
154
|
-
│ │Terminal │ │
|
|
155
|
-
│ │
|
|
156
|
-
│
|
|
157
|
-
│
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
│
|
|
163
|
-
│
|
|
164
|
-
│ │ WebSocket
|
|
165
|
-
│ │
|
|
166
|
-
│
|
|
167
|
-
│
|
|
168
|
-
│
|
|
169
|
-
│
|
|
170
|
-
│ │ node-pty│ │Local Filesystem│ │ cloudflared daemon
|
|
171
|
-
│ │
|
|
172
|
-
│
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
162
|
+
┌────────────────────────────────────────────────────────────────┐
|
|
163
|
+
│ Your Browser │
|
|
164
|
+
│ ┌──────────┐ ┌───────────┐ ┌─────────────┐ ┌───────────┐ │
|
|
165
|
+
│ │ Terminal │ │ File │ │ Tunnel │ │ Settings │ │
|
|
166
|
+
│ │ (xterm) │ │ Explorer │ │ Manager │ │ Panel │ │
|
|
167
|
+
│ └─────┬────┘ └─────┬─────┘ └──────┬──────┘ └─────┬─────┘ │
|
|
168
|
+
│ │ │ │ │ │
|
|
169
|
+
└────────┼─────────────┼───────────────┼───────────────┼─────────┘
|
|
170
|
+
│ WebSocket │ REST API │ │
|
|
171
|
+
▼ ▼ ▼ │
|
|
172
|
+
┌────────────────────────────────────────────────────────────────┐
|
|
173
|
+
│ Node.js Server │
|
|
174
|
+
│ ┌─────────────┐ ┌──────────────┐ ┌───────────────────────┐ │
|
|
175
|
+
│ │ WebSocket │ │ File API │ │ Cloudflare Tunnel │ │
|
|
176
|
+
│ │ Handler │ │ (read/write) │ │ Manager │ │
|
|
177
|
+
│ └──────┬──────┘ └──────┬───────┘ └───────────┬───────────┘ │
|
|
178
|
+
│ │ │ │ │
|
|
179
|
+
│ ▼ ▼ ▼ │
|
|
180
|
+
│ ┌──────────┐ ┌──────────────────┐ ┌──────────────────────┐ │
|
|
181
|
+
│ │ node-pty │ │ Local Filesystem │ │ cloudflared daemon │ │
|
|
182
|
+
│ │ (shell) │ │ (your server) │ │ (exposes to internet)│ │
|
|
183
|
+
│ └──────────┘ └──────────────────┘ └──────────────────────┘ │
|
|
184
|
+
└────────────────────────────────────────────────────────────────┘
|
|
185
|
+
│
|
|
186
|
+
▼
|
|
187
|
+
┌─────────────────┐
|
|
188
|
+
│ Internet Users │
|
|
189
|
+
│ (HTTPS URL) │
|
|
190
|
+
└─────────────────┘
|
|
180
191
|
```
|
|
181
192
|
|
|
182
193
|
---
|
|
@@ -231,6 +242,9 @@ kill $(pgrep -f 'cloudflared tunnel')
|
|
|
231
242
|
| Permission denied on shell | Ensure user has shell access: `chsh -s /bin/bash` |
|
|
232
243
|
| File upload fails | Check `public/uploads/` permissions: `chmod 755 public/uploads/` |
|
|
233
244
|
| Port 3000 in use | Change port: `PORT=3001 npm start` |
|
|
245
|
+
| Windows terminal opens PowerShell instead of Git Bash | Set `WEBTUN_SHELL=/usr/bin/bash` in `.env` |
|
|
246
|
+
| Behind reverse proxy, wrong client IP | Set `TRUST_PROXY=true` in `.env` |
|
|
247
|
+
| Tunnel persists after server restart | Tunnels are auto-managed; manual cleanup: `kill $(pgrep -f 'cloudflared tunnel')` |
|
|
234
248
|
|
|
235
249
|
---
|
|
236
250
|
|
package/bin/webtun.js
CHANGED
|
@@ -23,7 +23,7 @@ function printHelp() {
|
|
|
23
23
|
PORT Server port (default: 3000)
|
|
24
24
|
HOST Bind address (default: 0.0.0.0)
|
|
25
25
|
PIN Authentication PIN (empty = no auth)
|
|
26
|
-
SHELL Shell to use (default:
|
|
26
|
+
SHELL Shell to use (default: PowerShell on Windows, bash/sh elsewhere)
|
|
27
27
|
WORKSPACE_ROOT Root directory for file operations (default: ~)
|
|
28
28
|
|
|
29
29
|
Examples:
|
|
@@ -70,25 +70,20 @@ function parseArgs(argv) {
|
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
function startTunnel(port) {
|
|
73
|
-
const {
|
|
74
|
-
const
|
|
73
|
+
const { spawn } = require('child_process');
|
|
74
|
+
const { findCloudflared } = require('../server');
|
|
75
75
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
if (os.platform() === 'win32') {
|
|
79
|
-
execSync('where cloudflared', { stdio: 'ignore' });
|
|
80
|
-
} else {
|
|
81
|
-
execSync('command -v cloudflared', { stdio: 'ignore' });
|
|
82
|
-
}
|
|
83
|
-
} catch {
|
|
76
|
+
const bin = findCloudflared();
|
|
77
|
+
if (!bin) {
|
|
84
78
|
console.error('\n Error: cloudflared is not installed.');
|
|
85
79
|
console.error(' Install it from: https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/install-and-setup/');
|
|
80
|
+
console.error(' Or re-run: npm install webtun (postinstall downloads it)');
|
|
86
81
|
return;
|
|
87
82
|
}
|
|
88
83
|
|
|
89
|
-
console.log('
|
|
84
|
+
console.log(' Starting Cloudflare Tunnel...');
|
|
90
85
|
|
|
91
|
-
const proc = spawn(
|
|
86
|
+
const proc = spawn(bin, ['tunnel', '--url', `http://localhost:${port}`], {
|
|
92
87
|
stdio: ['ignore', 'pipe', 'pipe']
|
|
93
88
|
});
|
|
94
89
|
|
|
@@ -121,23 +116,22 @@ function startTunnel(port) {
|
|
|
121
116
|
}
|
|
122
117
|
});
|
|
123
118
|
|
|
124
|
-
|
|
125
|
-
process.on('SIGINT', () => {
|
|
126
|
-
try { proc.kill('SIGTERM'); } catch {}
|
|
127
|
-
process.exit(0);
|
|
128
|
-
});
|
|
129
|
-
process.on('SIGTERM', () => {
|
|
119
|
+
const stop = () => {
|
|
130
120
|
try { proc.kill('SIGTERM'); } catch {}
|
|
131
121
|
process.exit(0);
|
|
132
|
-
}
|
|
122
|
+
};
|
|
123
|
+
process.on('SIGINT', stop);
|
|
124
|
+
process.on('SIGTERM', stop);
|
|
133
125
|
}
|
|
134
126
|
|
|
135
127
|
const opts = parseArgs(args);
|
|
136
|
-
const { startServer } = require('../server');
|
|
128
|
+
const { startServer, PORT } = require('../server');
|
|
129
|
+
|
|
130
|
+
const listenPort = opts.port || PORT;
|
|
137
131
|
|
|
138
132
|
startServer(opts).then(() => {
|
|
139
133
|
if (opts.tunnel) {
|
|
140
|
-
startTunnel(
|
|
134
|
+
startTunnel(listenPort);
|
|
141
135
|
}
|
|
142
136
|
}).catch(err => {
|
|
143
137
|
console.error('Failed to start server:', err.message);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "webtun",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.5",
|
|
4
4
|
"description": "Self-hosted web terminal with Cloudflare Tunnel, file explorer, and PWA support",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Gaurang Patel",
|
|
@@ -93,10 +93,12 @@
|
|
|
93
93
|
}
|
|
94
94
|
},
|
|
95
95
|
"dependencies": {
|
|
96
|
+
"archiver": "^8.0.0",
|
|
96
97
|
"express": "^4.18.2",
|
|
97
98
|
"multer": "^2.0.0",
|
|
98
99
|
"node-pty": "^1.0.0",
|
|
99
|
-
"ws": "^8.21.0"
|
|
100
|
+
"ws": "^8.21.0",
|
|
101
|
+
"yauzl": "^3.4.0"
|
|
100
102
|
},
|
|
101
103
|
"devDependencies": {
|
|
102
104
|
"electron": "^42.2.0",
|
|
@@ -105,5 +107,8 @@
|
|
|
105
107
|
},
|
|
106
108
|
"overrides": {
|
|
107
109
|
"form-data": "4.0.6"
|
|
110
|
+
},
|
|
111
|
+
"allowScripts": {
|
|
112
|
+
"node-pty@1.1.0": true
|
|
108
113
|
}
|
|
109
114
|
}
|
package/postinstall.js
CHANGED
|
@@ -2,6 +2,8 @@ const { execSync, spawnSync } = require('child_process');
|
|
|
2
2
|
const fs = require('fs');
|
|
3
3
|
const os = require('os');
|
|
4
4
|
const path = require('path');
|
|
5
|
+
const https = require('https');
|
|
6
|
+
const http = require('http');
|
|
5
7
|
|
|
6
8
|
// ── Rebuild node-pty if native module is missing ─────────────────────
|
|
7
9
|
function rebuildNodePty() {
|
|
@@ -18,16 +20,20 @@ function rebuildNodePty() {
|
|
|
18
20
|
spawnSync('npm', ['rebuild', 'node-pty'], {
|
|
19
21
|
cwd: __dirname,
|
|
20
22
|
stdio: 'inherit',
|
|
21
|
-
timeout: 120000
|
|
23
|
+
timeout: 120000,
|
|
24
|
+
shell: os.platform() === 'win32'
|
|
22
25
|
});
|
|
23
26
|
// Verify it worked
|
|
27
|
+
delete require.cache[require.resolve('node-pty')];
|
|
24
28
|
require('node-pty');
|
|
25
29
|
console.log(' node-pty rebuilt successfully');
|
|
26
30
|
} catch (e) {
|
|
27
31
|
console.log(' node-pty rebuild failed: ' + e.message);
|
|
28
|
-
console.log(' Terminal requires build tools:
|
|
29
|
-
console.log(' Linux:
|
|
30
|
-
console.log(' macOS:
|
|
32
|
+
console.log(' Terminal requires native build tools:');
|
|
33
|
+
console.log(' Linux: sudo apt-get install -y python3 make g++');
|
|
34
|
+
console.log(' macOS: xcode-select --install');
|
|
35
|
+
console.log(' Windows: install Visual Studio Build Tools with "Desktop development with C++"');
|
|
36
|
+
console.log(' https://visualstudio.microsoft.com/visual-cpp-build-tools/');
|
|
31
37
|
}
|
|
32
38
|
}
|
|
33
39
|
|
|
@@ -36,47 +42,101 @@ rebuildNodePty();
|
|
|
36
42
|
// ── Install cloudflared ──────────────────────────────────────────────
|
|
37
43
|
const CF_RELEASES = 'https://github.com/cloudflare/cloudflared/releases/latest/download';
|
|
38
44
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
45
|
+
function cloudflaredExists() {
|
|
46
|
+
try {
|
|
47
|
+
if (os.platform() === 'win32') {
|
|
48
|
+
execSync('where cloudflared', { stdio: 'ignore' });
|
|
49
|
+
} else {
|
|
50
|
+
execSync('command -v cloudflared', { stdio: 'ignore' });
|
|
51
|
+
}
|
|
52
|
+
return true;
|
|
53
|
+
} catch {}
|
|
54
|
+
|
|
55
|
+
const isWin = os.platform() === 'win32';
|
|
56
|
+
const name = isWin ? 'cloudflared.exe' : 'cloudflared';
|
|
57
|
+
const candidates = [
|
|
58
|
+
path.join(__dirname, name),
|
|
59
|
+
path.join(process.cwd(), name),
|
|
60
|
+
];
|
|
61
|
+
if (isWin) {
|
|
62
|
+
const localApp = process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local');
|
|
63
|
+
candidates.push(path.join(localApp, 'cloudflared', name));
|
|
64
|
+
const pf = process.env.ProgramW6432 || process.env.ProgramFiles;
|
|
65
|
+
if (pf) candidates.push(path.join(pf, 'cloudflared', name));
|
|
42
66
|
} else {
|
|
43
|
-
|
|
67
|
+
candidates.push(path.join(os.homedir(), '.local', 'bin', name));
|
|
68
|
+
candidates.push('/usr/local/bin/' + name);
|
|
44
69
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
70
|
+
return candidates.some(c => {
|
|
71
|
+
try { return fs.existsSync(c) && fs.statSync(c).isFile(); } catch { return false; }
|
|
72
|
+
});
|
|
73
|
+
}
|
|
49
74
|
|
|
50
|
-
|
|
51
|
-
linux: { x64: 'cloudflared-linux-amd64', arm64: 'cloudflared-linux-arm64', arm: 'cloudflared-linux-arm' },
|
|
52
|
-
darwin: { x64: 'cloudflared-darwin-amd64.tgz', arm64: 'cloudflared-darwin-arm64.tgz' },
|
|
53
|
-
win32: { x64: 'cloudflared-windows-amd64.exe' }
|
|
54
|
-
};
|
|
75
|
+
if (cloudflaredExists()) process.exit(0);
|
|
55
76
|
|
|
56
|
-
|
|
57
|
-
|
|
77
|
+
const platform = os.platform();
|
|
78
|
+
const arch = os.arch();
|
|
58
79
|
|
|
59
|
-
|
|
80
|
+
const files = {
|
|
81
|
+
linux: { x64: 'cloudflared-linux-amd64', arm64: 'cloudflared-linux-arm64', arm: 'cloudflared-linux-arm' },
|
|
82
|
+
darwin: { x64: 'cloudflared-darwin-amd64.tgz', arm64: 'cloudflared-darwin-arm64.tgz' },
|
|
83
|
+
win32: { x64: 'cloudflared-windows-amd64.exe', arm64: 'cloudflared-windows-amd64.exe' }
|
|
84
|
+
};
|
|
60
85
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
const tmp = path.join(tmpDir, 'cloudflared-' + process.pid + (platform === 'win32' ? '.exe' : ''));
|
|
86
|
+
const file = (files[platform] || {})[arch];
|
|
87
|
+
if (!file) process.exit(0);
|
|
64
88
|
|
|
65
|
-
|
|
66
|
-
try { fs.unlinkSync(tmp); } catch {}
|
|
67
|
-
}
|
|
89
|
+
console.log(' installing cloudflared...');
|
|
68
90
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
const r2 = spawnSync('wget', ['-q', url, '-O', tmp], { stdio: 'inherit', timeout: 60000 });
|
|
73
|
-
return r2.status === 0;
|
|
74
|
-
}
|
|
91
|
+
const url = CF_RELEASES + '/' + file;
|
|
92
|
+
const tmpDir = os.tmpdir();
|
|
93
|
+
const tmp = path.join(tmpDir, 'cloudflared-' + process.pid + (platform === 'win32' ? '.exe' : ''));
|
|
75
94
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
95
|
+
function cleanup() {
|
|
96
|
+
try { fs.unlinkSync(tmp); } catch {}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function downloadFile(downloadUrl, dest) {
|
|
100
|
+
return new Promise((resolve, reject) => {
|
|
101
|
+
const mod = downloadUrl.startsWith('https') ? https : http;
|
|
102
|
+
const req = mod.get(downloadUrl, { headers: { 'User-Agent': 'webtun-postinstall' } }, res => {
|
|
103
|
+
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
104
|
+
res.resume();
|
|
105
|
+
return downloadFile(res.headers.location, dest).then(resolve, reject);
|
|
106
|
+
}
|
|
107
|
+
if (res.statusCode !== 200) {
|
|
108
|
+
res.resume();
|
|
109
|
+
return reject(new Error('HTTP ' + res.statusCode));
|
|
110
|
+
}
|
|
111
|
+
const out = fs.createWriteStream(dest);
|
|
112
|
+
res.pipe(out);
|
|
113
|
+
out.on('finish', () => out.close(err => err ? reject(err) : resolve()));
|
|
114
|
+
out.on('error', reject);
|
|
115
|
+
});
|
|
116
|
+
req.on('error', reject);
|
|
117
|
+
req.setTimeout(60000, () => {
|
|
118
|
+
req.destroy();
|
|
119
|
+
reject(new Error('download timeout'));
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function downloadWithCurlOrWget() {
|
|
125
|
+
const r = spawnSync('curl', ['-#fL', url, '-o', tmp], { stdio: 'inherit', timeout: 60000 });
|
|
126
|
+
if (r.status === 0) return true;
|
|
127
|
+
const r2 = spawnSync('wget', ['-q', url, '-O', tmp], { stdio: 'inherit', timeout: 60000 });
|
|
128
|
+
return r2.status === 0;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async function main() {
|
|
132
|
+
try {
|
|
133
|
+
await downloadFile(url, tmp);
|
|
134
|
+
} catch (e) {
|
|
135
|
+
if (!downloadWithCurlOrWget()) {
|
|
136
|
+
console.log(' cloudflared install failed (download error) — skipping');
|
|
137
|
+
cleanup();
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
80
140
|
}
|
|
81
141
|
|
|
82
142
|
// Validate downloaded file is not HTML (e.g. 404 page)
|
|
@@ -89,15 +149,16 @@ try {
|
|
|
89
149
|
if (/^<!doctype\s+html/i.test(head) || /^<html/i.test(head)) {
|
|
90
150
|
console.log(' cloudflared install failed (downloaded file is not a binary) — skipping');
|
|
91
151
|
cleanup();
|
|
92
|
-
|
|
152
|
+
return;
|
|
93
153
|
}
|
|
94
154
|
} catch (e) {
|
|
95
155
|
console.log(' cloudflared install failed (cannot validate download): ' + e.message);
|
|
96
156
|
cleanup();
|
|
97
|
-
|
|
157
|
+
return;
|
|
98
158
|
}
|
|
99
159
|
|
|
100
160
|
function installBin(src, dest) {
|
|
161
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
101
162
|
try {
|
|
102
163
|
fs.renameSync(src, dest);
|
|
103
164
|
} catch (e) {
|
|
@@ -111,35 +172,61 @@ try {
|
|
|
111
172
|
try { fs.chmodSync(dest, 0o755); } catch {}
|
|
112
173
|
}
|
|
113
174
|
|
|
175
|
+
function preferredDest() {
|
|
176
|
+
const name = platform === 'win32' ? 'cloudflared.exe' : 'cloudflared';
|
|
177
|
+
if (platform === 'win32') {
|
|
178
|
+
const localApp = process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local');
|
|
179
|
+
return path.join(localApp, 'cloudflared', name);
|
|
180
|
+
}
|
|
181
|
+
return path.join(os.homedir(), '.local', 'bin', name);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function extractDarwinIfNeeded() {
|
|
185
|
+
if (platform !== 'darwin') return tmp;
|
|
186
|
+
const result = spawnSync('tar', ['xzf', tmp, '-C', tmpDir], { stdio: 'inherit' });
|
|
187
|
+
if (result.status !== 0) throw new Error('tar extraction failed');
|
|
188
|
+
try { fs.unlinkSync(tmp); } catch {}
|
|
189
|
+
return path.join(tmpDir, 'cloudflared');
|
|
190
|
+
}
|
|
191
|
+
|
|
114
192
|
try {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
const dest = path.join(progFiles, 'cloudflared', 'cloudflared.exe');
|
|
123
|
-
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
124
|
-
installBin(tmp, dest);
|
|
125
|
-
} else {
|
|
126
|
-
installBin(tmp, '/usr/local/bin/cloudflared');
|
|
193
|
+
const src = extractDarwinIfNeeded();
|
|
194
|
+
// Prefer user-local path (no admin), then package dir, then system
|
|
195
|
+
const dests = [preferredDest(), path.join(__dirname, platform === 'win32' ? 'cloudflared.exe' : 'cloudflared')];
|
|
196
|
+
if (platform !== 'win32') dests.push('/usr/local/bin/cloudflared');
|
|
197
|
+
else {
|
|
198
|
+
const pf = process.env.ProgramW6432 || process.env.ProgramFiles;
|
|
199
|
+
if (pf) dests.push(path.join(pf, 'cloudflared', 'cloudflared.exe'));
|
|
127
200
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
const localDest = path.join(process.cwd(), 'cloudflared' + (platform === 'win32' ? '.exe' : ''));
|
|
201
|
+
|
|
202
|
+
let installed = null;
|
|
203
|
+
let lastErr = null;
|
|
204
|
+
for (const dest of dests) {
|
|
133
205
|
try {
|
|
134
|
-
installBin(
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
} catch (
|
|
138
|
-
|
|
206
|
+
installBin(src, dest);
|
|
207
|
+
installed = dest;
|
|
208
|
+
break;
|
|
209
|
+
} catch (e) {
|
|
210
|
+
lastErr = e;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (installed) {
|
|
215
|
+
console.log(' cloudflared installed → ' + installed);
|
|
216
|
+
if (platform === 'win32' && installed.includes('Local')) {
|
|
217
|
+
console.log(' (not on PATH; WebTun will find it automatically)');
|
|
139
218
|
}
|
|
140
219
|
} else {
|
|
141
|
-
console.log(' cloudflared install failed: ' +
|
|
220
|
+
console.log(' cloudflared install failed: ' + (lastErr && lastErr.message) + ' — skipping');
|
|
142
221
|
}
|
|
143
222
|
cleanup();
|
|
223
|
+
} catch (e) {
|
|
224
|
+
console.log(' cloudflared install failed: ' + e.message + ' — skipping');
|
|
225
|
+
cleanup();
|
|
144
226
|
}
|
|
145
227
|
}
|
|
228
|
+
|
|
229
|
+
main().catch(e => {
|
|
230
|
+
console.log(' cloudflared install failed: ' + e.message + ' — skipping');
|
|
231
|
+
cleanup();
|
|
232
|
+
});
|