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 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
+ ![Terminal](https://img.shields.io/badge/Terminal-Web--Native-2D5B8E?style=for-the-badge)
4
+ ![PWA](https://img.shields.io/badge/PWA-Installable-6BA428?style=for-the-badge)
5
+ ![Cloudflare](https://img.shields.io/badge/Tunnel-Cloudflare-F38020?style=for-the-badge)
6
+ ![Desktop](https://img.shields.io/badge/Desktop-Electron-47848F?style=for-the-badge)
7
+ ![License](https://img.shields.io/badge/License-MIT-green?style=for-the-badge)
8
+
9
+ **Access your Linux server from any browser — no VPN, no SSH client, no installing anything.**
10
+
11
+ [![Quick Start](https://img.shields.io/badge/Quick_Start-One_Command-2D5B8E?style=for-the-badge&logo=gnu-bash)](install.sh)
12
+ [![Download](https://img.shields.io/github/v/release/unn-known1/webtun?style=for-the-badge&logo=github)](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