webtun 1.4.0
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/LICENSE +21 -0
- package/README.md +205 -0
- package/bin/webtun.js +73 -0
- package/package.json +115 -0
- package/postinstall.js +120 -0
- package/public/commands.js +7 -0
- package/public/icon-192.png +0 -0
- package/public/icon-512.png +0 -0
- package/public/index.html +4963 -0
- package/public/manifest.json +23 -0
- package/public/sw.js +70 -0
- package/server.js +1610 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Gaurang Patel
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
# WebTun — Self-Hosted Web Terminal
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+

|
|
5
|
+

|
|
6
|
+

|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
**Access your Linux server from any browser — no VPN, no SSH client, no installing anything.**
|
|
10
|
+
|
|
11
|
+
[](install.sh)
|
|
12
|
+
[](https://github.com/unn-known1/webtun/releases/latest)
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Why WebTun?
|
|
17
|
+
|
|
18
|
+
| Traditional SSH | WebTun |
|
|
19
|
+
|----------------|--------|
|
|
20
|
+
| Need SSH client installed | Open any browser |
|
|
21
|
+
| Configure VPN or port forwarding | Cloudflare Tunnel auto-configured |
|
|
22
|
+
| Can't access from phone easily | PWA works on iOS/Android |
|
|
23
|
+
| Share access requires key exchange | Web-based sharing in 1 click |
|
|
24
|
+
| Corporate firewall blocks port 22 | Runs over HTTPS (port 443) |
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Features
|
|
29
|
+
|
|
30
|
+
### Core
|
|
31
|
+
- **Real shell sessions** — node-pty backed, full bash/zsh support
|
|
32
|
+
- **Multi-tab terminal** — drag tabs to reorder, side-by-side sessions like your desktop
|
|
33
|
+
- **File explorer** — browse, upload, download, rename, delete, cut/copy/paste with conflict resolution (replace, merge, keep both, skip)
|
|
34
|
+
- **WebSocket + xterm.js** — responsive, low-latency typing
|
|
35
|
+
|
|
36
|
+
### Mobile-First UX
|
|
37
|
+
- **Touch gestures** — swipe to close tabs, swipe from edge to open sidebar, pull-to-refresh file list
|
|
38
|
+
- **Mobile keyboard bar** — ESC, Tab, arrow keys, Ctrl combos (Ctrl+C, Ctrl+D, etc.)
|
|
39
|
+
- **Virtual keyboard aware** — terminal resizes when keyboard opens, no overlap
|
|
40
|
+
- **Pinch-to-zoom** — adjust terminal font size (10–28px) with two fingers
|
|
41
|
+
- **Selection mode** — toggle between terminal interaction and text selection
|
|
42
|
+
- **Responsive** — optimized for phones (480px), tablets (1024px), and landscape orientation
|
|
43
|
+
|
|
44
|
+
### File Explorer
|
|
45
|
+
- **Multi-select** — select all / deselect all, batch delete and download files
|
|
46
|
+
- **Zip / Extract** — right-click any file or folder to zip; right-click `.zip` files to extract
|
|
47
|
+
- **Image viewer** — click images (.png, .jpg, .gif, .svg, .webp, .bmp, .ico) to preview inline
|
|
48
|
+
- **Cut, Copy, Paste** — with conflict resolution dialog (replace, merge folders, keep both, skip, cancel)
|
|
49
|
+
- **Upload with progress** — per-file progress bar via XHR
|
|
50
|
+
- **Breadcrumb navigation** — tappable directory segments for quick navigation
|
|
51
|
+
- **Long-press context menu** — rename, delete, download, copy on touch devices
|
|
52
|
+
- **Text selection** — long-press file names to select and copy text on touch devices
|
|
53
|
+
|
|
54
|
+
### Desktop App (Electron)
|
|
55
|
+
- Cross-platform app for Linux & Windows
|
|
56
|
+
- Runs the server as a child process — no terminal needed
|
|
57
|
+
- Native window with system tray, copy/paste, and file dialogs
|
|
58
|
+
- Download the latest release from [GitHub Releases](https://github.com/unn-known1/webtun/releases/latest)
|
|
59
|
+
- Or build from source: `npm run dist:linux` / `npm run dist:win`
|
|
60
|
+
|
|
61
|
+
### PWA (Install as App)
|
|
62
|
+
- Add to iOS/Android home screen — looks and feels like native app
|
|
63
|
+
- Install banner prompts on first visit (Chrome) with iOS instructions fallback
|
|
64
|
+
- Works offline (shows last session state)
|
|
65
|
+
- Push notifications for tunnel status
|
|
66
|
+
|
|
67
|
+
### Cloudflare Tunnel (Zero-Config)
|
|
68
|
+
- Create tunnel from UI — no CLI commands
|
|
69
|
+
- Built-in health check warns if local server is unreachable before creating tunnel
|
|
70
|
+
- Live/orphan status + target responsiveness indicator in tunnel list
|
|
71
|
+
- Get public HTTPS URL instantly
|
|
72
|
+
- Tunnels survive server restarts
|
|
73
|
+
- Stop/kill tunnels from settings panel
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Quick Start
|
|
78
|
+
|
|
79
|
+
### One-Command Install
|
|
80
|
+
```bash
|
|
81
|
+
bash -c "$(curl -fsSL https://raw.githubusercontent.com/unn-Known1/webtun/main/install.sh)"
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Manual Setup
|
|
85
|
+
```bash
|
|
86
|
+
git clone https://github.com/unn-Known1/webtun.git
|
|
87
|
+
cd webtun
|
|
88
|
+
chmod +x setup.sh && ./setup.sh
|
|
89
|
+
npm start
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Google Colab (Instant Terminal)
|
|
93
|
+
```python
|
|
94
|
+
!rm -rf webtun && git clone https://github.com/unn-Known1/webtun.git && cd webtun && npm install --loglevel=error && node server.js > /tmp/webtun.log 2>&1 & sleep 4 && for i in 1 2 3; do curl -sf http://localhost:3000/api/auth/required >/dev/null && break; sleep 2; done && curl -s -X POST http://localhost:3000/api/tunnel -H 'Content-Type: application/json' -d '{"url":"http://localhost:3000"}' --max-time 20 | python3 -c "import sys,json; d=json.load(sys.stdin); print('🌐 WebTun ready at:', d.get('url','Error: '+d.get('error','')))"
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## Architecture
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
103
|
+
│ Your Browser │
|
|
104
|
+
│ ┌─────────┐ ┌──────────┐ ┌────────────┐ ┌────────────┐ │
|
|
105
|
+
│ │Terminal │ │ File │ │ Tunnel │ │ Settings │ │
|
|
106
|
+
│ │ (xterm) │ │ Explorer │ │ Manager │ │ Panel │ │
|
|
107
|
+
│ └────┬────┘ └────┬─────┘ └──────┬─────┘ └─────┬──────┘ │
|
|
108
|
+
│ │ │ │ │ │
|
|
109
|
+
└───────┼────────────┼───────────────┼──────────────┼─────────┘
|
|
110
|
+
│ WebSocket │ │ │
|
|
111
|
+
▼ ▼ ▼ │
|
|
112
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
113
|
+
│ Node.js Server │
|
|
114
|
+
│ ┌────────────┐ ┌─────────────┐ ┌────────────────────┐ │
|
|
115
|
+
│ │ WebSocket │ │ File API │ │ Cloudflare Tunnel │ │
|
|
116
|
+
│ │ Handler │ │ (read/write)│ │ Manager │ │
|
|
117
|
+
│ └─────┬──────┘ └──────┬──────┘ └─────────┬──────────┘ │
|
|
118
|
+
│ │ │ │ │
|
|
119
|
+
│ ▼ ▼ ▼ │
|
|
120
|
+
│ ┌─────────┐ ┌────────────────┐ ┌──────────────────────┐ │
|
|
121
|
+
│ │ node-pty│ │Local Filesystem│ │ cloudflared daemon │ │
|
|
122
|
+
│ │ (shell) │ │(your server) │ │ (exposes to internet)│ │
|
|
123
|
+
│ └─────────┘ └────────────────┘ └──────────────────────┘ │
|
|
124
|
+
└─────────────────────────────────────────────────────────────┘
|
|
125
|
+
│
|
|
126
|
+
▼
|
|
127
|
+
┌─────────────────┐
|
|
128
|
+
│ Internet Users │
|
|
129
|
+
│ (HTTPS URL) │
|
|
130
|
+
└─────────────────┘
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## Use Cases
|
|
136
|
+
|
|
137
|
+
| Scenario | Why WebTun |
|
|
138
|
+
|----------|------------|
|
|
139
|
+
| **Home server access** | Access from anywhere without opening ports |
|
|
140
|
+
| **Emergency debugging** | Phone/laptop without SSH client |
|
|
141
|
+
| **Share terminal with friend** | No key exchange, just send URL |
|
|
142
|
+
| **Colab power-up** | Persistent terminal alongside Python notebooks |
|
|
143
|
+
| **Demo environment** | Spin up temp shell for presentations |
|
|
144
|
+
| **Corporate restrictions** | HTTPS works where SSH is blocked |
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## Tunnel Manager
|
|
149
|
+
|
|
150
|
+
1. Open **Settings** (gear icon)
|
|
151
|
+
2. Go to **Tunnel** section
|
|
152
|
+
3. Enter URL (default: `http://localhost:3000`)
|
|
153
|
+
4. Click **Create** — a health check pings the target first, warns if unreachable
|
|
154
|
+
5. Copy the public URL — share it with anyone
|
|
155
|
+
6. View all tunnels with live status indicators (green/orange)
|
|
156
|
+
|
|
157
|
+
**Note:** Tunnels created before a server restart need manual cleanup:
|
|
158
|
+
```bash
|
|
159
|
+
kill $(pgrep -f 'cloudflared tunnel')
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## Tech Stack
|
|
165
|
+
|
|
166
|
+
| Layer | Technology |
|
|
167
|
+
|-------|------------|
|
|
168
|
+
| Frontend | HTML + Vanilla JS (PWA, prefers-color-scheme, visualViewport, Drag & Drop) |
|
|
169
|
+
| Backend | Node.js + node-pty + Express |
|
|
170
|
+
| Terminal | xterm.js + fit addon |
|
|
171
|
+
| Protocol | WebSocket (ws) |
|
|
172
|
+
| Tunnel | Cloudflare Tunnel (cloudflared) |
|
|
173
|
+
| Desktop | Electron + electron-builder |
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## Troubleshooting
|
|
178
|
+
|
|
179
|
+
| Problem | Solution |
|
|
180
|
+
|---------|----------|
|
|
181
|
+
| Tunnel URL not loading | Check Cloudflare account quota at [dash.cloudflare.com](https://dash.cloudflare.com) |
|
|
182
|
+
| Permission denied on shell | Ensure user has shell access: `chsh -s /bin/bash` |
|
|
183
|
+
| File upload fails | Check `public/uploads/` permissions: `chmod 755 public/uploads/` |
|
|
184
|
+
| Port 3000 in use | Change port: `PORT=3001 npm start` |
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
## Contributing
|
|
189
|
+
|
|
190
|
+
1. Fork → Branch → Commit → PR
|
|
191
|
+
2. Follow existing code style (ES6+, no frameworks)
|
|
192
|
+
3. Test locally with `npm start`
|
|
193
|
+
4. Update this README if adding features
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
## License
|
|
198
|
+
|
|
199
|
+
MIT — do whatever you want with it.
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
<p align="center">
|
|
204
|
+
<sub>Made with ❤️ by <a href="https://github.com/unn-known1">Gaurang Patel</a></sub>
|
|
205
|
+
</p>
|
package/bin/webtun.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
'use strict';
|
|
4
|
+
|
|
5
|
+
const args = process.argv.slice(2);
|
|
6
|
+
|
|
7
|
+
function printHelp() {
|
|
8
|
+
console.log(`
|
|
9
|
+
WebTun — Web Terminal with Cloudflare Tunnel
|
|
10
|
+
|
|
11
|
+
Usage:
|
|
12
|
+
webtun [options]
|
|
13
|
+
|
|
14
|
+
Options:
|
|
15
|
+
--port, -p <port> Port to listen on (default: 3000 or $PORT)
|
|
16
|
+
--host, -h <host> Host to bind to (default: 0.0.0.0 or $HOST)
|
|
17
|
+
--pin <pin> PIN for authentication (default: $PIN)
|
|
18
|
+
--help Show this help message
|
|
19
|
+
--version Show version number
|
|
20
|
+
|
|
21
|
+
Environment Variables:
|
|
22
|
+
PORT Server port (default: 3000)
|
|
23
|
+
HOST Bind address (default: 0.0.0.0)
|
|
24
|
+
PIN Authentication PIN (empty = no auth)
|
|
25
|
+
SHELL Shell to use (default: /bin/bash or system shell)
|
|
26
|
+
WORKSPACE_ROOT Root directory for file operations (default: ~)
|
|
27
|
+
|
|
28
|
+
Examples:
|
|
29
|
+
webtun Start on default port
|
|
30
|
+
webtun --port 8080 Start on port 8080
|
|
31
|
+
webtun --pin secret123 Start with PIN protection
|
|
32
|
+
PORT=4000 webtun Start on port 4000 via env var
|
|
33
|
+
`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function parseArgs(argv) {
|
|
37
|
+
const opts = {};
|
|
38
|
+
for (let i = 0; i < argv.length; i++) {
|
|
39
|
+
const arg = argv[i];
|
|
40
|
+
if (arg === '--help' || arg === '-H') {
|
|
41
|
+
printHelp();
|
|
42
|
+
process.exit(0);
|
|
43
|
+
}
|
|
44
|
+
if (arg === '--version' || arg === '-v') {
|
|
45
|
+
console.log(require('../package.json').version);
|
|
46
|
+
process.exit(0);
|
|
47
|
+
}
|
|
48
|
+
if (arg === '--port' || arg === '-p') {
|
|
49
|
+
opts.port = parseInt(argv[++i], 10);
|
|
50
|
+
if (isNaN(opts.port)) {
|
|
51
|
+
console.error('Error: --port requires a numeric value');
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
} else if (arg === '--host' || arg === '-h') {
|
|
55
|
+
opts.host = argv[++i];
|
|
56
|
+
} else if (arg === '--pin') {
|
|
57
|
+
process.env.PIN = argv[++i] || '';
|
|
58
|
+
} else {
|
|
59
|
+
console.error(`Unknown option: ${arg}`);
|
|
60
|
+
printHelp();
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return opts;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const opts = parseArgs(args);
|
|
68
|
+
const { startServer } = require('../server');
|
|
69
|
+
|
|
70
|
+
startServer(opts).catch(err => {
|
|
71
|
+
console.error('Failed to start server:', err.message);
|
|
72
|
+
process.exit(1);
|
|
73
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "webtun",
|
|
3
|
+
"version": "1.4.0",
|
|
4
|
+
"description": "Self-hosted web terminal with Cloudflare Tunnel, file explorer, and PWA support",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "Gaurang Patel",
|
|
7
|
+
"email": "ptelgm.yt@gmail.com"
|
|
8
|
+
},
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"main": "server.js",
|
|
11
|
+
"bin": {
|
|
12
|
+
"webtun": "bin/webtun.js"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"bin/",
|
|
16
|
+
"server.js",
|
|
17
|
+
"public/",
|
|
18
|
+
"postinstall.js"
|
|
19
|
+
],
|
|
20
|
+
"engines": {
|
|
21
|
+
"node": ">=18.0.0"
|
|
22
|
+
},
|
|
23
|
+
"scripts": {
|
|
24
|
+
"start": "node server.js",
|
|
25
|
+
"dev": "nodemon server.js",
|
|
26
|
+
"electron": "electron .",
|
|
27
|
+
"pack": "electron-builder --dir",
|
|
28
|
+
"dist": "electron-builder",
|
|
29
|
+
"dist:win": "electron-builder --win",
|
|
30
|
+
"dist:linux": "electron-builder --linux",
|
|
31
|
+
"dist:mac": "electron-builder --mac",
|
|
32
|
+
"postinstall": "node postinstall.js"
|
|
33
|
+
},
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "https://github.com/unn-Known1/webtun.git"
|
|
37
|
+
},
|
|
38
|
+
"keywords": [
|
|
39
|
+
"terminal",
|
|
40
|
+
"web-terminal",
|
|
41
|
+
"ssh",
|
|
42
|
+
"remote-access",
|
|
43
|
+
"cloudflare",
|
|
44
|
+
"tunnel",
|
|
45
|
+
"file-manager",
|
|
46
|
+
"pwa",
|
|
47
|
+
"self-hosted"
|
|
48
|
+
],
|
|
49
|
+
"build": {
|
|
50
|
+
"appId": "com.webtun.app",
|
|
51
|
+
"productName": "WebTun",
|
|
52
|
+
"files": [
|
|
53
|
+
"electron/**/*",
|
|
54
|
+
"server.js",
|
|
55
|
+
"public/**/*",
|
|
56
|
+
"package.json"
|
|
57
|
+
],
|
|
58
|
+
"extraResources": [
|
|
59
|
+
{
|
|
60
|
+
"from": "node_modules",
|
|
61
|
+
"to": "node_modules",
|
|
62
|
+
"filter": [
|
|
63
|
+
"**/*"
|
|
64
|
+
]
|
|
65
|
+
}
|
|
66
|
+
],
|
|
67
|
+
"win": {
|
|
68
|
+
"target": [
|
|
69
|
+
"nsis",
|
|
70
|
+
"portable"
|
|
71
|
+
],
|
|
72
|
+
"icon": "public/icon-512.png"
|
|
73
|
+
},
|
|
74
|
+
"mac": {
|
|
75
|
+
"target": [
|
|
76
|
+
"dmg",
|
|
77
|
+
"zip"
|
|
78
|
+
],
|
|
79
|
+
"icon": "public/icon-512.png",
|
|
80
|
+
"category": "Utility"
|
|
81
|
+
},
|
|
82
|
+
"linux": {
|
|
83
|
+
"target": [
|
|
84
|
+
"AppImage",
|
|
85
|
+
"deb"
|
|
86
|
+
],
|
|
87
|
+
"icon": "public/icon-512.png",
|
|
88
|
+
"category": "Utility"
|
|
89
|
+
},
|
|
90
|
+
"nsis": {
|
|
91
|
+
"oneClick": false,
|
|
92
|
+
"allowToChangeInstallationDirectory": true
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
"dependencies": {
|
|
96
|
+
"express": "^4.18.2",
|
|
97
|
+
"multer": "^2.0.0",
|
|
98
|
+
"node-pty": "^1.0.0",
|
|
99
|
+
"ws": "^8.21.0"
|
|
100
|
+
},
|
|
101
|
+
"optionalDependencies": {
|
|
102
|
+
"electron": "^42.2.0",
|
|
103
|
+
"electron-builder": "^26.8.1"
|
|
104
|
+
},
|
|
105
|
+
"devDependencies": {
|
|
106
|
+
"nodemon": "^3.1.14"
|
|
107
|
+
},
|
|
108
|
+
"overrides": {
|
|
109
|
+
"form-data": "4.0.6"
|
|
110
|
+
},
|
|
111
|
+
"allowScripts": {
|
|
112
|
+
"electron-winstaller@5.4.0": true,
|
|
113
|
+
"node-pty@1.1.0": true
|
|
114
|
+
}
|
|
115
|
+
}
|
package/postinstall.js
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
const { execSync, spawnSync } = require('child_process');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
const CF_RELEASES = 'https://github.com/cloudflare/cloudflared/releases/latest/download';
|
|
7
|
+
|
|
8
|
+
try {
|
|
9
|
+
if (os.platform() === 'win32') {
|
|
10
|
+
execSync('where cloudflared', { stdio: 'ignore' });
|
|
11
|
+
} else {
|
|
12
|
+
execSync('command -v cloudflared', { stdio: 'ignore' });
|
|
13
|
+
}
|
|
14
|
+
process.exit(0);
|
|
15
|
+
} catch {}
|
|
16
|
+
|
|
17
|
+
const platform = os.platform();
|
|
18
|
+
const arch = os.arch();
|
|
19
|
+
|
|
20
|
+
const files = {
|
|
21
|
+
linux: { x64: 'cloudflared-linux-amd64', arm64: 'cloudflared-linux-arm64', arm: 'cloudflared-linux-arm' },
|
|
22
|
+
darwin: { x64: 'cloudflared-darwin-amd64.tgz', arm64: 'cloudflared-darwin-arm64.tgz' },
|
|
23
|
+
win32: { x64: 'cloudflared-windows-amd64.exe' }
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const file = (files[platform] || {})[arch];
|
|
27
|
+
if (!file) process.exit(0);
|
|
28
|
+
|
|
29
|
+
console.log(' installing cloudflared...');
|
|
30
|
+
|
|
31
|
+
const url = CF_RELEASES + '/' + file;
|
|
32
|
+
const tmpDir = os.tmpdir();
|
|
33
|
+
const tmp = path.join(tmpDir, 'cloudflared-' + process.pid + (platform === 'win32' ? '.exe' : ''));
|
|
34
|
+
const tmpExtracted = path.join(tmpDir, 'cloudflared-' + process.pid + '-bin' + (platform === 'win32' ? '.exe' : ''));
|
|
35
|
+
|
|
36
|
+
function cleanup() {
|
|
37
|
+
try { fs.unlinkSync(tmp); } catch {}
|
|
38
|
+
try { fs.unlinkSync(tmpExtracted); } catch {}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function download() {
|
|
42
|
+
const r = spawnSync('curl', ['-#fL', url, '-o', tmp], { stdio: 'inherit', timeout: 60000 });
|
|
43
|
+
if (r.status === 0) return true;
|
|
44
|
+
const r2 = spawnSync('wget', ['-q', url, '-O', tmp], { stdio: 'inherit', timeout: 60000 });
|
|
45
|
+
return r2.status === 0;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (!download()) {
|
|
49
|
+
console.log(' cloudflared install failed (download error) — skipping');
|
|
50
|
+
cleanup();
|
|
51
|
+
process.exit(0);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Validate downloaded file is not HTML (e.g. 404 page)
|
|
55
|
+
try {
|
|
56
|
+
const buf = Buffer.alloc(1024);
|
|
57
|
+
const fd = fs.openSync(tmp, 'r');
|
|
58
|
+
const bytesRead = fs.readSync(fd, buf, 0, 1024, 0);
|
|
59
|
+
fs.closeSync(fd);
|
|
60
|
+
const head = buf.slice(0, bytesRead).toString('utf8').trim();
|
|
61
|
+
if (/^<!doctype\s+html/i.test(head) || /^<html/i.test(head)) {
|
|
62
|
+
console.log(' cloudflared install failed (downloaded file is not a binary) — skipping');
|
|
63
|
+
cleanup();
|
|
64
|
+
process.exit(0);
|
|
65
|
+
}
|
|
66
|
+
} catch (e) {
|
|
67
|
+
console.log(' cloudflared install failed (cannot validate download): ' + e.message);
|
|
68
|
+
cleanup();
|
|
69
|
+
process.exit(0);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function installBin(src, dest) {
|
|
73
|
+
try {
|
|
74
|
+
fs.renameSync(src, dest);
|
|
75
|
+
} catch (e) {
|
|
76
|
+
if (e.code === 'EXDEV') {
|
|
77
|
+
fs.copyFileSync(src, dest);
|
|
78
|
+
try { fs.unlinkSync(src); } catch {}
|
|
79
|
+
} else {
|
|
80
|
+
throw e;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
// Always set executable bit regardless of rename or copy path
|
|
84
|
+
try { fs.chmodSync(dest, 0o755); } catch {}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
if (platform === 'darwin') {
|
|
89
|
+
const result = spawnSync('tar', ['xzf', tmp, '-C', tmpDir], { stdio: 'inherit' });
|
|
90
|
+
if (result.status !== 0) throw new Error('tar extraction failed');
|
|
91
|
+
installBin(path.join(tmpDir, 'cloudflared'), '/usr/local/bin/cloudflared');
|
|
92
|
+
try { fs.unlinkSync(tmp); } catch {}
|
|
93
|
+
} else if (platform === 'win32') {
|
|
94
|
+
const progFiles = process.env.ProgramW6432 || process.env.ProgramFiles || (process.env.SystemRoot + '\\Program Files');
|
|
95
|
+
const dest = path.join(progFiles, 'cloudflared', 'cloudflared.exe');
|
|
96
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
97
|
+
installBin(tmp, dest);
|
|
98
|
+
} else {
|
|
99
|
+
installBin(tmp, '/usr/local/bin/cloudflared');
|
|
100
|
+
}
|
|
101
|
+
console.log(' cloudflared installed');
|
|
102
|
+
cleanup();
|
|
103
|
+
} catch (e) {
|
|
104
|
+
// When installed globally via npm, /usr/local/bin may not be writable.
|
|
105
|
+
// In that case, download to a local path and suggest manual install.
|
|
106
|
+
if (e.code === 'EACCES' || e.code === 'EPERM') {
|
|
107
|
+
const localDest = path.join(process.cwd(), 'cloudflared' + (platform === 'win32' ? '.exe' : ''));
|
|
108
|
+
try {
|
|
109
|
+
installBin(tmp, localDest);
|
|
110
|
+
console.log(' cloudflared installed to ' + localDest);
|
|
111
|
+
console.log(' Move it to your PATH: sudo mv ' + localDest + ' /usr/local/bin/');
|
|
112
|
+
} catch (e2) {
|
|
113
|
+
console.log(' cloudflared install failed: ' + e2.message + ' — skipping');
|
|
114
|
+
}
|
|
115
|
+
} else {
|
|
116
|
+
console.log(' cloudflared install failed: ' + e.message + ' — skipping');
|
|
117
|
+
}
|
|
118
|
+
cleanup();
|
|
119
|
+
process.exit(0);
|
|
120
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
window.DEFAULT_CMDS = [
|
|
2
|
+
{ name: 'Install OpenCode AI', cmd: 'npm install -g opencode-ai', cat: 'AI Tools' },
|
|
3
|
+
{ name: 'Install MiMo CLI', cmd: 'npm install -g @mimo-ai/cli', cat: 'AI Tools' },
|
|
4
|
+
{ name: 'Install 9Router', cmd: 'npm install -g 9router', cat: 'AI Tools' },
|
|
5
|
+
{ name: 'GitHub TUI', cmd: 'npx github-tui', cat: 'Tools' },
|
|
6
|
+
{ name: 'Colab Setup', cmd: 'curl -fsSL https://raw.githubusercontent.com/unn-Known1/unn-Known1/main/colab_setup.sh | bash', cat: 'Setup' },
|
|
7
|
+
];
|
|
Binary file
|
|
Binary file
|