typescript-virtual-container 1.4.9 → 1.5.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/.vscode/settings.json +1 -1
- package/README.md +117 -79
- package/builds/self-standalone.mjs +1768 -0
- package/builds/standalone-wo-sftp.js +735 -267
- package/builds/standalone.cjs +735 -267
- package/bun.lock +3 -3
- package/dist/SSHMimic/exec.js +2 -2
- package/dist/SSHMimic/exec.js.map +1 -1
- package/dist/SSHMimic/index.d.ts.map +1 -1
- package/dist/SSHMimic/index.js +2 -1
- package/dist/SSHMimic/index.js.map +1 -1
- package/dist/SSHMimic/sftp.d.ts.map +1 -1
- package/dist/SSHMimic/sftp.js +4 -3
- package/dist/SSHMimic/sftp.js.map +1 -1
- package/dist/VirtualFileSystem/index.d.ts +14 -0
- package/dist/VirtualFileSystem/index.d.ts.map +1 -1
- package/dist/VirtualFileSystem/index.js +51 -3
- package/dist/VirtualFileSystem/index.js.map +1 -1
- package/dist/VirtualFileSystem/journal.d.ts.map +1 -1
- package/dist/VirtualFileSystem/journal.js +11 -5
- package/dist/VirtualFileSystem/journal.js.map +1 -1
- package/dist/VirtualShell/shell.js +12 -12
- package/dist/VirtualShell/shell.js.map +1 -1
- package/dist/VirtualUserManager/index.js +8 -11
- package/dist/VirtualUserManager/index.js.map +1 -1
- package/dist/commands/apt.js +3 -3
- package/dist/commands/apt.js.map +1 -1
- package/dist/commands/cd.d.ts.map +1 -1
- package/dist/commands/cd.js +2 -1
- package/dist/commands/cd.js.map +1 -1
- package/dist/commands/helpers.d.ts +1 -1
- package/dist/commands/helpers.d.ts.map +1 -1
- package/dist/commands/helpers.js +3 -2
- package/dist/commands/helpers.js.map +1 -1
- package/dist/commands/index.d.ts +1 -1
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +1 -1
- package/dist/commands/index.js.map +1 -1
- package/dist/commands/lsb-release.js +1 -1
- package/dist/commands/lsb-release.js.map +1 -1
- package/dist/commands/runtime.d.ts +2 -0
- package/dist/commands/runtime.d.ts.map +1 -1
- package/dist/commands/runtime.js +5 -1
- package/dist/commands/runtime.js.map +1 -1
- package/dist/modules/linuxRootfs.d.ts +9 -5
- package/dist/modules/linuxRootfs.d.ts.map +1 -1
- package/dist/modules/linuxRootfs.js +1079 -148
- package/dist/modules/linuxRootfs.js.map +1 -1
- package/dist/self-standalone.js +22 -12
- package/dist/self-standalone.js.map +1 -1
- package/docs/assets/hierarchy.js +1 -1
- package/docs/classes/HoneyPot.html +9 -9
- package/docs/classes/IdleManager.html +8 -8
- package/docs/classes/SshClient.html +18 -18
- package/docs/classes/VirtualFileSystem.html +34 -34
- package/docs/classes/VirtualPackageManager.html +13 -13
- package/docs/classes/VirtualSftpServer.html +3 -3
- package/docs/classes/VirtualShell.html +28 -28
- package/docs/classes/VirtualSshServer.html +5 -5
- package/docs/classes/VirtualUserManager.html +27 -27
- package/docs/functions/assertDiff.html +2 -2
- package/docs/functions/diffSnapshots.html +2 -2
- package/docs/functions/formatDiff.html +2 -2
- package/docs/functions/getArg.html +2 -2
- package/docs/functions/getFlag.html +2 -2
- package/docs/functions/ifFlag.html +2 -2
- package/docs/hierarchy.html +1 -1
- package/docs/index.html +10 -8
- package/docs/interfaces/AuditLogEntry.html +3 -3
- package/docs/interfaces/CommandContext.html +12 -12
- package/docs/interfaces/CommandResult.html +13 -13
- package/docs/interfaces/ExecStream.html +6 -6
- package/docs/interfaces/HoneyPotStats.html +3 -3
- package/docs/interfaces/IdleManagerOptions.html +3 -3
- package/docs/interfaces/InstalledPackage.html +11 -11
- package/docs/interfaces/NanoEditorSession.html +5 -5
- package/docs/interfaces/PackageDefinition.html +14 -14
- package/docs/interfaces/PackageFile.html +5 -5
- package/docs/interfaces/PasswordChallenge.html +9 -9
- package/docs/interfaces/RemoveOptions.html +3 -3
- package/docs/interfaces/ShellEnv.html +4 -4
- package/docs/interfaces/ShellModule.html +8 -8
- package/docs/interfaces/ShellProperties.html +5 -5
- package/docs/interfaces/ShellStream.html +7 -7
- package/docs/interfaces/SudoChallenge.html +9 -9
- package/docs/interfaces/VfsBaseNode.html +7 -7
- package/docs/interfaces/VfsDiff.html +6 -6
- package/docs/interfaces/VfsDiffEntry.html +4 -4
- package/docs/interfaces/VfsDiffModified.html +6 -6
- package/docs/interfaces/VfsDirectoryNode.html +8 -8
- package/docs/interfaces/VfsFileNode.html +9 -9
- package/docs/interfaces/VfsOptions.html +6 -6
- package/docs/interfaces/VfsSnapshot.html +3 -3
- package/docs/interfaces/VfsSnapshotBaseNode.html +4 -4
- package/docs/interfaces/VfsSnapshotDirectoryNode.html +5 -5
- package/docs/interfaces/VfsSnapshotFileNode.html +6 -6
- package/docs/interfaces/VirtualActiveSession.html +7 -7
- package/docs/interfaces/VirtualSftpServerOptions.html +3 -3
- package/docs/interfaces/VirtualShellVfsLike.html +3 -3
- package/docs/interfaces/VirtualShellVfsOptions.html +3 -3
- package/docs/interfaces/WriteFileOptions.html +4 -4
- package/docs/modules.html +1 -1
- package/docs/types/ArgParseOptions.html +3 -3
- package/docs/types/CommandMode.html +2 -2
- package/docs/types/CommandOutcome.html +2 -2
- package/docs/types/IdleState.html +1 -1
- package/docs/types/VfsNodeStats.html +2 -2
- package/docs/types/VfsNodeType.html +2 -2
- package/docs/types/VfsPersistenceMode.html +2 -2
- package/docs/types/VfsSnapshotNode.html +2 -2
- package/package.json +3 -3
- package/src/SSHMimic/exec.ts +2 -2
- package/src/SSHMimic/index.ts +2 -1
- package/src/SSHMimic/sftp.ts +4 -3
- package/src/VirtualFileSystem/index.ts +46 -3
- package/src/VirtualFileSystem/journal.ts +10 -5
- package/src/VirtualShell/shell.ts +12 -12
- package/src/VirtualUserManager/index.ts +11 -11
- package/src/commands/apt.ts +3 -3
- package/src/commands/cd.ts +2 -1
- package/src/commands/helpers.ts +3 -2
- package/src/commands/index.ts +1 -1
- package/src/commands/lsb-release.ts +1 -1
- package/src/commands/runtime.ts +6 -1
- package/src/modules/linuxRootfs.ts +1293 -207
- package/src/self-standalone.ts +26 -12
- package/tests/new-features.test.ts +2 -2
- package/tests/sftp.test.ts +13 -13
- package/builds/self-standalone.js +0 -1299
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
<!DOCTYPE html><html class="default" lang="en" data-base="../"><head><meta charset="utf-8"/><meta http-equiv="x-ua-compatible" content="IE=edge"/><title>VfsNodeStats | typescript-virtual-container - v1.
|
|
2
|
-
</div><aside class="tsd-sources"><ul><li>Defined in <a href="https://github.com/itsrealfortune/typescript-virtual-container/blob/
|
|
1
|
+
<!DOCTYPE html><html class="default" lang="en" data-base="../"><head><meta charset="utf-8"/><meta http-equiv="x-ua-compatible" content="IE=edge"/><title>VfsNodeStats | typescript-virtual-container - v1.5.0</title><meta name="description" content="Documentation for typescript-virtual-container"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="stylesheet" href="../assets/style.css"/><link rel="stylesheet" href="../assets/highlight.css"/><script defer src="../assets/main.js"></script><script async src="../assets/icons.js" id="tsd-icons-script"></script><script async src="../assets/search.js" id="tsd-search-script"></script><script async src="../assets/navigation.js" id="tsd-nav-script"></script><script async src="../assets/hierarchy.js" id="tsd-hierarchy-script"></script></head><body><script>document.documentElement.dataset.theme = localStorage.getItem("tsd-theme") || "os";document.body.style.display="none";setTimeout(() => window.app?app.showPage():document.body.style.removeProperty("display"),500)</script><header class="tsd-page-toolbar"><div class="tsd-toolbar-contents container"><a href="../index.html" class="title">typescript-virtual-container - v1.5.0</a><div id="tsd-toolbar-links"></div><button id="tsd-search-trigger" class="tsd-widget" aria-label="Search"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-search"></use></svg></button><dialog id="tsd-search" aria-label="Search"><input role="combobox" id="tsd-search-input" aria-controls="tsd-search-results" aria-autocomplete="list" aria-expanded="true" autocapitalize="off" autocomplete="off" placeholder="Search the docs" maxLength="100"/><ul role="listbox" id="tsd-search-results"></ul><div id="tsd-search-status" aria-live="polite" aria-atomic="true"><div>Preparing search index...</div></div></dialog><a href="#" class="tsd-widget menu" id="tsd-toolbar-menu-trigger" data-toggle="menu" aria-label="Menu"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-menu"></use></svg></a></div></header><div class="container container-main"><div class="col-content"><div class="tsd-page-title"><ul class="tsd-breadcrumb" aria-label="Breadcrumb"><li><a href="" aria-current="page">VfsNodeStats</a></li></ul><h1>Type Alias VfsNodeStats</h1></div><div class="tsd-signature"><span class="tsd-kind-type-alias">VfsNodeStats</span><span class="tsd-signature-symbol">:</span> <a href="../interfaces/VfsFileNode.html" class="tsd-signature-type tsd-kind-interface">VfsFileNode</a> <span class="tsd-signature-symbol">|</span> <a href="../interfaces/VfsDirectoryNode.html" class="tsd-signature-type tsd-kind-interface">VfsDirectoryNode</a></div><div class="tsd-comment tsd-typography"><p>Union of file and directory stat responses.</p>
|
|
2
|
+
</div><aside class="tsd-sources"><ul><li>Defined in <a href="https://github.com/itsrealfortune/typescript-virtual-container/blob/52880451442508494c6f496513aae8ae8d1600b8/src/types/vfs.ts#L35">src/types/vfs.ts:35</a></li></ul></aside></div><div class="col-sidebar"><div class="page-menu"><div class="tsd-navigation settings"><details class="tsd-accordion"><summary class="tsd-accordion-summary"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-chevronDown"></use></svg><h3>Settings</h3></summary><div class="tsd-accordion-details"><div class="tsd-filter-visibility"><span class="settings-label">Member Visibility</span><ul id="tsd-filter-options"><li class="tsd-filter-item"><label class="tsd-filter-input"><input type="checkbox" id="tsd-filter-protected" name="protected"/><svg width="32" height="32" viewBox="0 0 32 32" aria-hidden="true"><rect class="tsd-checkbox-background" width="30" height="30" x="1" y="1" rx="6" fill="none"></rect><path class="tsd-checkbox-checkmark" d="M8.35422 16.8214L13.2143 21.75L24.6458 10.25" stroke="none" stroke-width="3.5" stroke-linejoin="round" fill="none"></path></svg><span>Protected</span></label></li><li class="tsd-filter-item"><label class="tsd-filter-input"><input type="checkbox" id="tsd-filter-inherited" name="inherited" checked/><svg width="32" height="32" viewBox="0 0 32 32" aria-hidden="true"><rect class="tsd-checkbox-background" width="30" height="30" x="1" y="1" rx="6" fill="none"></rect><path class="tsd-checkbox-checkmark" d="M8.35422 16.8214L13.2143 21.75L24.6458 10.25" stroke="none" stroke-width="3.5" stroke-linejoin="round" fill="none"></path></svg><span>Inherited</span></label></li><li class="tsd-filter-item"><label class="tsd-filter-input"><input type="checkbox" id="tsd-filter-external" name="external"/><svg width="32" height="32" viewBox="0 0 32 32" aria-hidden="true"><rect class="tsd-checkbox-background" width="30" height="30" x="1" y="1" rx="6" fill="none"></rect><path class="tsd-checkbox-checkmark" d="M8.35422 16.8214L13.2143 21.75L24.6458 10.25" stroke="none" stroke-width="3.5" stroke-linejoin="round" fill="none"></path></svg><span>External</span></label></li></ul></div><div class="tsd-theme-toggle"><label class="settings-label" for="tsd-theme">Theme</label><select id="tsd-theme"><option value="os">OS</option><option value="light">Light</option><option value="dark">Dark</option></select></div></div></details></div></div><div class="site-menu"><nav class="tsd-navigation"><a href="../modules.html">typescript-virtual-container - v1.5.0</a><ul class="tsd-small-nested-navigation" id="tsd-nav-container"><li>Loading...</li></ul></nav></div></div></div><footer><p class="tsd-generator">Generated using <a href="https://typedoc.org/" target="_blank">TypeDoc</a></p></footer><div class="overlay"></div></body></html>
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
<!DOCTYPE html><html class="default" lang="en" data-base="../"><head><meta charset="utf-8"/><meta http-equiv="x-ua-compatible" content="IE=edge"/><title>VfsNodeType | typescript-virtual-container - v1.
|
|
2
|
-
</div><aside class="tsd-sources"><ul><li>Defined in <a href="https://github.com/itsrealfortune/typescript-virtual-container/blob/
|
|
1
|
+
<!DOCTYPE html><html class="default" lang="en" data-base="../"><head><meta charset="utf-8"/><meta http-equiv="x-ua-compatible" content="IE=edge"/><title>VfsNodeType | typescript-virtual-container - v1.5.0</title><meta name="description" content="Documentation for typescript-virtual-container"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="stylesheet" href="../assets/style.css"/><link rel="stylesheet" href="../assets/highlight.css"/><script defer src="../assets/main.js"></script><script async src="../assets/icons.js" id="tsd-icons-script"></script><script async src="../assets/search.js" id="tsd-search-script"></script><script async src="../assets/navigation.js" id="tsd-nav-script"></script><script async src="../assets/hierarchy.js" id="tsd-hierarchy-script"></script></head><body><script>document.documentElement.dataset.theme = localStorage.getItem("tsd-theme") || "os";document.body.style.display="none";setTimeout(() => window.app?app.showPage():document.body.style.removeProperty("display"),500)</script><header class="tsd-page-toolbar"><div class="tsd-toolbar-contents container"><a href="../index.html" class="title">typescript-virtual-container - v1.5.0</a><div id="tsd-toolbar-links"></div><button id="tsd-search-trigger" class="tsd-widget" aria-label="Search"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-search"></use></svg></button><dialog id="tsd-search" aria-label="Search"><input role="combobox" id="tsd-search-input" aria-controls="tsd-search-results" aria-autocomplete="list" aria-expanded="true" autocapitalize="off" autocomplete="off" placeholder="Search the docs" maxLength="100"/><ul role="listbox" id="tsd-search-results"></ul><div id="tsd-search-status" aria-live="polite" aria-atomic="true"><div>Preparing search index...</div></div></dialog><a href="#" class="tsd-widget menu" id="tsd-toolbar-menu-trigger" data-toggle="menu" aria-label="Menu"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-menu"></use></svg></a></div></header><div class="container container-main"><div class="col-content"><div class="tsd-page-title"><ul class="tsd-breadcrumb" aria-label="Breadcrumb"><li><a href="" aria-current="page">VfsNodeType</a></li></ul><h1>Type Alias VfsNodeType</h1></div><div class="tsd-signature"><span class="tsd-kind-type-alias">VfsNodeType</span><span class="tsd-signature-symbol">:</span> <span class="tsd-signature-type">"file"</span> <span class="tsd-signature-symbol">|</span> <span class="tsd-signature-type">"directory"</span></div><div class="tsd-comment tsd-typography"><p>Supported virtual node kinds.</p>
|
|
2
|
+
</div><aside class="tsd-sources"><ul><li>Defined in <a href="https://github.com/itsrealfortune/typescript-virtual-container/blob/52880451442508494c6f496513aae8ae8d1600b8/src/types/vfs.ts#L2">src/types/vfs.ts:2</a></li></ul></aside></div><div class="col-sidebar"><div class="page-menu"><div class="tsd-navigation settings"><details class="tsd-accordion"><summary class="tsd-accordion-summary"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-chevronDown"></use></svg><h3>Settings</h3></summary><div class="tsd-accordion-details"><div class="tsd-filter-visibility"><span class="settings-label">Member Visibility</span><ul id="tsd-filter-options"><li class="tsd-filter-item"><label class="tsd-filter-input"><input type="checkbox" id="tsd-filter-protected" name="protected"/><svg width="32" height="32" viewBox="0 0 32 32" aria-hidden="true"><rect class="tsd-checkbox-background" width="30" height="30" x="1" y="1" rx="6" fill="none"></rect><path class="tsd-checkbox-checkmark" d="M8.35422 16.8214L13.2143 21.75L24.6458 10.25" stroke="none" stroke-width="3.5" stroke-linejoin="round" fill="none"></path></svg><span>Protected</span></label></li><li class="tsd-filter-item"><label class="tsd-filter-input"><input type="checkbox" id="tsd-filter-inherited" name="inherited" checked/><svg width="32" height="32" viewBox="0 0 32 32" aria-hidden="true"><rect class="tsd-checkbox-background" width="30" height="30" x="1" y="1" rx="6" fill="none"></rect><path class="tsd-checkbox-checkmark" d="M8.35422 16.8214L13.2143 21.75L24.6458 10.25" stroke="none" stroke-width="3.5" stroke-linejoin="round" fill="none"></path></svg><span>Inherited</span></label></li><li class="tsd-filter-item"><label class="tsd-filter-input"><input type="checkbox" id="tsd-filter-external" name="external"/><svg width="32" height="32" viewBox="0 0 32 32" aria-hidden="true"><rect class="tsd-checkbox-background" width="30" height="30" x="1" y="1" rx="6" fill="none"></rect><path class="tsd-checkbox-checkmark" d="M8.35422 16.8214L13.2143 21.75L24.6458 10.25" stroke="none" stroke-width="3.5" stroke-linejoin="round" fill="none"></path></svg><span>External</span></label></li></ul></div><div class="tsd-theme-toggle"><label class="settings-label" for="tsd-theme">Theme</label><select id="tsd-theme"><option value="os">OS</option><option value="light">Light</option><option value="dark">Dark</option></select></div></div></details></div></div><div class="site-menu"><nav class="tsd-navigation"><a href="../modules.html">typescript-virtual-container - v1.5.0</a><ul class="tsd-small-nested-navigation" id="tsd-nav-container"><li>Loading...</li></ul></nav></div></div></div><footer><p class="tsd-generator">Generated using <a href="https://typedoc.org/" target="_blank">TypeDoc</a></p></footer><div class="overlay"></div></body></html>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
<!DOCTYPE html><html class="default" lang="en" data-base="../"><head><meta charset="utf-8"/><meta http-equiv="x-ua-compatible" content="IE=edge"/><title>VfsPersistenceMode | typescript-virtual-container - v1.
|
|
1
|
+
<!DOCTYPE html><html class="default" lang="en" data-base="../"><head><meta charset="utf-8"/><meta http-equiv="x-ua-compatible" content="IE=edge"/><title>VfsPersistenceMode | typescript-virtual-container - v1.5.0</title><meta name="description" content="Documentation for typescript-virtual-container"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="stylesheet" href="../assets/style.css"/><link rel="stylesheet" href="../assets/highlight.css"/><script defer src="../assets/main.js"></script><script async src="../assets/icons.js" id="tsd-icons-script"></script><script async src="../assets/search.js" id="tsd-search-script"></script><script async src="../assets/navigation.js" id="tsd-nav-script"></script><script async src="../assets/hierarchy.js" id="tsd-hierarchy-script"></script></head><body><script>document.documentElement.dataset.theme = localStorage.getItem("tsd-theme") || "os";document.body.style.display="none";setTimeout(() => window.app?app.showPage():document.body.style.removeProperty("display"),500)</script><header class="tsd-page-toolbar"><div class="tsd-toolbar-contents container"><a href="../index.html" class="title">typescript-virtual-container - v1.5.0</a><div id="tsd-toolbar-links"></div><button id="tsd-search-trigger" class="tsd-widget" aria-label="Search"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-search"></use></svg></button><dialog id="tsd-search" aria-label="Search"><input role="combobox" id="tsd-search-input" aria-controls="tsd-search-results" aria-autocomplete="list" aria-expanded="true" autocapitalize="off" autocomplete="off" placeholder="Search the docs" maxLength="100"/><ul role="listbox" id="tsd-search-results"></ul><div id="tsd-search-status" aria-live="polite" aria-atomic="true"><div>Preparing search index...</div></div></dialog><a href="#" class="tsd-widget menu" id="tsd-toolbar-menu-trigger" data-toggle="menu" aria-label="Menu"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-menu"></use></svg></a></div></header><div class="container container-main"><div class="col-content"><div class="tsd-page-title"><ul class="tsd-breadcrumb" aria-label="Breadcrumb"><li><a href="" aria-current="page">VfsPersistenceMode</a></li></ul><h1>Type Alias VfsPersistenceMode</h1></div><div class="tsd-signature"><span class="tsd-kind-type-alias">VfsPersistenceMode</span><span class="tsd-signature-symbol">:</span> <span class="tsd-signature-type">"memory"</span> <span class="tsd-signature-symbol">|</span> <span class="tsd-signature-type">"fs"</span></div><div class="tsd-comment tsd-typography"><p>"memory" — pure in-memory, no disk I/O (default).</p>
|
|
2
2
|
<p>"fs" — mirrors the VFS tree to a directory on the host filesystem.
|
|
3
3
|
<code>snapshotPath</code> must be set to the directory where the binary
|
|
4
4
|
snapshot file will be read/written (<code>vfs-snapshot.vfsb</code>).</p>
|
|
5
|
-
</div><aside class="tsd-sources"><ul><li>Defined in <a href="https://github.com/itsrealfortune/typescript-virtual-container/blob/
|
|
5
|
+
</div><aside class="tsd-sources"><ul><li>Defined in <a href="https://github.com/itsrealfortune/typescript-virtual-container/blob/52880451442508494c6f496513aae8ae8d1600b8/src/VirtualFileSystem/index.ts#L36">src/VirtualFileSystem/index.ts:36</a></li></ul></aside></div><div class="col-sidebar"><div class="page-menu"><div class="tsd-navigation settings"><details class="tsd-accordion"><summary class="tsd-accordion-summary"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-chevronDown"></use></svg><h3>Settings</h3></summary><div class="tsd-accordion-details"><div class="tsd-filter-visibility"><span class="settings-label">Member Visibility</span><ul id="tsd-filter-options"><li class="tsd-filter-item"><label class="tsd-filter-input"><input type="checkbox" id="tsd-filter-protected" name="protected"/><svg width="32" height="32" viewBox="0 0 32 32" aria-hidden="true"><rect class="tsd-checkbox-background" width="30" height="30" x="1" y="1" rx="6" fill="none"></rect><path class="tsd-checkbox-checkmark" d="M8.35422 16.8214L13.2143 21.75L24.6458 10.25" stroke="none" stroke-width="3.5" stroke-linejoin="round" fill="none"></path></svg><span>Protected</span></label></li><li class="tsd-filter-item"><label class="tsd-filter-input"><input type="checkbox" id="tsd-filter-inherited" name="inherited" checked/><svg width="32" height="32" viewBox="0 0 32 32" aria-hidden="true"><rect class="tsd-checkbox-background" width="30" height="30" x="1" y="1" rx="6" fill="none"></rect><path class="tsd-checkbox-checkmark" d="M8.35422 16.8214L13.2143 21.75L24.6458 10.25" stroke="none" stroke-width="3.5" stroke-linejoin="round" fill="none"></path></svg><span>Inherited</span></label></li><li class="tsd-filter-item"><label class="tsd-filter-input"><input type="checkbox" id="tsd-filter-external" name="external"/><svg width="32" height="32" viewBox="0 0 32 32" aria-hidden="true"><rect class="tsd-checkbox-background" width="30" height="30" x="1" y="1" rx="6" fill="none"></rect><path class="tsd-checkbox-checkmark" d="M8.35422 16.8214L13.2143 21.75L24.6458 10.25" stroke="none" stroke-width="3.5" stroke-linejoin="round" fill="none"></path></svg><span>External</span></label></li></ul></div><div class="tsd-theme-toggle"><label class="settings-label" for="tsd-theme">Theme</label><select id="tsd-theme"><option value="os">OS</option><option value="light">Light</option><option value="dark">Dark</option></select></div></div></details></div></div><div class="site-menu"><nav class="tsd-navigation"><a href="../modules.html">typescript-virtual-container - v1.5.0</a><ul class="tsd-small-nested-navigation" id="tsd-nav-container"><li>Loading...</li></ul></nav></div></div></div><footer><p class="tsd-generator">Generated using <a href="https://typedoc.org/" target="_blank">TypeDoc</a></p></footer><div class="overlay"></div></body></html>
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
<!DOCTYPE html><html class="default" lang="en" data-base="../"><head><meta charset="utf-8"/><meta http-equiv="x-ua-compatible" content="IE=edge"/><title>VfsSnapshotNode | typescript-virtual-container - v1.
|
|
2
|
-
</div><aside class="tsd-sources"><ul><li>Defined in <a href="https://github.com/itsrealfortune/typescript-virtual-container/blob/
|
|
1
|
+
<!DOCTYPE html><html class="default" lang="en" data-base="../"><head><meta charset="utf-8"/><meta http-equiv="x-ua-compatible" content="IE=edge"/><title>VfsSnapshotNode | typescript-virtual-container - v1.5.0</title><meta name="description" content="Documentation for typescript-virtual-container"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="stylesheet" href="../assets/style.css"/><link rel="stylesheet" href="../assets/highlight.css"/><script defer src="../assets/main.js"></script><script async src="../assets/icons.js" id="tsd-icons-script"></script><script async src="../assets/search.js" id="tsd-search-script"></script><script async src="../assets/navigation.js" id="tsd-nav-script"></script><script async src="../assets/hierarchy.js" id="tsd-hierarchy-script"></script></head><body><script>document.documentElement.dataset.theme = localStorage.getItem("tsd-theme") || "os";document.body.style.display="none";setTimeout(() => window.app?app.showPage():document.body.style.removeProperty("display"),500)</script><header class="tsd-page-toolbar"><div class="tsd-toolbar-contents container"><a href="../index.html" class="title">typescript-virtual-container - v1.5.0</a><div id="tsd-toolbar-links"></div><button id="tsd-search-trigger" class="tsd-widget" aria-label="Search"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-search"></use></svg></button><dialog id="tsd-search" aria-label="Search"><input role="combobox" id="tsd-search-input" aria-controls="tsd-search-results" aria-autocomplete="list" aria-expanded="true" autocapitalize="off" autocomplete="off" placeholder="Search the docs" maxLength="100"/><ul role="listbox" id="tsd-search-results"></ul><div id="tsd-search-status" aria-live="polite" aria-atomic="true"><div>Preparing search index...</div></div></dialog><a href="#" class="tsd-widget menu" id="tsd-toolbar-menu-trigger" data-toggle="menu" aria-label="Menu"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-menu"></use></svg></a></div></header><div class="container container-main"><div class="col-content"><div class="tsd-page-title"><ul class="tsd-breadcrumb" aria-label="Breadcrumb"><li><a href="" aria-current="page">VfsSnapshotNode</a></li></ul><h1>Type Alias VfsSnapshotNode</h1></div><div class="tsd-signature"><span class="tsd-kind-type-alias">VfsSnapshotNode</span><span class="tsd-signature-symbol">:</span> <a href="../interfaces/VfsSnapshotFileNode.html" class="tsd-signature-type tsd-kind-interface">VfsSnapshotFileNode</a> <span class="tsd-signature-symbol">|</span> <a href="../interfaces/VfsSnapshotDirectoryNode.html" class="tsd-signature-type tsd-kind-interface">VfsSnapshotDirectoryNode</a></div><div class="tsd-comment tsd-typography"><p>Union of serialized snapshot node variants.</p>
|
|
2
|
+
</div><aside class="tsd-sources"><ul><li>Defined in <a href="https://github.com/itsrealfortune/typescript-virtual-container/blob/52880451442508494c6f496513aae8ae8d1600b8/src/types/vfs.ts#L76">src/types/vfs.ts:76</a></li></ul></aside></div><div class="col-sidebar"><div class="page-menu"><div class="tsd-navigation settings"><details class="tsd-accordion"><summary class="tsd-accordion-summary"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-chevronDown"></use></svg><h3>Settings</h3></summary><div class="tsd-accordion-details"><div class="tsd-filter-visibility"><span class="settings-label">Member Visibility</span><ul id="tsd-filter-options"><li class="tsd-filter-item"><label class="tsd-filter-input"><input type="checkbox" id="tsd-filter-protected" name="protected"/><svg width="32" height="32" viewBox="0 0 32 32" aria-hidden="true"><rect class="tsd-checkbox-background" width="30" height="30" x="1" y="1" rx="6" fill="none"></rect><path class="tsd-checkbox-checkmark" d="M8.35422 16.8214L13.2143 21.75L24.6458 10.25" stroke="none" stroke-width="3.5" stroke-linejoin="round" fill="none"></path></svg><span>Protected</span></label></li><li class="tsd-filter-item"><label class="tsd-filter-input"><input type="checkbox" id="tsd-filter-inherited" name="inherited" checked/><svg width="32" height="32" viewBox="0 0 32 32" aria-hidden="true"><rect class="tsd-checkbox-background" width="30" height="30" x="1" y="1" rx="6" fill="none"></rect><path class="tsd-checkbox-checkmark" d="M8.35422 16.8214L13.2143 21.75L24.6458 10.25" stroke="none" stroke-width="3.5" stroke-linejoin="round" fill="none"></path></svg><span>Inherited</span></label></li><li class="tsd-filter-item"><label class="tsd-filter-input"><input type="checkbox" id="tsd-filter-external" name="external"/><svg width="32" height="32" viewBox="0 0 32 32" aria-hidden="true"><rect class="tsd-checkbox-background" width="30" height="30" x="1" y="1" rx="6" fill="none"></rect><path class="tsd-checkbox-checkmark" d="M8.35422 16.8214L13.2143 21.75L24.6458 10.25" stroke="none" stroke-width="3.5" stroke-linejoin="round" fill="none"></path></svg><span>External</span></label></li></ul></div><div class="tsd-theme-toggle"><label class="settings-label" for="tsd-theme">Theme</label><select id="tsd-theme"><option value="os">OS</option><option value="light">Light</option><option value="dark">Dark</option></select></div></div></details></div></div><div class="site-menu"><nav class="tsd-navigation"><a href="../modules.html">typescript-virtual-container - v1.5.0</a><ul class="tsd-small-nested-navigation" id="tsd-nav-container"><li>Loading...</li></ul></nav></div></div></div><footer><p class="tsd-generator">Generated using <a href="https://typedoc.org/" target="_blank">TypeDoc</a></p></footer><div class="overlay"></div></body></html>
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"type": "module",
|
|
7
|
-
"version": "1.
|
|
7
|
+
"version": "1.5.0",
|
|
8
8
|
"license": "MIT",
|
|
9
9
|
"repository": {
|
|
10
10
|
"type": "git",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"example-serve": "cd examples && bun server.js",
|
|
38
38
|
"web-full-build": "bunx esbuild src/web-api.ts --bundle --platform=browser --format=esm --target=es2020 --outfile=builds/web-full-api.min.js --tree-shaking=true --minify --alias:node:events=./polyfills/node_events/index.js --alias:node:path=./polyfills/node_path/index.js --alias:node:os=./polyfills/node_os/index.js --alias:node:fs=./polyfills/node_fs/index.js --alias:node:fs/promises=./polyfills/node_fs/promises.js --alias:node:crypto=./polyfills/node_crypto/index.js --alias:node:child_process=./polyfills/node_child_process/index.js --alias:node:zlib=./polyfills/node_zlib/index.js --alias:node:vm=./polyfills/node_vm/index.js",
|
|
39
39
|
"publish-package": "bash ./scripts/publish-package.sh",
|
|
40
|
-
"self-standalone-build": "bunx esbuild src/self-standalone.ts --bundle --platform=node --format=esm --target=node18 --outfile=builds/self-standalone.
|
|
40
|
+
"self-standalone-build": "bunx esbuild src/self-standalone.ts --bundle --platform=node --format=esm --target=node18 --outfile=builds/self-standalone.mjs --tree-shaking=true --minify --banner:js='#!/usr/bin/env node'",
|
|
41
41
|
"standalone-build": "bunx esbuild src/standalone.ts --bundle --platform=node --target=node18 --outfile=builds/standalone.cjs --tree-shaking=true --minify --banner:js='#!/usr/bin/env node'",
|
|
42
42
|
"build-all": "bun run generate-manuals && bun run self-standalone-build && bun run standalone-build && bun run standalone-build:wo-sftp && bun run web-build && bun run web-full-build && bun run example-build",
|
|
43
43
|
"publish-doc": "bunx typedoc && bunx gh-pages -d docs",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"@biomejs/biome": "^2.4.15",
|
|
48
|
-
"@types/bun": "^1.3.
|
|
48
|
+
"@types/bun": "^1.3.14",
|
|
49
49
|
"@types/node": "^25.7.0",
|
|
50
50
|
"@types/ssh2": "^1.15.5",
|
|
51
51
|
"gh-pages": "^6.3.0",
|
package/src/SSHMimic/exec.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { makeDefaultEnv, runCommand } from "../commands";
|
|
1
|
+
import { makeDefaultEnv, runCommand, userHome } from "../commands";
|
|
2
2
|
import type { ExecStream } from "../types/streams";
|
|
3
3
|
import type { VirtualShell } from "../VirtualShell";
|
|
4
4
|
|
|
@@ -22,7 +22,7 @@ export function runExec(
|
|
|
22
22
|
authUser,
|
|
23
23
|
hostname,
|
|
24
24
|
"exec",
|
|
25
|
-
|
|
25
|
+
userHome(authUser),
|
|
26
26
|
shell,
|
|
27
27
|
undefined,
|
|
28
28
|
makeDefaultEnv(authUser, hostname),
|
package/src/SSHMimic/index.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { EventEmitter } from "node:events";
|
|
2
2
|
import { Server as SshServer } from "ssh2";
|
|
3
3
|
import { VirtualShell } from "../VirtualShell";
|
|
4
|
+
import { userHome } from "../commands";
|
|
4
5
|
import { createPerfLogger, type PerfLogger } from "../utils/perfLogger";
|
|
5
6
|
import { runExec } from "./exec";
|
|
6
7
|
import { loadOrCreateHostKey } from "./hostKey";
|
|
@@ -104,7 +105,7 @@ class SshMimic extends EventEmitter {
|
|
|
104
105
|
// ── Home directory bootstrap ─────────────────────────────────────────────
|
|
105
106
|
|
|
106
107
|
private ensureHomeDir(authUser: string): void {
|
|
107
|
-
const homePath =
|
|
108
|
+
const homePath = userHome(authUser);
|
|
108
109
|
if (!this.shell.vfs.exists(homePath)) {
|
|
109
110
|
this.shell.vfs.mkdir(homePath, 0o755);
|
|
110
111
|
this.shell.vfs.writeFile(
|
package/src/SSHMimic/sftp.ts
CHANGED
|
@@ -7,6 +7,7 @@ import type { VfsNodeStats } from "../types/vfs";
|
|
|
7
7
|
import type { PerfLogger } from "../utils/perfLogger";
|
|
8
8
|
import { createPerfLogger } from "../utils/perfLogger";
|
|
9
9
|
import type VirtualFileSystem from "../VirtualFileSystem";
|
|
10
|
+
import { userHome } from "../commands";
|
|
10
11
|
import { VirtualShell } from "../VirtualShell";
|
|
11
12
|
import type { VirtualUserManager } from "../VirtualUserManager";
|
|
12
13
|
import { loadOrCreateHostKey } from "./hostKey";
|
|
@@ -246,7 +247,7 @@ export class SftpMimic extends EventEmitter {
|
|
|
246
247
|
this.getVfs().mkdir(homeRoot, 0o755);
|
|
247
248
|
}
|
|
248
249
|
|
|
249
|
-
const homePath =
|
|
250
|
+
const homePath = userHome(authUser);
|
|
250
251
|
if (!this.getVfs().exists(homePath)) {
|
|
251
252
|
this.getVfs().mkdir(homePath, 0o755);
|
|
252
253
|
this.getVfs().writeFile(
|
|
@@ -384,7 +385,7 @@ export class SftpMimic extends EventEmitter {
|
|
|
384
385
|
* This is standard SFTP behavior where the "working directory" is always the home.
|
|
385
386
|
*/
|
|
386
387
|
private resolveRequestPath(requestPath: string, authUser: string): string {
|
|
387
|
-
const homePath =
|
|
388
|
+
const homePath = userHome(authUser);
|
|
388
389
|
|
|
389
390
|
// Empty path or "." → resolve to home directory
|
|
390
391
|
if (!requestPath || requestPath === ".") {
|
|
@@ -408,7 +409,7 @@ export class SftpMimic extends EventEmitter {
|
|
|
408
409
|
* @returns true if path is within home, false if traversal attempt detected
|
|
409
410
|
*/
|
|
410
411
|
private isPathWithinHome(targetPath: string, authUser: string): boolean {
|
|
411
|
-
const homePath =
|
|
412
|
+
const homePath = userHome(authUser);
|
|
412
413
|
const normalized = path.posix.normalize(targetPath);
|
|
413
414
|
|
|
414
415
|
// Allow access to home directory itself
|
|
@@ -134,10 +134,11 @@ class VirtualFileSystem extends EventEmitter {
|
|
|
134
134
|
this.journalFile = path.resolve(options.snapshotPath, "vfs-journal.bin");
|
|
135
135
|
this.evictionThreshold = options.evictionThresholdBytes ?? 64 * 1024; // 64 KB default
|
|
136
136
|
this.flushAfterNWrites = options.flushAfterNWrites ?? 500;
|
|
137
|
-
const intervalMs = options.flushIntervalMs ??
|
|
137
|
+
const intervalMs = options.flushIntervalMs ?? 1_000;
|
|
138
138
|
if (intervalMs > 0) {
|
|
139
139
|
this._flushTimer = setInterval(() => {
|
|
140
|
-
|
|
140
|
+
const dirty = this._dirty;
|
|
141
|
+
if (dirty) void this._autoFlush();
|
|
141
142
|
}, intervalMs);
|
|
142
143
|
// Don't block process exit on this timer
|
|
143
144
|
if (typeof this._flushTimer === "object" && this._flushTimer !== null && "unref" in this._flushTimer) {
|
|
@@ -303,7 +304,8 @@ class VirtualFileSystem extends EventEmitter {
|
|
|
303
304
|
|
|
304
305
|
const dir = path.dirname(this.snapshotFile);
|
|
305
306
|
fsSync.mkdirSync(dir, { recursive: true });
|
|
306
|
-
const
|
|
307
|
+
const root = this.root;
|
|
308
|
+
const binary = encodeVfs(root);
|
|
307
309
|
fsSync.writeFileSync(this.snapshotFile, binary);
|
|
308
310
|
// Checkpoint complete — truncate the journal (entries are now in the snapshot)
|
|
309
311
|
if (this.journalFile) truncateJournal(this.journalFile);
|
|
@@ -382,6 +384,47 @@ class VirtualFileSystem extends EventEmitter {
|
|
|
382
384
|
try { this.root = root; } finally { this._replayMode = prev; }
|
|
383
385
|
}
|
|
384
386
|
|
|
387
|
+
/**
|
|
388
|
+
* Merge a static rootfs tree into the existing live tree.
|
|
389
|
+
* Used by `bootstrapLinuxRootfs` when a persisted snapshot already exists,
|
|
390
|
+
* to layer in missing system files without overwriting user data.
|
|
391
|
+
*
|
|
392
|
+
* Rules:
|
|
393
|
+
* - Directories: recurse — never overwrite a live dir with an empty one.
|
|
394
|
+
* - Files/stubs: only written if the path does NOT yet exist in the live tree.
|
|
395
|
+
* This ensures user-created files always win over static defaults.
|
|
396
|
+
*
|
|
397
|
+
* @internal
|
|
398
|
+
*/
|
|
399
|
+
public mergeRootTree(incoming: InternalDirectoryNode): void {
|
|
400
|
+
const prev = this._replayMode;
|
|
401
|
+
this._replayMode = true;
|
|
402
|
+
try { this._mergeDir(this.root, incoming); } finally { this._replayMode = prev; }
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
private _mergeDir(live: InternalDirectoryNode, incoming: InternalDirectoryNode): void {
|
|
406
|
+
for (const [name, node] of Object.entries(incoming.children)) {
|
|
407
|
+
const existing = live.children[name];
|
|
408
|
+
if (node.type === "directory") {
|
|
409
|
+
if (!existing) {
|
|
410
|
+
// Dir doesn't exist yet — add it
|
|
411
|
+
live.children[name] = node;
|
|
412
|
+
live._childCount++;
|
|
413
|
+
} else if (existing.type === "directory") {
|
|
414
|
+
// Both dirs — recurse
|
|
415
|
+
this._mergeDir(existing, node);
|
|
416
|
+
}
|
|
417
|
+
// existing is a file where dir expected — leave user file alone
|
|
418
|
+
} else {
|
|
419
|
+
// File or stub — only add if not already present
|
|
420
|
+
if (!existing) {
|
|
421
|
+
live.children[name] = node;
|
|
422
|
+
live._childCount++;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
385
428
|
/** Serialise current tree to VFSB binary. Used for the static rootfs cache. */
|
|
386
429
|
public encodeBinary(): Buffer {
|
|
387
430
|
return encodeVfs(this.root);
|
|
@@ -142,11 +142,16 @@ export function decodeJournal(buf: Buffer): JournalEntry[] {
|
|
|
142
142
|
/** Append a single entry to the journal file (O_APPEND, atomic write). */
|
|
143
143
|
export function appendJournalEntry(journalPath: string, entry: JournalEntry): void {
|
|
144
144
|
const buf = encodeEntry(entry);
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
fsSync.
|
|
148
|
-
|
|
149
|
-
|
|
145
|
+
console.log(`[Journal] Appending entry: op=${entry.op} path=${entry.path} ${entry.content ? `content_len=${entry.content.length}` : ""}${entry.mode ? ` mode=${entry.mode.toString(8)}` : ""}${entry.dest ? ` dest=${entry.dest}` : ""}`);
|
|
146
|
+
if (fsSync.existsSync(journalPath)) {
|
|
147
|
+
const fd = fsSync.openSync(journalPath, fsSync.constants.O_WRONLY | fsSync.constants.O_CREAT | fsSync.constants.O_APPEND);
|
|
148
|
+
try {
|
|
149
|
+
fsSync.writeSync(fd, buf);
|
|
150
|
+
} finally {
|
|
151
|
+
fsSync.closeSync(fd);
|
|
152
|
+
}
|
|
153
|
+
} else {
|
|
154
|
+
fsSync.writeFileSync(journalPath, buf);
|
|
150
155
|
}
|
|
151
156
|
}
|
|
152
157
|
|
|
@@ -2,7 +2,7 @@ import type { ChildProcessWithoutNullStreams } from "node:child_process";
|
|
|
2
2
|
import { readFile, unlink, writeFile } from "node:fs/promises";
|
|
3
3
|
import * as path from "node:path";
|
|
4
4
|
import type { ShellProperties, VirtualShell } from ".";
|
|
5
|
-
import { getCommandNames, makeDefaultEnv, runCommand } from "../commands";
|
|
5
|
+
import { getCommandNames, makeDefaultEnv, runCommand, userHome } from "../commands";
|
|
6
6
|
import {
|
|
7
7
|
spawnHtopProcess,
|
|
8
8
|
spawnNanoEditorProcess,
|
|
@@ -55,12 +55,12 @@ export function startShell(
|
|
|
55
55
|
let history = loadHistory(shell.vfs, authUser);
|
|
56
56
|
let historyIndex: number | null = null;
|
|
57
57
|
let historyDraft = "";
|
|
58
|
-
let cwd =
|
|
58
|
+
let cwd = userHome(authUser);
|
|
59
59
|
const shellEnv: ShellEnv = makeDefaultEnv(authUser, hostname);
|
|
60
60
|
let nanoSession: NanoSession | null = null;
|
|
61
61
|
let pendingSudo: PendingSudo | null = null;
|
|
62
62
|
const buildCurrentPrompt = (): string => {
|
|
63
|
-
const homePath =
|
|
63
|
+
const homePath = userHome(authUser);
|
|
64
64
|
const cwdLabel = cwd === homePath ? "~" : path.posix.basename(cwd) || "/";
|
|
65
65
|
return buildPrompt(authUser, hostname, cwdLabel);
|
|
66
66
|
};
|
|
@@ -71,7 +71,7 @@ export function startShell(
|
|
|
71
71
|
|
|
72
72
|
// Load .bashrc if it exists
|
|
73
73
|
void (async () => {
|
|
74
|
-
const bashrcPath =
|
|
74
|
+
const bashrcPath = `${userHome(authUser)}/.bashrc`;
|
|
75
75
|
if (shell.vfs.exists(bashrcPath)) {
|
|
76
76
|
try {
|
|
77
77
|
const bashrc = shell.vfs.readFile(bashrcPath);
|
|
@@ -146,7 +146,7 @@ export function startShell(
|
|
|
146
146
|
if (!challenge.commandLine) {
|
|
147
147
|
authUser = challenge.targetUser;
|
|
148
148
|
if (challenge.loginShell) {
|
|
149
|
-
cwd =
|
|
149
|
+
cwd = userHome(authUser);
|
|
150
150
|
}
|
|
151
151
|
shell.users.updateSession(sessionId, authUser, remoteAddress);
|
|
152
152
|
stream.write("\r\n");
|
|
@@ -154,7 +154,7 @@ export function startShell(
|
|
|
154
154
|
return;
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
-
const runCwd = challenge.loginShell ?
|
|
157
|
+
const runCwd = challenge.loginShell ? userHome(challenge.targetUser) : cwd;
|
|
158
158
|
const result = await Promise.resolve(
|
|
159
159
|
runCommand(
|
|
160
160
|
challenge.commandLine,
|
|
@@ -196,7 +196,7 @@ export function startShell(
|
|
|
196
196
|
|
|
197
197
|
if (result.switchUser) {
|
|
198
198
|
authUser = result.switchUser;
|
|
199
|
-
cwd = result.nextCwd ??
|
|
199
|
+
cwd = result.nextCwd ?? userHome(authUser);
|
|
200
200
|
shell.users.updateSession(sessionId, authUser, remoteAddress);
|
|
201
201
|
} else if (result.nextCwd) {
|
|
202
202
|
cwd = result.nextCwd;
|
|
@@ -388,11 +388,11 @@ export function startShell(
|
|
|
388
388
|
}
|
|
389
389
|
|
|
390
390
|
const data = history.length > 0 ? `${history.join("\n")}\n` : "";
|
|
391
|
-
shell.vfs.writeFile(
|
|
391
|
+
shell.vfs.writeFile(`${userHome(authUser)}/.bash_history`, data);
|
|
392
392
|
}
|
|
393
393
|
|
|
394
394
|
function readLastLogin(): { at: string; from: string } | null {
|
|
395
|
-
const lastlogPath =
|
|
395
|
+
const lastlogPath = `${userHome(authUser)}/.lastlog.json`;
|
|
396
396
|
if (!shell.vfs.exists(lastlogPath)) {
|
|
397
397
|
return null;
|
|
398
398
|
}
|
|
@@ -408,7 +408,7 @@ export function startShell(
|
|
|
408
408
|
}
|
|
409
409
|
|
|
410
410
|
function writeLastLogin(nowIso: string): void {
|
|
411
|
-
const lastlogPath =
|
|
411
|
+
const lastlogPath = `${userHome(authUser)}/.lastlog`;
|
|
412
412
|
shell.vfs.writeFile(
|
|
413
413
|
lastlogPath,
|
|
414
414
|
JSON.stringify({ at: nowIso, from: remoteAddress }),
|
|
@@ -651,7 +651,7 @@ export function startShell(
|
|
|
651
651
|
|
|
652
652
|
if (result.switchUser) {
|
|
653
653
|
authUser = result.switchUser;
|
|
654
|
-
cwd = result.nextCwd ??
|
|
654
|
+
cwd = result.nextCwd ?? userHome(authUser);
|
|
655
655
|
shell.users.updateSession(sessionId, authUser, remoteAddress);
|
|
656
656
|
lineBuffer = "";
|
|
657
657
|
cursorPos = 0;
|
|
@@ -686,7 +686,7 @@ export function startShell(
|
|
|
686
686
|
}
|
|
687
687
|
|
|
688
688
|
function loadHistory(vfs: VirtualFileSystem, authUser: string): string[] {
|
|
689
|
-
const historyPath =
|
|
689
|
+
const historyPath = `${userHome(authUser)}/.bash_history`;
|
|
690
690
|
if (!vfs.exists(historyPath)) {
|
|
691
691
|
vfs.writeFile(historyPath, "");
|
|
692
692
|
return [];
|
|
@@ -103,14 +103,14 @@ export class VirtualUserManager extends EventEmitter {
|
|
|
103
103
|
// changed = true;
|
|
104
104
|
// }
|
|
105
105
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
106
|
+
const homePath = "/root";
|
|
107
|
+
if (!this.vfs.exists(homePath)) {
|
|
108
|
+
this.vfs.mkdir(homePath, 0o755);
|
|
109
|
+
this.vfs.writeFile(
|
|
110
|
+
`${homePath}/README.txt`,
|
|
111
|
+
`Welcome to the virtual environment, root`,
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
114
|
|
|
115
115
|
if (changed) {
|
|
116
116
|
await this.persist();
|
|
@@ -173,7 +173,7 @@ export class VirtualUserManager extends EventEmitter {
|
|
|
173
173
|
*/
|
|
174
174
|
public getUsageBytes(username: string): number {
|
|
175
175
|
perf.mark("getUsageBytes");
|
|
176
|
-
const homePath = `/home/${username}`;
|
|
176
|
+
const homePath = username === "root" ? "/root" : `/home/${username}`;
|
|
177
177
|
if (!this.vfs.exists(homePath)) {
|
|
178
178
|
return 0;
|
|
179
179
|
}
|
|
@@ -202,7 +202,7 @@ export class VirtualUserManager extends EventEmitter {
|
|
|
202
202
|
}
|
|
203
203
|
|
|
204
204
|
const normalizedPath = normalizeVfsPath(targetPath);
|
|
205
|
-
const homePath = normalizeVfsPath(`/home/${username}`);
|
|
205
|
+
const homePath = normalizeVfsPath(username === "root" ? "/root" : `/home/${username}`);
|
|
206
206
|
const inUserHome =
|
|
207
207
|
normalizedPath === homePath || normalizedPath.startsWith(`${homePath}/`);
|
|
208
208
|
if (!inUserHome) {
|
|
@@ -279,7 +279,7 @@ export class VirtualUserManager extends EventEmitter {
|
|
|
279
279
|
if (this.autoSudoForNewUsers) {
|
|
280
280
|
this.sudoers.add(username);
|
|
281
281
|
}
|
|
282
|
-
const homePath = `/home/${username}`;
|
|
282
|
+
const homePath = username === "root" ? "/root" : `/home/${username}`;
|
|
283
283
|
if (!this.vfs.exists(homePath)) {
|
|
284
284
|
this.vfs.mkdir(homePath, 0o755);
|
|
285
285
|
this.vfs.writeFile(
|
package/src/commands/apt.ts
CHANGED
|
@@ -57,8 +57,8 @@ export const aptCommand: ShellModule = {
|
|
|
57
57
|
case "update": {
|
|
58
58
|
return {
|
|
59
59
|
stdout: [
|
|
60
|
-
"Hit:1 fortune://packages.fortune.local
|
|
61
|
-
"Hit:2 fortune://security.fortune.local
|
|
60
|
+
"Hit:1 fortune://packages.fortune.local nyx InRelease",
|
|
61
|
+
"Hit:2 fortune://security.fortune.local nyx-security InRelease",
|
|
62
62
|
"Reading package lists... Done",
|
|
63
63
|
"Building dependency tree... Done",
|
|
64
64
|
"Reading state information... Done",
|
|
@@ -218,7 +218,7 @@ export const aptCacheCommand: ShellModule = {
|
|
|
218
218
|
` Candidate: ${def.version}`,
|
|
219
219
|
` Version table:`,
|
|
220
220
|
` ${def.version} 500`,
|
|
221
|
-
` 500 fortune://packages.fortune.local
|
|
221
|
+
` 500 fortune://packages.fortune.local nyx/main amd64 Packages`,
|
|
222
222
|
].join("\n"),
|
|
223
223
|
exitCode: 0,
|
|
224
224
|
};
|
package/src/commands/cd.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ShellModule } from "../types/commands";
|
|
2
2
|
import { assertPathAccess, resolvePath } from "./helpers";
|
|
3
|
+
import { userHome } from "./runtime";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Change current working directory.
|
|
@@ -12,7 +13,7 @@ export const cdCommand: ShellModule = {
|
|
|
12
13
|
category: "navigation",
|
|
13
14
|
params: ["[path]"],
|
|
14
15
|
run: ({ authUser, shell, cwd, args }) => {
|
|
15
|
-
const target = resolvePath(cwd, args[0] ??
|
|
16
|
+
const target = resolvePath(cwd, args[0] ?? "~", userHome(authUser));
|
|
16
17
|
assertPathAccess(authUser, target, "cd");
|
|
17
18
|
const stats = shell.vfs.stat(target);
|
|
18
19
|
if (stats.type !== "directory") {
|
package/src/commands/helpers.ts
CHANGED
|
@@ -28,12 +28,13 @@ export function normalizeTerminalOutput(text: string): string {
|
|
|
28
28
|
.trimEnd();
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
export function resolvePath(cwd: string, inputPath: string): string {
|
|
31
|
+
export function resolvePath(cwd: string, inputPath: string, homeDir?: string): string {
|
|
32
32
|
if (!inputPath || inputPath.trim() === "") {
|
|
33
33
|
return cwd;
|
|
34
34
|
}
|
|
35
35
|
if (inputPath.startsWith("~")) {
|
|
36
|
-
|
|
36
|
+
const home = homeDir ?? "/root";
|
|
37
|
+
return path.posix.normalize(`${home}${inputPath.slice(1)}`);
|
|
37
38
|
}
|
|
38
39
|
return inputPath.startsWith("/")
|
|
39
40
|
? path.posix.normalize(inputPath)
|
package/src/commands/index.ts
CHANGED
|
@@ -13,7 +13,7 @@ export const lsbReleaseCommand: ShellModule = {
|
|
|
13
13
|
params: ["[-a] [-i] [-d] [-r] [-c]"],
|
|
14
14
|
run: ({ args, shell }) => {
|
|
15
15
|
let osName = shell.properties?.os ?? "Fortune GNU/Linux x64";
|
|
16
|
-
let codename = "
|
|
16
|
+
let codename = "nyx";
|
|
17
17
|
let version = "1.0";
|
|
18
18
|
|
|
19
19
|
try {
|
package/src/commands/runtime.ts
CHANGED
|
@@ -11,11 +11,16 @@ import { expandAsync, expandBraces } from "../utils/expand";
|
|
|
11
11
|
import { tokenizeCommand } from "../utils/tokenize";
|
|
12
12
|
import { resolveModule } from "./registry";
|
|
13
13
|
|
|
14
|
+
/** Returns the home directory path for a given user. Root lives at /root. */
|
|
15
|
+
export function userHome(authUser: string): string {
|
|
16
|
+
return authUser === "root" ? "/root" : `/home/${authUser}`;
|
|
17
|
+
}
|
|
18
|
+
|
|
14
19
|
export function makeDefaultEnv(authUser: string, hostname: string): ShellEnv {
|
|
15
20
|
return {
|
|
16
21
|
vars: {
|
|
17
22
|
PATH: "/usr/local/bin:/usr/bin:/bin",
|
|
18
|
-
HOME:
|
|
23
|
+
HOME: userHome(authUser),
|
|
19
24
|
USER: authUser,
|
|
20
25
|
LOGNAME: authUser,
|
|
21
26
|
SHELL: "/bin/sh",
|