typescript-virtual-container 1.5.11 → 1.6.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 +236 -456
- package/dist/.tsbuildinfo +1 -1
- package/dist/Honeypot/index.d.ts +9 -0
- package/dist/Honeypot/index.js +57 -0
- package/dist/SSHMimic/exec.d.ts +4 -0
- package/dist/SSHMimic/exec.js +4 -0
- package/dist/SSHMimic/executor.d.ts +10 -1
- package/dist/SSHMimic/executor.js +18 -8
- package/dist/SSHMimic/hostKey.d.ts +5 -0
- package/dist/SSHMimic/hostKey.js +5 -0
- package/dist/SSHMimic/loginBanner.d.ts +7 -0
- package/dist/SSHMimic/loginBanner.js +4 -0
- package/dist/SSHMimic/loginFormat.d.ts +4 -0
- package/dist/SSHMimic/loginFormat.js +4 -0
- package/dist/SSHMimic/prompt.d.ts +9 -0
- package/dist/SSHMimic/prompt.js +9 -0
- package/dist/SSHMimic/scp.d.ts +18 -0
- package/dist/SSHMimic/scp.js +14 -0
- package/dist/VirtualFileSystem/binaryPack.d.ts +7 -3
- package/dist/VirtualFileSystem/binaryPack.js +32 -10
- package/dist/VirtualFileSystem/index.d.ts +29 -0
- package/dist/VirtualFileSystem/index.js +126 -5
- package/dist/VirtualFileSystem/internalTypes.d.ts +4 -0
- package/dist/VirtualFileSystem/journal.d.ts +10 -4
- package/dist/VirtualFileSystem/journal.js +12 -2
- package/dist/VirtualFileSystem/path.d.ts +23 -1
- package/dist/VirtualFileSystem/path.js +23 -3
- package/dist/VirtualPackageManager/index.js +1 -1
- package/dist/VirtualShell/index.d.ts +3 -0
- package/dist/VirtualShell/index.js +12 -3
- package/dist/VirtualUserManager/index.d.ts +20 -1
- package/dist/VirtualUserManager/index.js +52 -15
- package/dist/commands/bc.d.ts +5 -0
- package/dist/commands/bc.js +5 -0
- package/dist/commands/cat.js +2 -2
- package/dist/commands/chgrp.d.ts +7 -0
- package/dist/commands/chgrp.js +42 -0
- package/dist/commands/chown.d.ts +7 -0
- package/dist/commands/chown.js +79 -0
- package/dist/commands/cp.js +4 -3
- package/dist/commands/dd.d.ts +7 -0
- package/dist/commands/dd.js +60 -0
- package/dist/commands/declare.js +0 -2
- package/dist/commands/expr.d.ts +7 -0
- package/dist/commands/expr.js +63 -0
- package/dist/commands/fun.d.ts +5 -0
- package/dist/commands/fun.js +5 -1
- package/dist/commands/help.d.ts +5 -0
- package/dist/commands/help.js +5 -0
- package/dist/commands/helpers.d.ts +43 -0
- package/dist/commands/helpers.js +61 -0
- package/dist/commands/id.d.ts +5 -0
- package/dist/commands/id.js +5 -0
- package/dist/commands/ip.d.ts +1 -0
- package/dist/commands/ip.js +50 -23
- package/dist/commands/jobs.js +43 -9
- package/dist/commands/kill.d.ts +1 -0
- package/dist/commands/kill.js +13 -5
- package/dist/commands/last.js +1 -1
- package/dist/commands/ln.d.ts +5 -0
- package/dist/commands/ln.js +5 -0
- package/dist/commands/ls.d.ts +5 -0
- package/dist/commands/ls.js +19 -4
- package/dist/commands/lsb-release.js +1 -1
- package/dist/commands/man.d.ts +5 -0
- package/dist/commands/man.js +5 -0
- package/dist/commands/manuals-bundle.js +242 -0
- package/dist/commands/miscutils.d.ts +43 -0
- package/dist/commands/miscutils.js +233 -0
- package/dist/commands/mkdir.js +3 -2
- package/dist/commands/mv.js +4 -3
- package/dist/commands/netcat.d.ts +7 -0
- package/dist/commands/netcat.js +64 -0
- package/dist/commands/nice.d.ts +7 -0
- package/dist/commands/nice.js +22 -0
- package/dist/commands/nohup.d.ts +7 -0
- package/dist/commands/nohup.js +18 -0
- package/dist/commands/ping.d.ts +2 -1
- package/dist/commands/ping.js +46 -8
- package/dist/commands/procUtils.d.ts +13 -0
- package/dist/commands/procUtils.js +72 -0
- package/dist/commands/pwd.d.ts +5 -0
- package/dist/commands/pwd.js +5 -0
- package/dist/commands/python.js +0 -4
- package/dist/commands/read.js +0 -1
- package/dist/commands/registry.d.ts +37 -0
- package/dist/commands/registry.js +73 -0
- package/dist/commands/rm.js +3 -2
- package/dist/commands/runtime.d.ts +47 -1
- package/dist/commands/runtime.js +60 -5
- package/dist/commands/sh.d.ts +5 -0
- package/dist/commands/sh.js +5 -0
- package/dist/commands/stat.js +3 -2
- package/dist/commands/strace.js +0 -1
- package/dist/commands/sysinfo.d.ts +19 -0
- package/dist/commands/sysinfo.js +73 -0
- package/dist/commands/test.d.ts +5 -0
- package/dist/commands/test.js +5 -0
- package/dist/commands/textutils.d.ts +25 -0
- package/dist/commands/textutils.js +171 -0
- package/dist/commands/top.d.ts +7 -0
- package/dist/commands/top.js +54 -0
- package/dist/commands/touch.js +6 -2
- package/dist/commands/tr.d.ts +5 -0
- package/dist/commands/tr.js +5 -0
- package/dist/commands/w.js +1 -1
- package/dist/commands/which.d.ts +5 -0
- package/dist/commands/which.js +5 -0
- package/dist/index.d.ts +10 -2
- package/dist/index.js +4 -0
- package/dist/modules/VirtualNetworkManager.d.ts +54 -0
- package/dist/modules/VirtualNetworkManager.js +144 -0
- package/dist/modules/linuxRootfs.d.ts +4 -3
- package/dist/modules/linuxRootfs.js +115 -74
- package/dist/modules/neofetch.d.ts +2 -0
- package/dist/modules/neofetch.js +3 -2
- package/dist/modules/pacmanGame.d.ts +2 -0
- package/dist/modules/pacmanGame.js +1 -0
- package/dist/modules/shellInteractive.d.ts +2 -0
- package/dist/modules/shellInteractive.js +2 -0
- package/dist/modules/shellRuntime.d.ts +7 -0
- package/dist/modules/shellRuntime.js +6 -0
- package/dist/modules/webTermRenderer.js +0 -7
- package/dist/types/commands.d.ts +1 -1
- package/dist/types/vfs.d.ts +8 -0
- package/dist/utils/argv.d.ts +22 -3
- package/dist/utils/argv.js +22 -3
- package/dist/utils/perfLogger.d.ts +10 -2
- package/dist/utils/perfLogger.js +7 -14
- package/dist/utils/shellSession.d.ts +35 -0
- package/dist/utils/shellSession.js +35 -0
- package/package.json +1 -1
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VirtualNetworkManager — a configurable virtual network stack.
|
|
3
|
+
*
|
|
4
|
+
* Maintains a routing table, ARP cache, and interface list with simulated
|
|
5
|
+
* latency and packet loss. Used by the `ip`, `ping`, and `netstat` commands
|
|
6
|
+
* to produce dynamic, deterministic output instead of hardcoded strings.
|
|
7
|
+
*/
|
|
8
|
+
export interface VirtualInterface {
|
|
9
|
+
name: string;
|
|
10
|
+
type: "loopback" | "ether";
|
|
11
|
+
mac: string;
|
|
12
|
+
mtu: number;
|
|
13
|
+
state: "UP" | "DOWN";
|
|
14
|
+
ipv4: string;
|
|
15
|
+
ipv4Mask: number;
|
|
16
|
+
ipv6: string;
|
|
17
|
+
}
|
|
18
|
+
export interface VirtualRoute {
|
|
19
|
+
destination: string;
|
|
20
|
+
gateway: string;
|
|
21
|
+
netmask: string;
|
|
22
|
+
device: string;
|
|
23
|
+
flags: string;
|
|
24
|
+
}
|
|
25
|
+
export interface VirtualArpEntry {
|
|
26
|
+
ip: string;
|
|
27
|
+
mac: string;
|
|
28
|
+
device: string;
|
|
29
|
+
state: "REACHABLE" | "STALE" | "PERMANENT";
|
|
30
|
+
}
|
|
31
|
+
export declare class VirtualNetworkManager {
|
|
32
|
+
private interfaces;
|
|
33
|
+
private routes;
|
|
34
|
+
private arpCache;
|
|
35
|
+
getInterfaces(): VirtualInterface[];
|
|
36
|
+
getRoutes(): VirtualRoute[];
|
|
37
|
+
getArpCache(): VirtualArpEntry[];
|
|
38
|
+
addRoute(dest: string, gateway: string, netmask: string, device: string): void;
|
|
39
|
+
delRoute(dest: string): boolean;
|
|
40
|
+
setInterfaceState(name: string, state: "UP" | "DOWN"): boolean;
|
|
41
|
+
setInterfaceIp(name: string, ipv4: string, mask: number): boolean;
|
|
42
|
+
/** Ping simulation: returns latency in ms, or -1 if unreachable. */
|
|
43
|
+
ping(host: string): number;
|
|
44
|
+
/** Get formatted output for `ip addr`. */
|
|
45
|
+
formatIpAddr(): string;
|
|
46
|
+
/** Get formatted output for `ip route`. */
|
|
47
|
+
formatIpRoute(): string;
|
|
48
|
+
/** Get formatted output for `ip link`. */
|
|
49
|
+
formatIpLink(): string;
|
|
50
|
+
/** Get formatted output for `ip neigh`. */
|
|
51
|
+
formatIpNeigh(): string;
|
|
52
|
+
private _maskToCidr;
|
|
53
|
+
private _ipForDevice;
|
|
54
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VirtualNetworkManager — a configurable virtual network stack.
|
|
3
|
+
*
|
|
4
|
+
* Maintains a routing table, ARP cache, and interface list with simulated
|
|
5
|
+
* latency and packet loss. Used by the `ip`, `ping`, and `netstat` commands
|
|
6
|
+
* to produce dynamic, deterministic output instead of hardcoded strings.
|
|
7
|
+
*/
|
|
8
|
+
function randomMac() {
|
|
9
|
+
const hex = () => Math.floor(Math.random() * 256).toString(16).padStart(2, "0");
|
|
10
|
+
return `02:42:${hex()}:${hex()}:${hex()}:${hex()}`;
|
|
11
|
+
}
|
|
12
|
+
export class VirtualNetworkManager {
|
|
13
|
+
interfaces = [
|
|
14
|
+
{
|
|
15
|
+
name: "lo",
|
|
16
|
+
type: "loopback",
|
|
17
|
+
mac: "00:00:00:00:00:00",
|
|
18
|
+
mtu: 65536,
|
|
19
|
+
state: "UP",
|
|
20
|
+
ipv4: "127.0.0.1",
|
|
21
|
+
ipv4Mask: 8,
|
|
22
|
+
ipv6: "::1",
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
name: "eth0",
|
|
26
|
+
type: "ether",
|
|
27
|
+
mac: randomMac(),
|
|
28
|
+
mtu: 1500,
|
|
29
|
+
state: "UP",
|
|
30
|
+
ipv4: "10.0.0.2",
|
|
31
|
+
ipv4Mask: 24,
|
|
32
|
+
ipv6: "fe80::42:aff:fe00:2",
|
|
33
|
+
},
|
|
34
|
+
];
|
|
35
|
+
routes = [
|
|
36
|
+
{ destination: "default", gateway: "10.0.0.1", netmask: "0.0.0.0", device: "eth0", flags: "UG" },
|
|
37
|
+
{ destination: "10.0.0.0", gateway: "0.0.0.0", netmask: "255.255.255.0", device: "eth0", flags: "U" },
|
|
38
|
+
{ destination: "127.0.0.0", gateway: "0.0.0.0", netmask: "255.0.0.0", device: "lo", flags: "U" },
|
|
39
|
+
];
|
|
40
|
+
arpCache = [
|
|
41
|
+
{ ip: "10.0.0.1", mac: "02:42:0a:00:00:01", device: "eth0", state: "REACHABLE" },
|
|
42
|
+
];
|
|
43
|
+
getInterfaces() {
|
|
44
|
+
return [...this.interfaces];
|
|
45
|
+
}
|
|
46
|
+
getRoutes() {
|
|
47
|
+
return [...this.routes];
|
|
48
|
+
}
|
|
49
|
+
getArpCache() {
|
|
50
|
+
return [...this.arpCache];
|
|
51
|
+
}
|
|
52
|
+
addRoute(dest, gateway, netmask, device) {
|
|
53
|
+
this.routes.push({ destination: dest, gateway, netmask, device, flags: "UG" });
|
|
54
|
+
}
|
|
55
|
+
delRoute(dest) {
|
|
56
|
+
const idx = this.routes.findIndex((r) => r.destination === dest);
|
|
57
|
+
if (idx === -1)
|
|
58
|
+
return false;
|
|
59
|
+
this.routes.splice(idx, 1);
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
setInterfaceState(name, state) {
|
|
63
|
+
const iface = this.interfaces.find((i) => i.name === name);
|
|
64
|
+
if (!iface)
|
|
65
|
+
return false;
|
|
66
|
+
iface.state = state;
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
setInterfaceIp(name, ipv4, mask) {
|
|
70
|
+
const iface = this.interfaces.find((i) => i.name === name);
|
|
71
|
+
if (!iface)
|
|
72
|
+
return false;
|
|
73
|
+
iface.ipv4 = ipv4;
|
|
74
|
+
iface.ipv4Mask = mask;
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
/** Ping simulation: returns latency in ms, or -1 if unreachable. */
|
|
78
|
+
ping(host) {
|
|
79
|
+
// Loopback always works
|
|
80
|
+
if (host === "127.0.0.1" || host === "localhost" || host === "::1") {
|
|
81
|
+
return 0.05 + Math.random() * 0.1;
|
|
82
|
+
}
|
|
83
|
+
// Check ARP cache / routing
|
|
84
|
+
const arp = this.arpCache.find((e) => e.ip === host);
|
|
85
|
+
if (arp && arp.state === "REACHABLE") {
|
|
86
|
+
return 0.5 + Math.random() * 2;
|
|
87
|
+
}
|
|
88
|
+
// Simulate random packet loss (5%)
|
|
89
|
+
if (Math.random() < 0.05)
|
|
90
|
+
return -1;
|
|
91
|
+
return 0.8 + Math.random() * 5;
|
|
92
|
+
}
|
|
93
|
+
/** Get formatted output for `ip addr`. */
|
|
94
|
+
formatIpAddr() {
|
|
95
|
+
const lines = [];
|
|
96
|
+
let idx = 1;
|
|
97
|
+
for (const iface of this.interfaces) {
|
|
98
|
+
const flags = iface.state === "UP"
|
|
99
|
+
? (iface.type === "loopback" ? "LOOPBACK,UP,LOWER_UP" : "BROADCAST,MULTICAST,UP,LOWER_UP")
|
|
100
|
+
: "DOWN";
|
|
101
|
+
lines.push(`${idx}: ${iface.name}: <${flags}> mtu ${iface.mtu} qdisc mq state ${iface.state === "UP" ? "UNKNOWN" : "DOWN"} group default qlen 1000`);
|
|
102
|
+
lines.push(` link/${iface.type === "loopback" ? "loopback" : "ether"} ${iface.mac} brd ff:ff:ff:ff:ff:ff`);
|
|
103
|
+
lines.push(` inet ${iface.ipv4}/${iface.ipv4Mask} scope global ${iface.name}`);
|
|
104
|
+
lines.push(` valid_lft forever preferred_lft forever`);
|
|
105
|
+
lines.push(` inet6 ${iface.ipv6}/64 scope link`);
|
|
106
|
+
lines.push(` valid_lft forever preferred_lft forever`);
|
|
107
|
+
idx++;
|
|
108
|
+
}
|
|
109
|
+
return lines.join("\n");
|
|
110
|
+
}
|
|
111
|
+
/** Get formatted output for `ip route`. */
|
|
112
|
+
formatIpRoute() {
|
|
113
|
+
return this.routes.map((r) => {
|
|
114
|
+
if (r.destination === "default") {
|
|
115
|
+
return `default via ${r.gateway} dev ${r.device}`;
|
|
116
|
+
}
|
|
117
|
+
return `${r.destination}/${this._maskToCidr(r.netmask)} dev ${r.device} proto kernel scope link src ${this._ipForDevice(r.device)}`;
|
|
118
|
+
}).join("\n");
|
|
119
|
+
}
|
|
120
|
+
/** Get formatted output for `ip link`. */
|
|
121
|
+
formatIpLink() {
|
|
122
|
+
const lines = [];
|
|
123
|
+
let idx = 1;
|
|
124
|
+
for (const iface of this.interfaces) {
|
|
125
|
+
const flags = iface.state === "UP"
|
|
126
|
+
? (iface.type === "loopback" ? "LOOPBACK,UP,LOWER_UP" : "BROADCAST,MULTICAST,UP,LOWER_UP")
|
|
127
|
+
: "DOWN";
|
|
128
|
+
lines.push(`${idx}: ${iface.name}: <${flags}> mtu ${iface.mtu} qdisc mq state ${iface.state === "UP" ? "UNKNOWN" : "DOWN"} mode DEFAULT group default qlen 1000`);
|
|
129
|
+
lines.push(` link/${iface.type === "loopback" ? "loopback" : "ether"} ${iface.mac} brd ff:ff:ff:ff:ff:ff`);
|
|
130
|
+
idx++;
|
|
131
|
+
}
|
|
132
|
+
return lines.join("\n");
|
|
133
|
+
}
|
|
134
|
+
/** Get formatted output for `ip neigh`. */
|
|
135
|
+
formatIpNeigh() {
|
|
136
|
+
return this.arpCache.map((e) => `${e.ip} dev ${e.device} lladdr ${e.mac} ${e.state}`).join("\n");
|
|
137
|
+
}
|
|
138
|
+
_maskToCidr(mask) {
|
|
139
|
+
return mask.split(".").reduce((acc, oct) => acc + (parseInt(oct, 10) ? parseInt(oct, 10).toString(2).split("1").length - 1 : 0), 0);
|
|
140
|
+
}
|
|
141
|
+
_ipForDevice(device) {
|
|
142
|
+
return this.interfaces.find((i) => i.name === device)?.ipv4 ?? "0.0.0.0";
|
|
143
|
+
}
|
|
144
|
+
}
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
import VirtualFileSystem from "../VirtualFileSystem";
|
|
21
21
|
import type { ShellProperties } from "../VirtualShell";
|
|
22
22
|
import type { VirtualActiveSession, VirtualUserManager } from "../VirtualUserManager";
|
|
23
|
+
import type { VirtualNetworkManager } from "./VirtualNetworkManager";
|
|
23
24
|
/**
|
|
24
25
|
* Sync `/etc/passwd`, `/etc/group`, and `/etc/shadow` from the
|
|
25
26
|
* VirtualUserManager's current user list into the VFS.
|
|
@@ -32,7 +33,7 @@ export declare function syncEtcPasswd(vfs: VirtualFileSystem, users: VirtualUser
|
|
|
32
33
|
*
|
|
33
34
|
* Safe to call repeatedly — acts as a live kernel state snapshot.
|
|
34
35
|
*/
|
|
35
|
-
export declare function refreshProc(vfs: VirtualFileSystem, props: ShellProperties, hostname: string, shellStartTime: number, sessions?: VirtualActiveSession[]): void;
|
|
36
|
+
export declare function refreshProc(vfs: VirtualFileSystem, props: ShellProperties, hostname: string, shellStartTime: number, sessions?: VirtualActiveSession[], network?: VirtualNetworkManager): void;
|
|
36
37
|
/**
|
|
37
38
|
* Build or retrieve the static rootfs VFSB snapshot for the given
|
|
38
39
|
* hostname + ShellProperties combination.
|
|
@@ -51,7 +52,7 @@ export declare function getStaticRootfsSnapshot(hostname: string, props: ShellPr
|
|
|
51
52
|
* @param shellStartTime Unix ms of shell creation (for uptime).
|
|
52
53
|
* @param sessions Active sessions (for /proc/<pid> population).
|
|
53
54
|
*/
|
|
54
|
-
export declare function bootstrapLinuxRootfs(vfs: VirtualFileSystem, users: VirtualUserManager, hostname: string, props: ShellProperties, shellStartTime: number, sessions?: VirtualActiveSession[]): void;
|
|
55
|
+
export declare function bootstrapLinuxRootfs(vfs: VirtualFileSystem, users: VirtualUserManager, hostname: string, props: ShellProperties, shellStartTime: number, sessions?: VirtualActiveSession[], network?: VirtualNetworkManager): void;
|
|
55
56
|
/**
|
|
56
57
|
* Engine for runtimes that want periodic /proc refresh (e.g. web shell
|
|
57
58
|
* with live `top`/`ps` output). Call `.boot()` once, then `.tick()` on
|
|
@@ -63,7 +64,7 @@ export declare function bootstrapLinuxRootfs(vfs: VirtualFileSystem, users: Virt
|
|
|
63
64
|
* setInterval(() => engine.tick(shell.listActiveSessions()), 5000);
|
|
64
65
|
* ```
|
|
65
66
|
*/
|
|
66
|
-
export declare function createLinuxRootfsEngine(vfs: VirtualFileSystem, props: ShellProperties, hostname: string, startTime: number): {
|
|
67
|
+
export declare function createLinuxRootfsEngine(vfs: VirtualFileSystem, props: ShellProperties, hostname: string, startTime: number, network?: VirtualNetworkManager): {
|
|
67
68
|
boot(users: VirtualUserManager, sessions?: VirtualActiveSession[]): void;
|
|
68
69
|
tick(sessions?: VirtualActiveSession[]): void;
|
|
69
70
|
};
|
|
@@ -505,7 +505,7 @@ function bootProcLog(vfs, props) {
|
|
|
505
505
|
*
|
|
506
506
|
* Safe to call repeatedly — acts as a live kernel state snapshot.
|
|
507
507
|
*/
|
|
508
|
-
export function refreshProc(vfs, props, hostname, shellStartTime, sessions = []) {
|
|
508
|
+
export function refreshProc(vfs, props, hostname, shellStartTime, sessions = [], network) {
|
|
509
509
|
ensureDir(vfs, "/proc");
|
|
510
510
|
const uptimeSec = Math.floor((Date.now() - shellStartTime) / 1000);
|
|
511
511
|
const idleSec = Math.floor(uptimeSec * 0.9);
|
|
@@ -589,6 +589,59 @@ export function refreshProc(vfs, props, hostname, shellStartTime, sessions = [])
|
|
|
589
589
|
const load = (Math.random() * 0.3).toFixed(2);
|
|
590
590
|
const numProcs = 1 + sessions.length;
|
|
591
591
|
write(vfs, "/proc/loadavg", `${load} ${load} ${load} ${numProcs}/${numProcs} 1\n`);
|
|
592
|
+
// /proc/stat — CPU statistics
|
|
593
|
+
const cpuCount = os.cpus().length;
|
|
594
|
+
const userJiffies = Math.floor(uptimeSec * 100);
|
|
595
|
+
const niceJiffies = Math.floor(uptimeSec * 2);
|
|
596
|
+
const systemJiffies = Math.floor(uptimeSec * 30);
|
|
597
|
+
const idleJiffies = Math.floor(uptimeSec * 800);
|
|
598
|
+
const iowaitJiffies = Math.floor(uptimeSec * 5);
|
|
599
|
+
const irqJiffies = Math.floor(uptimeSec * 1);
|
|
600
|
+
const softirqJiffies = Math.floor(uptimeSec * 2);
|
|
601
|
+
const stealJiffies = Math.floor(uptimeSec * 0);
|
|
602
|
+
const totalJiffies = userJiffies + niceJiffies + systemJiffies + idleJiffies + iowaitJiffies + irqJiffies + softirqJiffies + stealJiffies;
|
|
603
|
+
const cpuStats = `cpu ${userJiffies} ${niceJiffies} ${systemJiffies} ${idleJiffies} ${iowaitJiffies} ${irqJiffies} ${softirqJiffies} ${stealJiffies} 0 0\n`;
|
|
604
|
+
const perCpuStats = Array.from({ length: cpuCount }, (_, i) => `cpu${i} ${Math.floor(userJiffies / cpuCount)} ${Math.floor(niceJiffies / cpuCount)} ${Math.floor(systemJiffies / cpuCount)} ${Math.floor(idleJiffies / cpuCount)} ${Math.floor(iowaitJiffies / cpuCount)} ${Math.floor(irqJiffies / cpuCount)} ${Math.floor(softirqJiffies / cpuCount)} ${Math.floor(stealJiffies / cpuCount)} 0 0`).join("\n");
|
|
605
|
+
write(vfs, "/proc/stat", `${cpuStats}${perCpuStats}\nintr ${Math.floor(totalJiffies * 2)} 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\nctxt ${Math.floor(totalJiffies * 50)}\nbtime ${Math.floor(shellStartTime / 1000)}\nprocesses ${numProcs + 10}\nprocs_running 1\nprocs_blocked 0\n`);
|
|
606
|
+
// /proc/vmstat — virtual memory stats
|
|
607
|
+
const pgpgin = Math.floor(totalJiffies * 0.5);
|
|
608
|
+
const pgpgout = Math.floor(totalJiffies * 0.3);
|
|
609
|
+
const pswpin = 0;
|
|
610
|
+
const pswpout = 0;
|
|
611
|
+
const pgalloc = Math.floor(totalJiffies * 2);
|
|
612
|
+
const pgfault = pgalloc + Math.floor(totalJiffies * 0.5);
|
|
613
|
+
const pgmajfault = Math.floor(totalJiffies * 0.01);
|
|
614
|
+
write(vfs, "/proc/vmstat", `nr_free_pages ${Math.floor(freeMemKb / 4)}\nnr_zone_inactive_anon 0\nnr_zone_active_anon 0\nnr_zone_inactive_file ${Math.floor(cachedKb / 4)}\nnr_zone_active_file ${Math.floor(buffersKb / 4)}\nnr_zone_unevictable 0\nnr_zone_write_pending 0\nnr_mlock 0\nnr_page_table_pages ${pageTablesKb}\nnr_kernel_stack ${Math.floor(totalMemKb * 0.0005)}\nnr_bounce 0\nnr_zspages 0\nnr_free_cma 0\nnuma_hit ${Math.floor(totalJiffies * 3)}\nnuma_miss 0\nnuma_foreign 0\nnuma_interleave 0\nnuma_local ${Math.floor(totalJiffies * 3)}\nnuma_other 0\nnr_inactive_anon 0\nnr_active_anon 0\nnr_inactive_file ${Math.floor(cachedKb / 4)}\nnr_active_file ${Math.floor(buffersKb / 4)}\nnr_unevictable 0\nnr_slab_reclaimable ${Math.floor(slabKb * 0.6)}\nnr_slab_unreclaimable ${Math.floor(slabKb * 0.4)}\nnr_isolated_anon 0\nnr_isolated_file 0\nworkingset_nodes 0\nworkingset_refault 0\nworkingset_activate 0\nworkingset_restore 0\nworkingset_nodereclaim 0\nnr_anon_pages ${Math.floor(totalMemKb * 0.001)}\nnr_mapped ${Math.floor(cachedKb * 0.4)}\nnr_file_pages ${Math.floor(cachedKb * 0.8)}\nnr_dirty ${Math.floor(totalMemKb * 0.001)}\nnr_writeback 0\nnr_writeback_temp 0\nnr_shmem ${Math.floor(totalMemKb * 0.005)}\nnr_shmem_hugepages 0\nnr_shmem_pmdmapped 0\nnr_file_hugepages 0\nnr_file_pmdmapped 0\nnr_anon_transparent_hugepages 0\nnr_vmscan_write 0\nnr_vmscan_immediate_reclaim 0\nnr_dirtied ${Math.floor(totalJiffies * 2)}\nnr_written ${Math.floor(totalJiffies * 2)}\nnr_throttled_written 0\nnr_kernel_misc_reclaimable 0\nnr_reclaim_pages 0\nnr_zone_active_anon 0\nnr_zone_active_file ${Math.floor(buffersKb / 4)}\npgpgin ${pgpgin}\npgpgout ${pgpgout}\npswpin ${pswpin}\npswpout ${pswpout}\npgalloc_dma 0\npgalloc_dma32 ${Math.floor(pgalloc * 0.3)}\npgalloc_normal ${Math.floor(pgalloc * 0.7)}\npgalloc_movable 0\npgfree ${pgalloc}\npgactivate ${Math.floor(totalJiffies * 0.5)}\npgdeactivate 0\npgfault ${pgfault}\npgmajfault ${pgmajfault}\npglazyfree 0\npgrefill 0\npgsteal_kswapd 0\npgsteal_direct 0\npgscan_kswapd 0\npgscan_direct 0\npgskip_dma 0\npgskip_dma32 0\npgskip_normal 0\npgskip_movable 0\npgmigrate_success 0\npgmigrate_fail 0\ncompact_migrate_scanned 0\ncompact_free_scanned 0\ncompact_isolated 0\ncompact_stall 0\ncompact_fail 0\ncompact_success 0\nhtlb_buddy_alloc_success 0\nhtlb_buddy_alloc_fail 0\nunevictable_pgs_culled 0\nunevictable_pgs_scanned 0\nunevictable_pgs_rescued 0\nunevictable_pgs_mlocked 0\nunevictable_pgs_munlocked 0\nunevictable_pgs_cleared 0\nunevictable_pgs_stranded 0\nswap_ra 0\nswap_ra_hit 0\nnr_hugepages 0\nnr_hugepages_bootmem 0\n\n`);
|
|
615
|
+
// /proc/pressure — PSI (Pressure Stall Information)
|
|
616
|
+
ensureDir(vfs, "/proc/pressure");
|
|
617
|
+
const someAvg10 = (Math.random() * 0.3).toFixed(2);
|
|
618
|
+
const someAvg60 = (Math.random() * 0.2 + 0.1).toFixed(2);
|
|
619
|
+
const someAvg300 = (Math.random() * 0.1 + 0.05).toFixed(2);
|
|
620
|
+
const someTotal = Math.floor(totalJiffies * 10);
|
|
621
|
+
write(vfs, "/proc/pressure/cpu", `some avg10=${someAvg10} avg60=${someAvg60} avg300=${someAvg300} total=${someTotal}\n`);
|
|
622
|
+
write(vfs, "/proc/pressure/memory", `some avg10=${(Number(someAvg10) * 0.5).toFixed(2)} avg60=${(Number(someAvg60) * 0.3).toFixed(2)} avg300=${(Number(someAvg300) * 0.2).toFixed(2)} total=${Math.floor(someTotal * 0.3)}\n`);
|
|
623
|
+
write(vfs, "/proc/pressure/io", `some avg10=${(Number(someAvg10) * 0.7).toFixed(2)} avg60=${(Number(someAvg60) * 0.5).toFixed(2)} avg300=${(Number(someAvg300) * 0.3).toFixed(2)} total=${Math.floor(someTotal * 0.5)}\n`);
|
|
624
|
+
// /proc/modules
|
|
625
|
+
write(vfs, "/proc/modules", `${[
|
|
626
|
+
"virtio 163840 10 - Live 0x0000000000000000",
|
|
627
|
+
"virtio_ring 28672 10 virtio, Live 0x0000000000000000",
|
|
628
|
+
"virtio_blk 20480 10 - Live 0x0000000000000000",
|
|
629
|
+
"virtio_net 57344 10 - Live 0x0000000000000000",
|
|
630
|
+
"virtio_console 28672 10 - Live 0x0000000000000000",
|
|
631
|
+
"virtio_pci 24576 10 - Live 0x0000000000000000",
|
|
632
|
+
"virtio_pci_legacy_dev 12288 1 virtio_pci, Live 0x0000000000000000",
|
|
633
|
+
"virtio_pci_modern_dev 16384 1 virtio_pci, Live 0x0000000000000000",
|
|
634
|
+
"ext4 847872 10 - Live 0x0000000000000000",
|
|
635
|
+
"jbd2 131072 1 ext4, Live 0x0000000000000000",
|
|
636
|
+
"mbcache 16384 1 ext4, Live 0x0000000000000000",
|
|
637
|
+
"fuse 172032 10 - Live 0x0000000000000000",
|
|
638
|
+
"overlay 131072 10 - Live 0x0000000000000000",
|
|
639
|
+
"nf_tables 188416 10 - Live 0x0000000000000000",
|
|
640
|
+
"tun 49152 10 - Live 0x0000000000000000",
|
|
641
|
+
"bridge 286720 10 - Live 0x0000000000000000",
|
|
642
|
+
"dm_mod 155648 10 - Live 0x0000000000000000",
|
|
643
|
+
"crc32c_intel 24576 10 - Live 0x0000000000000000",
|
|
644
|
+
].join("\n")}\n`);
|
|
592
645
|
// /proc/cmdline — Firecracker boot args
|
|
593
646
|
write(vfs, "/proc/cmdline", `console=ttyS0 reboot=k panic=1 nomodule random.trust_cpu=1 ipv6.disable=1 swiotlb=noforce rdinit=/process_api init_on_free=1 -- --firecracker-init --addr 0.0.0.0:2024 --max-ws-buffer-size 32768 --block-local-connections\n`);
|
|
594
647
|
// /proc/filesystems — matching real container
|
|
@@ -651,26 +704,69 @@ export function refreshProc(vfs, props, hostname, shellStartTime, sessions = [])
|
|
|
651
704
|
write(vfs, "/proc/mounts", mountsContent);
|
|
652
705
|
ensureDir(vfs, "/proc/self");
|
|
653
706
|
write(vfs, "/proc/self/mounts", mountsContent);
|
|
654
|
-
// /proc/net
|
|
707
|
+
// /proc/net — dynamic when network manager is available
|
|
655
708
|
ensureDir(vfs, "/proc/net");
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
709
|
+
if (network) {
|
|
710
|
+
const ifaces = network.getInterfaces();
|
|
711
|
+
const routes = network.getRoutes();
|
|
712
|
+
const arpCache = network.getArpCache();
|
|
713
|
+
const ipToHex = (ip) => ip.split(".")
|
|
714
|
+
.reverse()
|
|
715
|
+
.map((n) => parseInt(n, 10).toString(16).padStart(2, "0"))
|
|
716
|
+
.join("")
|
|
717
|
+
.toUpperCase();
|
|
718
|
+
// /proc/net/dev
|
|
719
|
+
const devHeader = "Inter-| Receive | Transmit\n face |bytes packets errs drop fifo frame compressed multicast|bytes packets errs drop fifo colls carrier compressed";
|
|
720
|
+
const devLines = ifaces.map((iface) => {
|
|
721
|
+
const name = iface.name.padStart(4);
|
|
722
|
+
if (iface.name === "lo") {
|
|
723
|
+
return `${name}: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0`;
|
|
724
|
+
}
|
|
725
|
+
const rxBytes = Math.floor(Math.random() * 200000);
|
|
726
|
+
const rxPkts = Math.floor(Math.random() * 2000);
|
|
727
|
+
const txBytes = Math.floor(Math.random() * 50000000);
|
|
728
|
+
const txPkts = Math.floor(Math.random() * 3000);
|
|
729
|
+
return `${name}: ${String(rxBytes).padStart(8)} ${String(rxPkts).padStart(7)} 0 0 0 0 0 0 ${String(txBytes).padStart(9)} ${String(txPkts).padStart(7)} 0 0 0 0 0 0`;
|
|
730
|
+
});
|
|
731
|
+
write(vfs, "/proc/net/dev", `${devHeader}\n${devLines.join("\n")}\n`);
|
|
732
|
+
// /proc/net/route
|
|
733
|
+
const routeLines = routes.map((r) => [
|
|
734
|
+
r.device,
|
|
735
|
+
ipToHex(r.destination === "default" ? "0.0.0.0" : r.destination),
|
|
736
|
+
ipToHex(r.gateway),
|
|
737
|
+
r.flags === "UG"
|
|
738
|
+
? "0003"
|
|
739
|
+
: r.flags === "U"
|
|
740
|
+
? "0001"
|
|
741
|
+
: "0000",
|
|
742
|
+
"0", "0", "100",
|
|
743
|
+
ipToHex(r.netmask),
|
|
744
|
+
"0", "0", "0",
|
|
745
|
+
].join("\t"));
|
|
746
|
+
write(vfs, "/proc/net/route", `Iface\tDestination\tGateway\tFlags\tRefCnt\tUse\tMetric\tMask\t\tMTU\tWindow\tIRTT\n${routeLines.join("\n")}\n`);
|
|
747
|
+
// /proc/net/arp
|
|
748
|
+
const arpLines = arpCache.map((e) => `${e.ip.padEnd(15)} 0x1 0x2 ${e.mac.padEnd(17)} * ${e.device}`);
|
|
749
|
+
write(vfs, "/proc/net/arp", `IP address HW type Flags HW address Mask Device\n${arpLines.join("\n")}\n`);
|
|
750
|
+
}
|
|
751
|
+
else {
|
|
752
|
+
write(vfs, "/proc/net/dev", "Inter-| Receive | Transmit\n face |bytes packets errs drop fifo frame compressed multicast|bytes packets errs drop fifo colls carrier compressed\n lo: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n eth0: 128628 1230 0 19 0 0 0 0 52027469 2045 0 0 0 0 0 0\n");
|
|
753
|
+
write(vfs, "/proc/net/route", "Iface\tDestination\tGateway\tFlags\tRefCnt\tUse\tMetric\tMask\t\tMTU\tWindow\tIRTT\neth0\t00000000\t0101A8C0\t0003\t0\t0\t100\t00000000\t0\t0\t0\neth0\t0000A8C0\t00000000\t0001\t0\t0\t100\t00FFFFFF\t0\t0\t0\n");
|
|
754
|
+
write(vfs, "/proc/net/arp", "IP address HW type Flags HW address Mask Device\n");
|
|
755
|
+
}
|
|
662
756
|
write(vfs, "/proc/net/if_inet6", "");
|
|
663
|
-
|
|
757
|
+
// /proc/net/tcp — fake listening sockets
|
|
758
|
+
const tcpListen = [
|
|
759
|
+
" 0: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 10000 1 0000000000000000 100 0 0 10 0",
|
|
760
|
+
" 1: 00000000:022D 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 10001 1 0000000000000000 100 0 0 10 0",
|
|
761
|
+
" 2: 00000000:0A8C 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 10002 1 0000000000000000 100 0 0 10 0",
|
|
762
|
+
].join("\n");
|
|
763
|
+
write(vfs, "/proc/net/tcp", ` sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode\n${tcpListen}\n`);
|
|
664
764
|
write(vfs, "/proc/net/tcp6", " sl local_address remote_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode\n");
|
|
665
765
|
write(vfs, "/proc/net/udp", " sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode\n");
|
|
666
|
-
write(vfs, "/proc/net/route", "Iface\tDestination\tGateway\tFlags\tRefCnt\tUse\tMetric\tMask\t\tMTU\tWindow\tIRTT\n" +
|
|
667
|
-
"eth0\t00000000\t0101A8C0\t0003\t0\t0\t100\t00000000\t0\t0\t0\n" +
|
|
668
|
-
"eth0\t0000A8C0\t00000000\t0001\t0\t0\t100\t00FFFFFF\t0\t0\t0\n");
|
|
669
|
-
write(vfs, "/proc/net/arp", "IP address HW type Flags HW address Mask Device\n");
|
|
670
766
|
write(vfs, "/proc/net/fib_trie", "Local:\n +-- 0.0.0.0/0 3 0 5\n");
|
|
671
767
|
write(vfs, "/proc/net/unix", "Num RefCount Protocol Flags Type St Inode Path\n" +
|
|
672
768
|
"0000000000000000: 00000002 00000000 00010000 0001 01 10000 /run/dbus/system_bus_socket\n");
|
|
673
|
-
write(vfs, "/proc/net/sockstat", "sockets: used
|
|
769
|
+
write(vfs, "/proc/net/sockstat", "sockets: used 11\nTCP: inuse 3 orphan 0 tw 0 alloc 3 mem 1024\nUDP: inuse 0 mem 0\nUDPLITE: inuse 0\nRAW: inuse 0\nFRAG: inuse 0 memory 0\n");
|
|
674
770
|
// /proc/partitions — virtio block devices
|
|
675
771
|
write(vfs, "/proc/partitions", `${[
|
|
676
772
|
"major minor #blocks name",
|
|
@@ -994,61 +1090,6 @@ function bootstrapUsr(vfs) {
|
|
|
994
1090
|
"hostname", "uname", "ps", "kill", "df", "du", "curl", "wget",
|
|
995
1091
|
"nano", "diff", "uniq", "xargs", "base64",
|
|
996
1092
|
];
|
|
997
|
-
// From a real container
|
|
998
|
-
// const builtins = [
|
|
999
|
-
// // core
|
|
1000
|
-
// "sh", "bash", "dash",
|
|
1001
|
-
// "ls", "cat", "echo", "grep", "find", "sort",
|
|
1002
|
-
// "head", "tail", "cut", "tr", "sed", "awk", "mawk", "gawk",
|
|
1003
|
-
// "wc", "tee", "tar", "gzip", "gunzip", "bzip2", "xz",
|
|
1004
|
-
// "touch", "mkdir", "rm", "mv", "cp", "ln", "pwd",
|
|
1005
|
-
// "chmod", "chown", "chgrp", "env", "date", "sleep",
|
|
1006
|
-
// "id", "whoami", "hostname", "uname", "ps", "kill",
|
|
1007
|
-
// "df", "du", "dd", "stat", "file",
|
|
1008
|
-
// // net
|
|
1009
|
-
// "curl", "wget", "nc", "netcat", "ss", "ip",
|
|
1010
|
-
// // editors
|
|
1011
|
-
// "nano", "vi",
|
|
1012
|
-
// // text
|
|
1013
|
-
// "diff", "uniq", "xargs", "base64", "md5sum", "sha256sum",
|
|
1014
|
-
// "strings", "hexdump", "od", "column", "fmt", "paste",
|
|
1015
|
-
// "join", "comm", "split", "csplit", "fold", "expand",
|
|
1016
|
-
// // archive
|
|
1017
|
-
// "zip", "unzip",
|
|
1018
|
-
// // process
|
|
1019
|
-
// "top", "htop", "free", "uptime", "dmesg", "lsof",
|
|
1020
|
-
// "strace", "ltrace", "pgrep", "pkill", "nohup", "nice",
|
|
1021
|
-
// // fs
|
|
1022
|
-
// "mount", "umount", "lsblk", "fdisk", "blkid", "e2fsck",
|
|
1023
|
-
// // misc
|
|
1024
|
-
// "bc", "expr", "seq", "yes", "true", "false", "test",
|
|
1025
|
-
// "readlink", "realpath", "dirname", "basename", "mktemp",
|
|
1026
|
-
// "install", "make",
|
|
1027
|
-
// // dev tools
|
|
1028
|
-
// "gcc", "gcc-13", "g++", "g++-13", "cpp", "as", "ld",
|
|
1029
|
-
// "ar", "nm", "objdump", "objcopy", "strip", "size",
|
|
1030
|
-
// "cc", "c++", "pkg-config",
|
|
1031
|
-
// // package
|
|
1032
|
-
// "apt", "apt-get", "apt-cache", "dpkg", "dpkg-query",
|
|
1033
|
-
// "lsb_release", "add-apt-repository",
|
|
1034
|
-
// // scripting
|
|
1035
|
-
// "perl", "python3", "python3.12", "pipx",
|
|
1036
|
-
// // node/npm
|
|
1037
|
-
// "node", "npm", "npx",
|
|
1038
|
-
// // java
|
|
1039
|
-
// "java", "javac", "jar", "javadoc",
|
|
1040
|
-
// // security
|
|
1041
|
-
// "openssl", "gpg", "gpg2", "gpgv", "ssh", "ssh-keygen",
|
|
1042
|
-
// "sudo", "su", "passwd", "adduser", "useradd",
|
|
1043
|
-
// // misc system
|
|
1044
|
-
// "systemctl", "journalctl", "loginctl",
|
|
1045
|
-
// "timedatectl", "localectl",
|
|
1046
|
-
// "lshw", "lscpu", "lsusb", "lspci",
|
|
1047
|
-
// // text proc
|
|
1048
|
-
// "jq", "xmllint", "pandoc",
|
|
1049
|
-
// // multimedia
|
|
1050
|
-
// "ffmpeg",
|
|
1051
|
-
// ];
|
|
1052
1093
|
for (const bin of builtins) {
|
|
1053
1094
|
ensureFile(vfs, `/usr/bin/${bin}`, `#!/bin/sh\nexec builtin ${bin} "$@"\n`, 0o755);
|
|
1054
1095
|
}
|
|
@@ -1547,7 +1588,7 @@ export function getStaticRootfsSnapshot(hostname, props) {
|
|
|
1547
1588
|
* @param shellStartTime Unix ms of shell creation (for uptime).
|
|
1548
1589
|
* @param sessions Active sessions (for /proc/<pid> population).
|
|
1549
1590
|
*/
|
|
1550
|
-
export function bootstrapLinuxRootfs(vfs, users, hostname, props, shellStartTime, sessions = []) {
|
|
1591
|
+
export function bootstrapLinuxRootfs(vfs, users, hostname, props, shellStartTime, sessions = [], network) {
|
|
1551
1592
|
const snapshot = getStaticRootfsSnapshot(hostname, props);
|
|
1552
1593
|
const hasRestoredData = vfs.getMode() === "fs" && vfs.exists("/home");
|
|
1553
1594
|
if (hasRestoredData) {
|
|
@@ -1557,7 +1598,7 @@ export function bootstrapLinuxRootfs(vfs, users, hostname, props, shellStartTime
|
|
|
1557
1598
|
vfs.importRootTree(decodeVfs(snapshot));
|
|
1558
1599
|
}
|
|
1559
1600
|
bootstrapRoot(vfs);
|
|
1560
|
-
refreshProc(vfs, props, hostname, shellStartTime, sessions);
|
|
1601
|
+
refreshProc(vfs, props, hostname, shellStartTime, sessions, network);
|
|
1561
1602
|
syncEtcPasswd(vfs, users);
|
|
1562
1603
|
}
|
|
1563
1604
|
// ─── optional live engine ─────────────────────────────────────────────────────
|
|
@@ -1572,13 +1613,13 @@ export function bootstrapLinuxRootfs(vfs, users, hostname, props, shellStartTime
|
|
|
1572
1613
|
* setInterval(() => engine.tick(shell.listActiveSessions()), 5000);
|
|
1573
1614
|
* ```
|
|
1574
1615
|
*/
|
|
1575
|
-
export function createLinuxRootfsEngine(vfs, props, hostname, startTime) {
|
|
1616
|
+
export function createLinuxRootfsEngine(vfs, props, hostname, startTime, network) {
|
|
1576
1617
|
return {
|
|
1577
1618
|
boot(users, sessions = []) {
|
|
1578
|
-
bootstrapLinuxRootfs(vfs, users, hostname, props, startTime, sessions);
|
|
1619
|
+
bootstrapLinuxRootfs(vfs, users, hostname, props, startTime, sessions, network);
|
|
1579
1620
|
},
|
|
1580
1621
|
tick(sessions = []) {
|
|
1581
|
-
refreshProc(vfs, props, hostname, startTime, sessions);
|
|
1622
|
+
refreshProc(vfs, props, hostname, startTime, sessions, network);
|
|
1582
1623
|
},
|
|
1583
1624
|
};
|
|
1584
1625
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ShellProperties } from "../VirtualShell";
|
|
2
|
+
/** System info fields used to render neofetch output. */
|
|
2
3
|
export interface NeofetchInfo {
|
|
3
4
|
user: string;
|
|
4
5
|
host: string;
|
|
@@ -15,4 +16,5 @@ export interface NeofetchInfo {
|
|
|
15
16
|
memoryUsedMiB?: number;
|
|
16
17
|
memoryTotalMiB?: number;
|
|
17
18
|
}
|
|
19
|
+
/** Builds the full neofetch ASCII art system-info string. */
|
|
18
20
|
export declare function buildNeofetchOutput(info: NeofetchInfo): string;
|
package/dist/modules/neofetch.js
CHANGED
|
@@ -133,7 +133,7 @@ function countDpkgPackages() {
|
|
|
133
133
|
const matches = data.match(/^Package:\s+/gm);
|
|
134
134
|
return matches?.length ?? 0;
|
|
135
135
|
}
|
|
136
|
-
catch { }
|
|
136
|
+
catch { /* dpkg status file may not exist */ }
|
|
137
137
|
}
|
|
138
138
|
return undefined;
|
|
139
139
|
}
|
|
@@ -148,7 +148,7 @@ function countSnapPackages() {
|
|
|
148
148
|
const count = entries.filter((entry) => entry.isDirectory()).length;
|
|
149
149
|
return count;
|
|
150
150
|
}
|
|
151
|
-
catch { }
|
|
151
|
+
catch { /* snap directory may not be readable */ }
|
|
152
152
|
}
|
|
153
153
|
return undefined;
|
|
154
154
|
}
|
|
@@ -216,6 +216,7 @@ function resolveDefaults(info) {
|
|
|
216
216
|
memoryTotalMiB: info.memoryTotalMiB ?? toMiB(totalMem),
|
|
217
217
|
};
|
|
218
218
|
}
|
|
219
|
+
/** Builds the full neofetch ASCII art system-info string. */
|
|
219
220
|
export function buildNeofetchOutput(info) {
|
|
220
221
|
const fields = resolveDefaults(info);
|
|
221
222
|
const uptime = formatUptime(fields.uptimeSeconds);
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import type { ShellStream } from "../types/streams";
|
|
2
2
|
import type { TerminalSize } from "./shellRuntime";
|
|
3
|
+
/** Configuration options for creating a PacmanGame instance. */
|
|
3
4
|
export interface PacmanGameOptions {
|
|
4
5
|
stream: ShellStream;
|
|
5
6
|
terminalSize: TerminalSize;
|
|
6
7
|
onExit: () => void;
|
|
7
8
|
}
|
|
9
|
+
/** Classic Pacman game that runs in the terminal with ANSI rendering. */
|
|
8
10
|
export declare class PacmanGame {
|
|
9
11
|
private stream;
|
|
10
12
|
private onExit;
|
|
@@ -92,6 +92,7 @@ const DR = [0, 1, 0, -1];
|
|
|
92
92
|
const DC = [1, 0, -1, 0];
|
|
93
93
|
const OPP = [2, 3, 0, 1];
|
|
94
94
|
// ── PacmanGame ────────────────────────────────────────────────────────────────
|
|
95
|
+
/** Classic Pacman game that runs in the terminal with ANSI rendering. */
|
|
95
96
|
export class PacmanGame {
|
|
96
97
|
stream;
|
|
97
98
|
onExit;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { type ChildProcessWithoutNullStreams } from "node:child_process";
|
|
2
2
|
import type { ShellStream } from "../types/streams";
|
|
3
3
|
import { type TerminalSize } from "./shellRuntime";
|
|
4
|
+
/** Spawns the nano editor as an interactive subprocess. */
|
|
4
5
|
export declare function spawnNanoEditorProcess(tempPath: string, terminalSize: TerminalSize, stream: ShellStream): ChildProcessWithoutNullStreams;
|
|
6
|
+
/** Spawns htop as an interactive subprocess, filtered to the given PIDs. */
|
|
5
7
|
export declare function spawnHtopProcess(pidList: string, terminalSize: TerminalSize, stream: ShellStream): ChildProcessWithoutNullStreams;
|
|
@@ -18,9 +18,11 @@ function spawnScriptProcess(command, terminalSize, stream) {
|
|
|
18
18
|
});
|
|
19
19
|
return proc;
|
|
20
20
|
}
|
|
21
|
+
/** Spawns the nano editor as an interactive subprocess. */
|
|
21
22
|
export function spawnNanoEditorProcess(tempPath, terminalSize, stream) {
|
|
22
23
|
return spawnScriptProcess(`nano -- ${shellQuote(tempPath)}`, terminalSize, stream);
|
|
23
24
|
}
|
|
25
|
+
/** Spawns htop as an interactive subprocess, filtered to the given PIDs. */
|
|
24
26
|
export function spawnHtopProcess(pidList, terminalSize, stream) {
|
|
25
27
|
return spawnScriptProcess(`htop -p ${shellQuote(pidList)}`, terminalSize, stream);
|
|
26
28
|
}
|
|
@@ -1,10 +1,17 @@
|
|
|
1
|
+
/** Terminal dimensions (columns × rows). */
|
|
1
2
|
export interface TerminalSize {
|
|
2
3
|
cols: number;
|
|
3
4
|
rows: number;
|
|
4
5
|
}
|
|
6
|
+
/** Shell-escapes a string for safe use in a command line. */
|
|
5
7
|
export declare function shellQuote(value: string): string;
|
|
8
|
+
/** Converts line endings to CRLF for TTY output. */
|
|
6
9
|
export declare function toTtyLines(text: string): string;
|
|
10
|
+
/** Wraps a command to run with a specific terminal size via stty. */
|
|
7
11
|
export declare function withTerminalSize(command: string, terminalSize: TerminalSize): string;
|
|
12
|
+
/** Resolves a path relative to a base working directory. */
|
|
8
13
|
export declare function resolvePath(base: string, inputPath: string): string;
|
|
14
|
+
/** Recursively collects all child PIDs of a given parent process. */
|
|
9
15
|
export declare function collectChildPids(parentPid: number): Promise<number[]>;
|
|
16
|
+
/** Returns a comma-separated PID list visible in htop, or null if none. */
|
|
10
17
|
export declare function getVisibleHtopPidList(rootPid?: number): Promise<string | null>;
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
+
/** Shell-escapes a string for safe use in a command line. */
|
|
3
4
|
export function shellQuote(value) {
|
|
4
5
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
5
6
|
}
|
|
7
|
+
/** Converts line endings to CRLF for TTY output. */
|
|
6
8
|
export function toTtyLines(text) {
|
|
7
9
|
return text
|
|
8
10
|
.replace(/\r\n/g, "\n")
|
|
9
11
|
.replace(/\r/g, "\n")
|
|
10
12
|
.replace(/\n/g, "\r\n");
|
|
11
13
|
}
|
|
14
|
+
/** Wraps a command to run with a specific terminal size via stty. */
|
|
12
15
|
export function withTerminalSize(command, terminalSize) {
|
|
13
16
|
const cols = Number.isFinite(terminalSize.cols) && terminalSize.cols > 0
|
|
14
17
|
? Math.floor(terminalSize.cols)
|
|
@@ -18,6 +21,7 @@ export function withTerminalSize(command, terminalSize) {
|
|
|
18
21
|
: 24;
|
|
19
22
|
return `stty cols ${cols} rows ${rows} 2>/dev/null; ${command}`;
|
|
20
23
|
}
|
|
24
|
+
/** Resolves a path relative to a base working directory. */
|
|
21
25
|
export function resolvePath(base, inputPath) {
|
|
22
26
|
if (!inputPath || inputPath.trim() === "" || inputPath === ".") {
|
|
23
27
|
return base;
|
|
@@ -26,6 +30,7 @@ export function resolvePath(base, inputPath) {
|
|
|
26
30
|
? path.posix.normalize(inputPath)
|
|
27
31
|
: path.posix.normalize(path.posix.join(base, inputPath));
|
|
28
32
|
}
|
|
33
|
+
/** Recursively collects all child PIDs of a given parent process. */
|
|
29
34
|
export async function collectChildPids(parentPid) {
|
|
30
35
|
try {
|
|
31
36
|
const childrenRaw = await readFile(`/proc/${parentPid}/task/${parentPid}/children`, "utf8");
|
|
@@ -42,6 +47,7 @@ export async function collectChildPids(parentPid) {
|
|
|
42
47
|
return [];
|
|
43
48
|
}
|
|
44
49
|
}
|
|
50
|
+
/** Returns a comma-separated PID list visible in htop, or null if none. */
|
|
45
51
|
export async function getVisibleHtopPidList(rootPid = process.pid) {
|
|
46
52
|
const descendants = await collectChildPids(rootPid);
|
|
47
53
|
const unique = Array.from(new Set(descendants)).sort((a, b) => a - b);
|
|
@@ -392,13 +392,6 @@ export class WebTermRenderer {
|
|
|
392
392
|
return html;
|
|
393
393
|
}
|
|
394
394
|
}
|
|
395
|
-
// const ANSI_NORMAL_TO_BRIGHT: Record<string, string> = {
|
|
396
|
-
// "#000": "#555", "#c00": "#f55", "#0c0": "#5f5", "#cc0": "#ff5",
|
|
397
|
-
// "#00c": "#55f", "#c0c": "#f5f", "#0cc": "#5ff", "#ccc": "#fff",
|
|
398
|
-
// };
|
|
399
|
-
// function boldBright(fg: string): string {
|
|
400
|
-
// return ANSI_NORMAL_TO_BRIGHT[fg] ?? fg;
|
|
401
|
-
// }
|
|
402
395
|
function escHtml(ch) {
|
|
403
396
|
if (ch === "&")
|
|
404
397
|
return "&";
|