typescript-virtual-container 1.1.2 → 1.1.4
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/dist/SSHMimic/exec.d.ts.map +1 -1
- package/dist/SSHMimic/exec.js +8 -2
- package/dist/SSHMimic/index.d.ts +1 -0
- package/dist/SSHMimic/index.d.ts.map +1 -1
- package/dist/SSHMimic/index.js +9 -3
- package/dist/SSHMimic/sftp.d.ts +46 -0
- package/dist/SSHMimic/sftp.d.ts.map +1 -0
- package/dist/SSHMimic/sftp.js +576 -0
- package/dist/VirtualFileSystem/index.d.ts +6 -4
- package/dist/VirtualFileSystem/index.d.ts.map +1 -1
- package/dist/VirtualFileSystem/index.js +144 -153
- package/dist/VirtualShell/index.d.ts +6 -0
- package/dist/VirtualShell/index.d.ts.map +1 -1
- package/dist/VirtualShell/index.js +16 -4
- package/dist/VirtualShell/shell.d.ts.map +1 -1
- package/dist/VirtualShell/shell.js +7 -0
- package/dist/VirtualUserManager/index.d.ts +8 -0
- package/dist/VirtualUserManager/index.d.ts.map +1 -1
- package/dist/VirtualUserManager/index.js +30 -0
- package/dist/commands/exit.d.ts.map +1 -1
- package/dist/commands/exit.js +1 -0
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +2 -0
- package/dist/commands/passwd.d.ts +3 -0
- package/dist/commands/passwd.d.ts.map +1 -0
- package/dist/commands/passwd.js +21 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/modules/neofetch.d.ts.map +1 -1
- package/dist/modules/neofetch.js +0 -1
- package/dist/standalone.js +10 -1
- package/package.json +1 -1
- package/src/SSHMimic/exec.ts +18 -12
- package/src/SSHMimic/index.ts +16 -7
- package/src/SSHMimic/sftp.ts +833 -0
- package/src/VirtualFileSystem/index.ts +158 -188
- package/src/VirtualShell/index.ts +19 -8
- package/src/VirtualShell/shell.ts +7 -0
- package/src/VirtualUserManager/index.ts +38 -0
- package/src/commands/exit.ts +1 -0
- package/src/commands/index.ts +2 -0
- package/src/commands/passwd.ts +25 -0
- package/src/index.ts +2 -1
- package/src/modules/neofetch.ts +0 -2
- package/src/standalone.ts +11 -1
- package/tests/sftp.test.ts +319 -0
- package/tests/ssh-exec.test.ts +45 -0
- package/tests/users.test.ts +13 -0
|
@@ -1,10 +1,7 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { gunzipSync, gzipSync } from "node:zlib";
|
|
4
|
-
import {
|
|
5
|
-
import { getNode, getParentDirectory, normalizePath, splitPath } from "./path";
|
|
6
|
-
import { applySnapshot, createSnapshot } from "./snapshot";
|
|
7
|
-
import { renderTree } from "./tree";
|
|
4
|
+
import { normalizePath } from "./path";
|
|
8
5
|
/**
|
|
9
6
|
* In-memory virtual filesystem with tar.gz mirror persistence.
|
|
10
7
|
*
|
|
@@ -13,35 +10,72 @@ import { renderTree } from "./tree";
|
|
|
13
10
|
* {@link VirtualFileSystem.flushMirror} to persist pending changes.
|
|
14
11
|
*/
|
|
15
12
|
class VirtualFileSystem {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
13
|
+
mirrorRoot;
|
|
14
|
+
ensureMirrorRoot() {
|
|
15
|
+
fs.mkdirSync(this.mirrorRoot, { recursive: true, mode: 0o755 });
|
|
16
|
+
}
|
|
17
|
+
resolveFsPath(targetPath) {
|
|
18
|
+
const normalized = normalizePath(targetPath);
|
|
19
|
+
const relativePath = normalized.slice(1);
|
|
20
|
+
const resolved = path.resolve(this.mirrorRoot, relativePath || ".");
|
|
21
|
+
const relative = path.relative(this.mirrorRoot, resolved);
|
|
22
|
+
if (relative.startsWith("..") || path.isAbsolute(relative)) {
|
|
23
|
+
throw new Error(`Invalid path '${targetPath}'.`);
|
|
24
|
+
}
|
|
25
|
+
return resolved;
|
|
26
|
+
}
|
|
27
|
+
detectGzipFile(targetPath) {
|
|
28
|
+
const fd = fs.openSync(targetPath, "r");
|
|
29
|
+
try {
|
|
30
|
+
const header = Buffer.alloc(2);
|
|
31
|
+
const bytesRead = fs.readSync(fd, header, 0, 2, 0);
|
|
32
|
+
return bytesRead === 2 && header[0] === 0x1f && header[1] === 0x8b;
|
|
33
|
+
}
|
|
34
|
+
finally {
|
|
35
|
+
fs.closeSync(fd);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
computeDiskUsageBytes(targetPath) {
|
|
39
|
+
const stats = fs.statSync(targetPath);
|
|
40
|
+
if (stats.isFile()) {
|
|
41
|
+
return stats.size;
|
|
22
42
|
}
|
|
23
43
|
let total = 0;
|
|
24
|
-
for (const
|
|
25
|
-
total += this.
|
|
44
|
+
for (const entry of fs.readdirSync(targetPath)) {
|
|
45
|
+
total += this.computeDiskUsageBytes(path.join(targetPath, entry));
|
|
26
46
|
}
|
|
27
47
|
return total;
|
|
28
48
|
}
|
|
49
|
+
renderTreeLines(targetPath, label) {
|
|
50
|
+
const lines = [label];
|
|
51
|
+
const walk = (currentPath, prefix) => {
|
|
52
|
+
const entries = fs
|
|
53
|
+
.readdirSync(currentPath, { withFileTypes: true })
|
|
54
|
+
.map((entry) => entry.name)
|
|
55
|
+
.sort((left, right) => left.localeCompare(right));
|
|
56
|
+
for (let i = 0; i < entries.length; i += 1) {
|
|
57
|
+
const name = entries[i];
|
|
58
|
+
const isLast = i === entries.length - 1;
|
|
59
|
+
const connector = isLast ? "└── " : "├── ";
|
|
60
|
+
const nextPrefix = `${prefix}${isLast ? " " : "│ "}`;
|
|
61
|
+
const entryPath = path.join(currentPath, name);
|
|
62
|
+
const isDirectory = fs.statSync(entryPath).isDirectory();
|
|
63
|
+
lines.push(`${prefix}${connector}${name}`);
|
|
64
|
+
if (isDirectory) {
|
|
65
|
+
walk(entryPath, nextPrefix);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
walk(targetPath, "");
|
|
70
|
+
return lines.join("\n");
|
|
71
|
+
}
|
|
29
72
|
/**
|
|
30
73
|
* Creates a virtual filesystem instance.
|
|
31
74
|
*
|
|
32
75
|
* @param baseDir Base directory used to resolve mirror archive location.
|
|
33
76
|
*/
|
|
34
77
|
constructor(baseDir = process.cwd()) {
|
|
35
|
-
|
|
36
|
-
this.archivePath = path.resolve(baseDir, ".vfs", "mirror.tar.gz");
|
|
37
|
-
this.root = {
|
|
38
|
-
type: "directory",
|
|
39
|
-
name: "",
|
|
40
|
-
mode: 0o755,
|
|
41
|
-
createdAt: now,
|
|
42
|
-
updatedAt: now,
|
|
43
|
-
children: new Map(),
|
|
44
|
-
};
|
|
78
|
+
this.mirrorRoot = path.resolve(baseDir, ".vfs", "mirror");
|
|
45
79
|
}
|
|
46
80
|
/**
|
|
47
81
|
* Restores filesystem state from mirror archive.
|
|
@@ -49,19 +83,7 @@ class VirtualFileSystem {
|
|
|
49
83
|
* If archive does not exist or cannot be read, creates fresh mirror file.
|
|
50
84
|
*/
|
|
51
85
|
async restoreMirror() {
|
|
52
|
-
|
|
53
|
-
try {
|
|
54
|
-
const compressed = await fs.readFile(this.archivePath);
|
|
55
|
-
const tarBuffer = gunzipSync(compressed);
|
|
56
|
-
const snapshot = await readSnapshotFromTar(tarBuffer);
|
|
57
|
-
applySnapshot(this.root, snapshot);
|
|
58
|
-
this.dirty = false;
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
catch {
|
|
62
|
-
console.warn(`No valid mirror archive found at '${this.archivePath}'. Starting with empty filesystem.`);
|
|
63
|
-
await this.flushMirror();
|
|
64
|
-
}
|
|
86
|
+
this.ensureMirrorRoot();
|
|
65
87
|
}
|
|
66
88
|
/**
|
|
67
89
|
* Persists current filesystem state to mirror archive.
|
|
@@ -69,15 +91,7 @@ class VirtualFileSystem {
|
|
|
69
91
|
* No-op when nothing changed and archive already exists.
|
|
70
92
|
*/
|
|
71
93
|
async flushMirror() {
|
|
72
|
-
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
75
|
-
await fs.mkdir(path.dirname(this.archivePath), { recursive: true });
|
|
76
|
-
const snapshotJson = JSON.stringify(createSnapshot(this.root), null, 2);
|
|
77
|
-
const tarBuffer = await createTarBuffer(snapshotJson);
|
|
78
|
-
const compressed = gzipSync(tarBuffer);
|
|
79
|
-
await fs.writeFile(this.archivePath, compressed);
|
|
80
|
-
this.dirty = false;
|
|
94
|
+
this.ensureMirrorRoot();
|
|
81
95
|
}
|
|
82
96
|
/**
|
|
83
97
|
* Creates directory and any missing parent directories.
|
|
@@ -86,32 +100,12 @@ class VirtualFileSystem {
|
|
|
86
100
|
* @param mode POSIX-like mode bits for new directories.
|
|
87
101
|
*/
|
|
88
102
|
mkdir(targetPath, mode = 0o755) {
|
|
89
|
-
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
const existing = current.children.get(part);
|
|
94
|
-
if (!existing) {
|
|
95
|
-
const now = new Date();
|
|
96
|
-
const nextDir = {
|
|
97
|
-
type: "directory",
|
|
98
|
-
name: part,
|
|
99
|
-
mode,
|
|
100
|
-
createdAt: now,
|
|
101
|
-
updatedAt: now,
|
|
102
|
-
children: new Map(),
|
|
103
|
-
};
|
|
104
|
-
current.children.set(part, nextDir);
|
|
105
|
-
current.updatedAt = now;
|
|
106
|
-
this.dirty = true;
|
|
107
|
-
current = nextDir;
|
|
108
|
-
continue;
|
|
109
|
-
}
|
|
110
|
-
if (existing.type !== "directory") {
|
|
111
|
-
throw new Error(`Cannot create directory '${normalized}': '${part}' is a file.`);
|
|
112
|
-
}
|
|
113
|
-
current = existing;
|
|
103
|
+
this.ensureMirrorRoot();
|
|
104
|
+
const fsPath = this.resolveFsPath(targetPath);
|
|
105
|
+
if (fs.existsSync(fsPath) && !fs.statSync(fsPath).isDirectory()) {
|
|
106
|
+
throw new Error(`Cannot create directory '${normalizePath(targetPath)}': path is a file.`);
|
|
114
107
|
}
|
|
108
|
+
fs.mkdirSync(fsPath, { recursive: true, mode });
|
|
115
109
|
}
|
|
116
110
|
/**
|
|
117
111
|
* Writes UTF-8 text or binary content into file.
|
|
@@ -123,31 +117,21 @@ class VirtualFileSystem {
|
|
|
123
117
|
* @param options Optional write behavior (mode, compression).
|
|
124
118
|
*/
|
|
125
119
|
writeFile(targetPath, content, options = {}) {
|
|
120
|
+
this.ensureMirrorRoot();
|
|
126
121
|
const normalized = normalizePath(targetPath);
|
|
127
|
-
const
|
|
128
|
-
const
|
|
122
|
+
const fsPath = this.resolveFsPath(normalized);
|
|
123
|
+
const parentPath = path.dirname(fsPath);
|
|
124
|
+
fs.mkdirSync(parentPath, { recursive: true, mode: 0o755 });
|
|
129
125
|
const rawContent = Buffer.isBuffer(content)
|
|
130
126
|
? content
|
|
131
127
|
: Buffer.from(content, "utf8");
|
|
132
128
|
const shouldCompress = options.compress ?? false;
|
|
133
129
|
const storedContent = shouldCompress ? gzipSync(rawContent) : rawContent;
|
|
134
|
-
|
|
135
|
-
if (existing && existing.type === "directory") {
|
|
130
|
+
if (fs.existsSync(fsPath) && fs.statSync(fsPath).isDirectory()) {
|
|
136
131
|
throw new Error(`Cannot write file '${normalized}': path is a directory.`);
|
|
137
132
|
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
parent.children.set(name, {
|
|
141
|
-
type: "file",
|
|
142
|
-
name,
|
|
143
|
-
mode,
|
|
144
|
-
createdAt,
|
|
145
|
-
updatedAt: now,
|
|
146
|
-
content: storedContent,
|
|
147
|
-
compressed: shouldCompress,
|
|
148
|
-
});
|
|
149
|
-
parent.updatedAt = now;
|
|
150
|
-
this.dirty = true;
|
|
133
|
+
fs.writeFileSync(fsPath, storedContent);
|
|
134
|
+
fs.chmodSync(fsPath, options.mode ?? 0o644);
|
|
151
135
|
}
|
|
152
136
|
/**
|
|
153
137
|
* Reads file content as UTF-8 text.
|
|
@@ -158,11 +142,13 @@ class VirtualFileSystem {
|
|
|
158
142
|
* @returns UTF-8 string content.
|
|
159
143
|
*/
|
|
160
144
|
readFile(targetPath) {
|
|
161
|
-
|
|
162
|
-
|
|
145
|
+
this.ensureMirrorRoot();
|
|
146
|
+
const fsPath = this.resolveFsPath(targetPath);
|
|
147
|
+
if (!fs.existsSync(fsPath) || !fs.statSync(fsPath).isFile()) {
|
|
163
148
|
throw new Error(`Cannot read '${targetPath}': not a file.`);
|
|
164
149
|
}
|
|
165
|
-
const
|
|
150
|
+
const stored = fs.readFileSync(fsPath);
|
|
151
|
+
const raw = this.detectGzipFile(fsPath) ? gunzipSync(stored) : stored;
|
|
166
152
|
return raw.toString("utf8");
|
|
167
153
|
}
|
|
168
154
|
/**
|
|
@@ -173,8 +159,8 @@ class VirtualFileSystem {
|
|
|
173
159
|
*/
|
|
174
160
|
exists(targetPath) {
|
|
175
161
|
try {
|
|
176
|
-
|
|
177
|
-
return
|
|
162
|
+
const fsPath = this.resolveFsPath(targetPath);
|
|
163
|
+
return fs.existsSync(fsPath);
|
|
178
164
|
}
|
|
179
165
|
catch {
|
|
180
166
|
return false;
|
|
@@ -187,10 +173,11 @@ class VirtualFileSystem {
|
|
|
187
173
|
* @param mode New POSIX-like mode.
|
|
188
174
|
*/
|
|
189
175
|
chmod(targetPath, mode) {
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
176
|
+
const fsPath = this.resolveFsPath(targetPath);
|
|
177
|
+
if (!fs.existsSync(fsPath)) {
|
|
178
|
+
throw new Error(`Path '${normalizePath(targetPath)}' does not exist.`);
|
|
179
|
+
}
|
|
180
|
+
fs.chmodSync(fsPath, mode);
|
|
194
181
|
}
|
|
195
182
|
/**
|
|
196
183
|
* Returns metadata for file or directory.
|
|
@@ -199,28 +186,35 @@ class VirtualFileSystem {
|
|
|
199
186
|
* @returns Typed stat object based on node type.
|
|
200
187
|
*/
|
|
201
188
|
stat(targetPath) {
|
|
189
|
+
this.ensureMirrorRoot();
|
|
202
190
|
const normalized = normalizePath(targetPath);
|
|
203
|
-
const
|
|
204
|
-
if (
|
|
191
|
+
const fsPath = this.resolveFsPath(normalized);
|
|
192
|
+
if (!fs.existsSync(fsPath)) {
|
|
193
|
+
throw new Error(`Path '${normalized}' does not exist.`);
|
|
194
|
+
}
|
|
195
|
+
const stats = fs.statSync(fsPath);
|
|
196
|
+
const mode = stats.mode & 0o777;
|
|
197
|
+
const name = normalized === "/" ? "" : path.posix.basename(normalized);
|
|
198
|
+
if (stats.isFile()) {
|
|
205
199
|
return {
|
|
206
200
|
type: "file",
|
|
207
|
-
name
|
|
201
|
+
name,
|
|
208
202
|
path: normalized,
|
|
209
|
-
mode
|
|
210
|
-
createdAt:
|
|
211
|
-
updatedAt:
|
|
212
|
-
compressed:
|
|
213
|
-
size:
|
|
203
|
+
mode,
|
|
204
|
+
createdAt: stats.birthtime,
|
|
205
|
+
updatedAt: stats.mtime,
|
|
206
|
+
compressed: this.detectGzipFile(fsPath),
|
|
207
|
+
size: stats.size,
|
|
214
208
|
};
|
|
215
209
|
}
|
|
216
210
|
return {
|
|
217
211
|
type: "directory",
|
|
218
|
-
name
|
|
212
|
+
name,
|
|
219
213
|
path: normalized,
|
|
220
|
-
mode
|
|
221
|
-
createdAt:
|
|
222
|
-
updatedAt:
|
|
223
|
-
childrenCount:
|
|
214
|
+
mode,
|
|
215
|
+
createdAt: stats.birthtime,
|
|
216
|
+
updatedAt: stats.mtime,
|
|
217
|
+
childrenCount: fs.readdirSync(fsPath).length,
|
|
224
218
|
};
|
|
225
219
|
}
|
|
226
220
|
/**
|
|
@@ -230,11 +224,11 @@ class VirtualFileSystem {
|
|
|
230
224
|
* @returns Sorted child names.
|
|
231
225
|
*/
|
|
232
226
|
list(dirPath = "/") {
|
|
233
|
-
const
|
|
234
|
-
if (
|
|
227
|
+
const fsPath = this.resolveFsPath(dirPath);
|
|
228
|
+
if (!fs.existsSync(fsPath) || !fs.statSync(fsPath).isDirectory()) {
|
|
235
229
|
throw new Error(`Cannot list '${dirPath}': not a directory.`);
|
|
236
230
|
}
|
|
237
|
-
return
|
|
231
|
+
return fs.readdirSync(fsPath).sort();
|
|
238
232
|
}
|
|
239
233
|
/**
|
|
240
234
|
* Renders ASCII tree view of directory hierarchy.
|
|
@@ -243,12 +237,12 @@ class VirtualFileSystem {
|
|
|
243
237
|
* @returns Multi-line tree string.
|
|
244
238
|
*/
|
|
245
239
|
tree(dirPath = "/") {
|
|
246
|
-
const
|
|
247
|
-
if (
|
|
240
|
+
const fsPath = this.resolveFsPath(dirPath);
|
|
241
|
+
if (!fs.existsSync(fsPath) || !fs.statSync(fsPath).isDirectory()) {
|
|
248
242
|
throw new Error(`Cannot render tree for '${dirPath}': not a directory.`);
|
|
249
243
|
}
|
|
250
244
|
const rootLabel = dirPath === "/" ? "/" : path.posix.basename(normalizePath(dirPath));
|
|
251
|
-
return
|
|
245
|
+
return this.renderTreeLines(fsPath, rootLabel);
|
|
252
246
|
}
|
|
253
247
|
/**
|
|
254
248
|
* Computes total stored file bytes under a path.
|
|
@@ -260,8 +254,11 @@ class VirtualFileSystem {
|
|
|
260
254
|
* @returns Total byte usage for file content under target path.
|
|
261
255
|
*/
|
|
262
256
|
getUsageBytes(targetPath = "/") {
|
|
263
|
-
const
|
|
264
|
-
|
|
257
|
+
const fsPath = this.resolveFsPath(targetPath);
|
|
258
|
+
if (!fs.existsSync(fsPath)) {
|
|
259
|
+
throw new Error(`Path '${normalizePath(targetPath)}' does not exist.`);
|
|
260
|
+
}
|
|
261
|
+
return this.computeDiskUsageBytes(fsPath);
|
|
265
262
|
}
|
|
266
263
|
/**
|
|
267
264
|
* Compresses file content with gzip and flags node as compressed.
|
|
@@ -269,15 +266,13 @@ class VirtualFileSystem {
|
|
|
269
266
|
* @param targetPath Path to file.
|
|
270
267
|
*/
|
|
271
268
|
compressFile(targetPath) {
|
|
272
|
-
const
|
|
273
|
-
if (
|
|
269
|
+
const fsPath = this.resolveFsPath(targetPath);
|
|
270
|
+
if (!fs.existsSync(fsPath) || !fs.statSync(fsPath).isFile()) {
|
|
274
271
|
throw new Error(`Cannot compress '${targetPath}': not a file.`);
|
|
275
272
|
}
|
|
276
|
-
if (!
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
node.updatedAt = new Date();
|
|
280
|
-
this.dirty = true;
|
|
273
|
+
if (!this.detectGzipFile(fsPath)) {
|
|
274
|
+
const content = fs.readFileSync(fsPath);
|
|
275
|
+
fs.writeFileSync(fsPath, gzipSync(content));
|
|
281
276
|
}
|
|
282
277
|
}
|
|
283
278
|
/**
|
|
@@ -286,15 +281,13 @@ class VirtualFileSystem {
|
|
|
286
281
|
* @param targetPath Path to file.
|
|
287
282
|
*/
|
|
288
283
|
decompressFile(targetPath) {
|
|
289
|
-
const
|
|
290
|
-
if (
|
|
284
|
+
const fsPath = this.resolveFsPath(targetPath);
|
|
285
|
+
if (!fs.existsSync(fsPath) || !fs.statSync(fsPath).isFile()) {
|
|
291
286
|
throw new Error(`Cannot decompress '${targetPath}': not a file.`);
|
|
292
287
|
}
|
|
293
|
-
if (
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
node.updatedAt = new Date();
|
|
297
|
-
this.dirty = true;
|
|
288
|
+
if (this.detectGzipFile(fsPath)) {
|
|
289
|
+
const content = fs.readFileSync(fsPath);
|
|
290
|
+
fs.writeFileSync(fsPath, gunzipSync(content));
|
|
298
291
|
}
|
|
299
292
|
}
|
|
300
293
|
/**
|
|
@@ -308,19 +301,23 @@ class VirtualFileSystem {
|
|
|
308
301
|
if (normalized === "/") {
|
|
309
302
|
throw new Error("Cannot remove root directory.");
|
|
310
303
|
}
|
|
311
|
-
const
|
|
312
|
-
|
|
313
|
-
if (!node) {
|
|
304
|
+
const fsPath = this.resolveFsPath(normalized);
|
|
305
|
+
if (!fs.existsSync(fsPath)) {
|
|
314
306
|
throw new Error(`Path '${normalized}' does not exist.`);
|
|
315
307
|
}
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
308
|
+
const stats = fs.statSync(fsPath);
|
|
309
|
+
if (stats.isDirectory() && !options.recursive) {
|
|
310
|
+
const entries = fs.readdirSync(fsPath);
|
|
311
|
+
if (entries.length > 0) {
|
|
312
|
+
throw new Error(`Directory '${normalized}' is not empty. Use recursive option.`);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
if (stats.isDirectory()) {
|
|
316
|
+
fs.rmSync(fsPath, { recursive: options.recursive ?? false });
|
|
317
|
+
}
|
|
318
|
+
else {
|
|
319
|
+
fs.rmSync(fsPath);
|
|
320
320
|
}
|
|
321
|
-
parent.children.delete(name);
|
|
322
|
-
parent.updatedAt = new Date();
|
|
323
|
-
this.dirty = true;
|
|
324
321
|
}
|
|
325
322
|
/**
|
|
326
323
|
* Moves or renames node to destination path.
|
|
@@ -334,22 +331,16 @@ class VirtualFileSystem {
|
|
|
334
331
|
if (fromNormalized === "/" || toNormalized === "/") {
|
|
335
332
|
throw new Error("Cannot move root directory.");
|
|
336
333
|
}
|
|
337
|
-
const
|
|
338
|
-
const
|
|
339
|
-
if (!
|
|
334
|
+
const fromFsPath = this.resolveFsPath(fromNormalized);
|
|
335
|
+
const toFsPath = this.resolveFsPath(toNormalized);
|
|
336
|
+
if (!fs.existsSync(fromFsPath)) {
|
|
340
337
|
throw new Error(`Path '${fromNormalized}' does not exist.`);
|
|
341
338
|
}
|
|
342
|
-
|
|
343
|
-
if (toParent.children.has(toName)) {
|
|
339
|
+
if (fs.existsSync(toFsPath)) {
|
|
344
340
|
throw new Error(`Destination '${toNormalized}' already exists.`);
|
|
345
341
|
}
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
node.updatedAt = new Date();
|
|
349
|
-
toParent.children.set(toName, node);
|
|
350
|
-
fromParent.updatedAt = new Date();
|
|
351
|
-
toParent.updatedAt = new Date();
|
|
352
|
-
this.dirty = true;
|
|
342
|
+
fs.mkdirSync(path.dirname(toFsPath), { recursive: true, mode: 0o755 });
|
|
343
|
+
fs.renameSync(fromFsPath, toFsPath);
|
|
353
344
|
}
|
|
354
345
|
}
|
|
355
346
|
export default VirtualFileSystem;
|
|
@@ -19,6 +19,7 @@ declare class VirtualShell {
|
|
|
19
19
|
users: VirtualUserManager;
|
|
20
20
|
hostname: string;
|
|
21
21
|
properties: ShellProperties;
|
|
22
|
+
private initialized;
|
|
22
23
|
/**
|
|
23
24
|
* Creates a new virtual shell instance.
|
|
24
25
|
*
|
|
@@ -27,6 +28,11 @@ declare class VirtualShell {
|
|
|
27
28
|
* @param basePath Optional base path for the virtual filesystem (defaults to process.cwd()).
|
|
28
29
|
*/
|
|
29
30
|
constructor(hostname: string, properties?: ShellProperties, basePath?: string);
|
|
31
|
+
/**
|
|
32
|
+
* Ensures initialization is complete before allowing operations.
|
|
33
|
+
* Call this before any authentication or command execution.
|
|
34
|
+
*/
|
|
35
|
+
ensureInitialized(): Promise<void>;
|
|
30
36
|
/**
|
|
31
37
|
* Registers a new command in the shell runtime.
|
|
32
38
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/VirtualShell/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACvE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,iBAAiB,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAG3D,MAAM,WAAW,eAAe;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;CACb;AA8BD;;;;;GAKG;AACH,cAAM,YAAY;IACjB,QAAQ,EAAE,MAAM,CAAO;IACvB,GAAG,EAAE,iBAAiB,CAAC;IACvB,KAAK,EAAE,kBAAkB,CAAC;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/VirtualShell/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACvE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,iBAAiB,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAG3D,MAAM,WAAW,eAAe;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;CACb;AA8BD;;;;;GAKG;AACH,cAAM,YAAY;IACjB,QAAQ,EAAE,MAAM,CAAO;IACvB,GAAG,EAAE,iBAAiB,CAAC;IACvB,KAAK,EAAE,kBAAkB,CAAC;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,eAAe,CAAC;IAC5B,OAAO,CAAC,WAAW,CAAgB;IAEnC;;;;;;OAMG;gBAEF,QAAQ,EAAE,MAAM,EAChB,UAAU,CAAC,EAAE,eAAe,EAC5B,QAAQ,CAAC,EAAE,MAAM;IAuBlB;;;OAGG;IACU,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC;IAI/C;;;;;;OAMG;IACH,UAAU,CACT,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EAAE,EAChB,QAAQ,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,GACvE,IAAI;IASP;;;;;;OAMG;IACH,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI;IAIrE;;;;;;;OAOG;IAEH,uBAAuB,CACtB,MAAM,EAAE,WAAW,EACnB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,GAAG,IAAI,EACxB,aAAa,EAAE,MAAM,EACrB,YAAY,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAC1C,IAAI;IAcP;;;;OAIG;IACI,MAAM,IAAI,iBAAiB,GAAG,IAAI;IAIzC;;;;OAIG;IACI,QAAQ,IAAI,kBAAkB,GAAG,IAAI;IAI5C;;;;OAIG;IACI,WAAW,IAAI,MAAM;IAI5B;;;;;;OAMG;IACI,eAAe,CACrB,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,GAAG,MAAM,GACtB,IAAI;CAIP;AAED,OAAO,EAAE,YAAY,EAAE,CAAC"}
|
|
@@ -36,6 +36,7 @@ class VirtualShell {
|
|
|
36
36
|
users;
|
|
37
37
|
hostname;
|
|
38
38
|
properties;
|
|
39
|
+
initialized;
|
|
39
40
|
/**
|
|
40
41
|
* Creates a new virtual shell instance.
|
|
41
42
|
*
|
|
@@ -49,10 +50,21 @@ class VirtualShell {
|
|
|
49
50
|
this.basePath = basePath || ".";
|
|
50
51
|
this.vfs = new VirtualFileSystem(this.basePath);
|
|
51
52
|
this.users = new VirtualUserManager(this.vfs, resolveRootPassword(), resolveAutoSudoForNewUsers());
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
53
|
+
// Store references to avoid TypeScript "used before assigned" errors
|
|
54
|
+
const vfs = this.vfs;
|
|
55
|
+
const users = this.users;
|
|
56
|
+
// Initialize both VFS mirror and users, ensuring all is ready before auth
|
|
57
|
+
this.initialized = (async () => {
|
|
58
|
+
await vfs.restoreMirror();
|
|
59
|
+
await users.initialize();
|
|
60
|
+
})();
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Ensures initialization is complete before allowing operations.
|
|
64
|
+
* Call this before any authentication or command execution.
|
|
65
|
+
*/
|
|
66
|
+
async ensureInitialized() {
|
|
67
|
+
await this.initialized;
|
|
56
68
|
}
|
|
57
69
|
/**
|
|
58
70
|
* Registers a new command in the shell runtime.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shell.d.ts","sourceRoot":"","sources":["../../src/VirtualShell/shell.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,GAAG,CAAC;AAMvD,OAAO,EAGN,KAAK,YAAY,EAEjB,MAAM,yBAAyB,CAAC;AAGjC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAmBpD,wBAAgB,UAAU,CACzB,UAAU,EAAE,eAAe,EAC3B,MAAM,EAAE,WAAW,EACnB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,GAAG,IAAI,EACxB,aAAa,oBAAY,EACzB,YAAY,EAAE,YAAY,YAAyB,EACnD,KAAK,EAAE,YAAY,GACjB,IAAI,
|
|
1
|
+
{"version":3,"file":"shell.d.ts","sourceRoot":"","sources":["../../src/VirtualShell/shell.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,GAAG,CAAC;AAMvD,OAAO,EAGN,KAAK,YAAY,EAEjB,MAAM,yBAAyB,CAAC;AAGjC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAmBpD,wBAAgB,UAAU,CACzB,UAAU,EAAE,eAAe,EAC3B,MAAM,EAAE,WAAW,EACnB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,GAAG,IAAI,EACxB,aAAa,oBAAY,EACzB,YAAY,EAAE,YAAY,YAAyB,EACnD,KAAK,EAAE,YAAY,GACjB,IAAI,CA8lBN"}
|
|
@@ -312,6 +312,13 @@ export function startShell(properties, stream, authUser, hostname, sessionId, re
|
|
|
312
312
|
for (let i = 0; i < input.length; i += 1) {
|
|
313
313
|
const ch = input[i];
|
|
314
314
|
if (ch === "\u0004") {
|
|
315
|
+
lineBuffer = "";
|
|
316
|
+
cursorPos = 0;
|
|
317
|
+
historyIndex = null;
|
|
318
|
+
historyDraft = "";
|
|
319
|
+
stream.write("bye\r\n");
|
|
320
|
+
pushHistory("bye");
|
|
321
|
+
await shell.vfs.flushMirror();
|
|
315
322
|
stream.write("logout\r\n");
|
|
316
323
|
stream.exit(0);
|
|
317
324
|
stream.end();
|
|
@@ -49,6 +49,7 @@ export declare class VirtualUserManager {
|
|
|
49
49
|
constructor(vfs: VirtualFileSystem, defaultRootPassword?: string, autoSudoForNewUsers?: boolean);
|
|
50
50
|
/**
|
|
51
51
|
* Loads users/sudoers from disk and ensures root account exists.
|
|
52
|
+
* Also creates the current system user if not already present.
|
|
52
53
|
*/
|
|
53
54
|
initialize(): Promise<void>;
|
|
54
55
|
/**
|
|
@@ -103,6 +104,13 @@ export declare class VirtualUserManager {
|
|
|
103
104
|
* @param password Initial plaintext password.
|
|
104
105
|
*/
|
|
105
106
|
addUser(username: string, password: string): Promise<void>;
|
|
107
|
+
/**
|
|
108
|
+
* Updates password for an existing user account.
|
|
109
|
+
*
|
|
110
|
+
* @param username Username to update.
|
|
111
|
+
* @param password New plaintext password.
|
|
112
|
+
*/
|
|
113
|
+
setPassword(username: string, password: string): Promise<void>;
|
|
106
114
|
/**
|
|
107
115
|
* Deletes existing non-root user account.
|
|
108
116
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/VirtualUserManager/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,iBAAiB,MAAM,sBAAsB,CAAC;AAE1D,gDAAgD;AAChD,MAAM,WAAW,iBAAiB;IACjC,yBAAyB;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,sDAAsD;IACtD,IAAI,EAAE,MAAM,CAAC;IACb,oDAAoD;IACpD,YAAY,EAAE,MAAM,CAAC;CACrB;AAED,2DAA2D;AAC3D,MAAM,WAAW,oBAAoB;IACpC,wCAAwC;IACxC,EAAE,EAAE,MAAM,CAAC;IACX,iCAAiC;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,2CAA2C;IAC3C,GAAG,EAAE,MAAM,CAAC;IACZ,sCAAsC;IACtC,aAAa,EAAE,MAAM,CAAC;IACtB,gCAAgC;IAChC,SAAS,EAAE,MAAM,CAAC;CAClB;AAED;;;;GAIG;AACH,qBAAa,kBAAkB;IAmB7B,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,mBAAmB;IACpC,OAAO,CAAC,QAAQ,CAAC,mBAAmB;IApBrC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAoC;IAC9D,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAmC;IAC/D,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAkC;IAC7D,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA2B;IACvD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAwC;IAC9D,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqB;IAC7C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA6B;IACpD,OAAO,CAAC,QAAQ,CAAC,cAAc,CAA2C;IAC1E,OAAO,CAAC,OAAO,CAAK;IAEpB;;;;;;OAMG;gBAEe,GAAG,EAAE,iBAAiB,EACtB,mBAAmB,GAAE,MAAe,EACpC,mBAAmB,GAAE,OAAc;IAGrD
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/VirtualUserManager/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -32,6 +32,7 @@ export class VirtualUserManager {
|
|
|
32
32
|
}
|
|
33
33
|
/**
|
|
34
34
|
* Loads users/sudoers from disk and ensures root account exists.
|
|
35
|
+
* Also creates the current system user if not already present.
|
|
35
36
|
*/
|
|
36
37
|
async initialize() {
|
|
37
38
|
this.loadFromVfs();
|
|
@@ -39,6 +40,20 @@ export class VirtualUserManager {
|
|
|
39
40
|
this.loadQuotasFromVfs();
|
|
40
41
|
this.users.set("root", this.createRecord("root", this.defaultRootPassword));
|
|
41
42
|
this.sudoers.add("root");
|
|
43
|
+
// Auto-create current system user for easier authentication
|
|
44
|
+
const currentUser = process.env.USER || process.env.USERNAME;
|
|
45
|
+
if (currentUser && currentUser !== "root" && !this.users.has(currentUser)) {
|
|
46
|
+
// Use same password as root for convenience, or a generic default
|
|
47
|
+
const userPassword = this.defaultRootPassword;
|
|
48
|
+
this.users.set(currentUser, this.createRecord(currentUser, userPassword));
|
|
49
|
+
this.sudoers.add(currentUser);
|
|
50
|
+
// Create home directory for the system user
|
|
51
|
+
const homePath = `/home/${currentUser}`;
|
|
52
|
+
if (!this.vfs.exists(homePath)) {
|
|
53
|
+
this.vfs.mkdir(homePath, 0o755);
|
|
54
|
+
this.vfs.writeFile(`${homePath}/README.txt`, `Welcome to the virtual environment, ${currentUser}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
42
57
|
await this.persist();
|
|
43
58
|
}
|
|
44
59
|
/**
|
|
@@ -163,6 +178,21 @@ export class VirtualUserManager {
|
|
|
163
178
|
}
|
|
164
179
|
await this.persist();
|
|
165
180
|
}
|
|
181
|
+
/**
|
|
182
|
+
* Updates password for an existing user account.
|
|
183
|
+
*
|
|
184
|
+
* @param username Username to update.
|
|
185
|
+
* @param password New plaintext password.
|
|
186
|
+
*/
|
|
187
|
+
async setPassword(username, password) {
|
|
188
|
+
this.validateUsername(username);
|
|
189
|
+
this.validatePassword(password);
|
|
190
|
+
if (!this.users.has(username)) {
|
|
191
|
+
throw new Error(`passwd: user '${username}' does not exist`);
|
|
192
|
+
}
|
|
193
|
+
this.users.set(username, this.createRecord(username, password));
|
|
194
|
+
await this.persist();
|
|
195
|
+
}
|
|
166
196
|
/**
|
|
167
197
|
* Deletes existing non-root user account.
|
|
168
198
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"exit.d.ts","sourceRoot":"","sources":["../../src/commands/exit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,eAAO,MAAM,WAAW,EAAE,
|
|
1
|
+
{"version":3,"file":"exit.d.ts","sourceRoot":"","sources":["../../src/commands/exit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,eAAO,MAAM,WAAW,EAAE,WAKzB,CAAC"}
|
package/dist/commands/exit.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/commands/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,KAAK,EACX,cAAc,EACd,WAAW,EACX,aAAa,EACb,WAAW,EACX,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/commands/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,KAAK,EACX,cAAc,EACd,WAAW,EACX,aAAa,EACb,WAAW,EACX,MAAM,mBAAmB,CAAC;AAgG3B,wBAAgB,eAAe,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,CAsBzD;AAED,wBAAgB,mBAAmB,CAClC,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EAAE,EAChB,GAAG,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,GAClE,WAAW,CAMb;AAED,wBAAgB,eAAe,IAAI,MAAM,EAAE,CAK1C;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS,CAKnE;AAsDD,wBAAsB,UAAU,CAC/B,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,WAAW,EACjB,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,YAAY,EACnB,KAAK,CAAC,EAAE,MAAM,GACZ,OAAO,CAAC,aAAa,CAAC,CA6DxB"}
|