windows-development-sandbox 1.7.0 → 1.9.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/config.example.json +10 -0
- package/dist/app/context.d.ts +8 -0
- package/dist/app/context.js +12 -0
- package/dist/app/features/defs.d.ts +322 -0
- package/dist/app/features/defs.js +28 -0
- package/dist/app/features/sandbox/index.d.ts +2 -0
- package/dist/app/features/sandbox/index.js +3 -0
- package/dist/app/features/sandbox/sandbox.def.d.ts +193 -0
- package/dist/app/features/sandbox/sandbox.def.js +245 -0
- package/dist/app/features/sandbox/sandbox.run.d.ts +72 -0
- package/dist/app/features/sandbox/sandbox.run.js +98 -0
- package/dist/app/features/sessions/index.d.ts +2 -0
- package/dist/app/features/sessions/index.js +3 -0
- package/dist/app/features/sessions/sessions.def.d.ts +141 -0
- package/dist/app/features/sessions/sessions.def.js +176 -0
- package/dist/app/features/sessions/sessions.run.d.ts +39 -0
- package/dist/app/features/sessions/sessions.run.js +58 -0
- package/dist/{cli/menu/settings/categories.d.ts → app/features/settings/categories.def.d.ts} +2 -0
- package/dist/{cli/menu/settings/categories.js → app/features/settings/categories.def.js} +24 -5
- package/dist/app/features/settings/index.d.ts +5 -0
- package/dist/app/features/settings/index.js +6 -0
- package/dist/{cli/menu/settings/config-file.d.ts → app/features/settings/settings-file.d.ts} +1 -1
- package/dist/{cli/menu/settings/config-file.js → app/features/settings/settings-file.js} +1 -1
- package/dist/app/features/settings/settings-rows.d.ts +35 -0
- package/dist/app/features/settings/settings-rows.js +94 -0
- package/dist/app/features/settings/settings.def.d.ts +157 -0
- package/dist/app/features/settings/settings.def.js +179 -0
- package/dist/app/features/settings/settings.run.d.ts +67 -0
- package/dist/app/features/settings/settings.run.js +84 -0
- package/dist/app/kernel/define.d.ts +143 -0
- package/dist/app/kernel/define.js +42 -0
- package/dist/app/kernel/errors.d.ts +34 -0
- package/dist/app/kernel/errors.js +45 -0
- package/dist/app/kernel/fields.d.ts +70 -0
- package/dist/app/kernel/fields.js +243 -0
- package/dist/app/kernel/index.d.ts +8 -0
- package/dist/app/kernel/index.js +10 -0
- package/dist/app/kernel/io.d.ts +193 -0
- package/dist/app/kernel/io.js +126 -0
- package/dist/app/kernel/rpc.d.ts +156 -0
- package/dist/app/kernel/rpc.js +47 -0
- package/dist/app/kernel/text.d.ts +6 -0
- package/dist/app/kernel/text.js +57 -0
- package/dist/app/kernel/view.d.ts +146 -0
- package/dist/app/kernel/view.js +28 -0
- package/dist/app/registry.d.ts +10 -0
- package/dist/app/registry.js +50 -0
- package/dist/cli/args.d.ts +5 -0
- package/dist/cli/args.js +6 -0
- package/dist/cli/commands/engine.d.ts +2 -0
- package/dist/cli/commands/engine.js +60 -0
- package/dist/cli/commands/index.d.ts +1 -1
- package/dist/cli/commands/index.js +1 -1
- package/dist/cli/commands/launch/launch.js +47 -7
- package/dist/cli/commands/launch/prepare.js +3 -3
- package/dist/cli/commands/launch/running.js +2 -1
- package/dist/cli/commands/launch/summary.d.ts +2 -0
- package/dist/cli/commands/maintenance/config-share.d.ts +12 -0
- package/dist/cli/commands/maintenance/config-share.js +80 -0
- package/dist/cli/commands/maintenance/config.js +8 -82
- package/dist/cli/commands/maintenance/logs.js +14 -0
- package/dist/cli/commands/registry.js +3 -7
- package/dist/cli/commands/repair/checks/contained.d.ts +2 -0
- package/dist/cli/commands/repair/checks/contained.js +139 -0
- package/dist/cli/commands/repair/checks/hcs.d.ts +7 -0
- package/dist/cli/commands/repair/checks/hcs.js +28 -0
- package/dist/cli/commands/repair/checks/index.js +4 -0
- package/dist/cli/commands/repair/checks/pc.js +32 -1
- package/dist/cli/commands/repair/checks/sandbox.js +16 -1
- package/dist/cli/commands/repair/checks/vpn.js +34 -9
- package/dist/cli/commands/repair/env.js +2 -1
- package/dist/cli/commands/sessions/index.d.ts +0 -4
- package/dist/cli/commands/sessions/index.js +3 -7
- package/dist/cli/commands/shared-use.d.ts +5 -2
- package/dist/cli/commands/shared-use.js +11 -8
- package/dist/cli/commands/storage/contained.d.ts +3 -0
- package/dist/cli/commands/storage/contained.js +179 -0
- package/dist/cli/commands/storage/offer.js +7 -1
- package/dist/cli/commands/storage/storage.js +10 -2
- package/dist/cli/commands/user-config.js +1 -1
- package/dist/cli/commands/vpn.js +19 -3
- package/dist/cli/commands/web.js +2 -0
- package/dist/cli/index.js +2 -1
- package/dist/cli/manifest.d.ts +72 -104
- package/dist/cli/manifest.js +64 -39
- package/dist/cli/menu/add-agent.d.ts +1 -1
- package/dist/cli/menu/add-agent.js +1 -1
- package/dist/cli/menu/apps.d.ts +7 -1
- package/dist/cli/menu/apps.js +36 -0
- package/dist/cli/menu/command-fields.d.ts +1 -1
- package/dist/cli/menu/command-form.js +2 -2
- package/dist/cli/menu/io-menu.d.ts +29 -0
- package/dist/cli/menu/io-menu.js +126 -0
- package/dist/cli/menu/live.d.ts +27 -0
- package/dist/cli/menu/live.js +69 -0
- package/dist/cli/menu/memory.d.ts +19 -0
- package/dist/cli/menu/memory.js +53 -0
- package/dist/cli/menu/menu.d.ts +10 -1
- package/dist/cli/menu/menu.js +27 -9
- package/dist/cli/menu/pages/agents-page.d.ts +3 -1
- package/dist/cli/menu/pages/agents-page.js +12 -5
- package/dist/cli/menu/pages/apps-page.d.ts +3 -1
- package/dist/cli/menu/pages/apps-page.js +19 -2
- package/dist/cli/menu/pages/list-page.d.ts +27 -0
- package/dist/cli/menu/pages/list-page.js +109 -7
- package/dist/cli/menu/pages/maintenance-page.d.ts +0 -5
- package/dist/cli/menu/pages/maintenance-page.js +17 -19
- package/dist/cli/menu/pages/sandbox-page.d.ts +4 -6
- package/dist/cli/menu/pages/sandbox-page.js +26 -118
- package/dist/cli/menu/pages/scopes.d.ts +1 -1
- package/dist/cli/menu/pages/sessions-page.d.ts +5 -11
- package/dist/cli/menu/pages/sessions-page.js +19 -88
- package/dist/cli/menu/pages/settings-page.d.ts +2 -10
- package/dist/cli/menu/pages/settings-page.js +64 -94
- package/dist/cli/menu/render-view.d.ts +28 -0
- package/dist/cli/menu/render-view.js +112 -0
- package/dist/cli/menu/settings/{path-kind.d.ts → ask-path.d.ts} +1 -7
- package/dist/cli/menu/settings/ask-path.js +14 -0
- package/dist/cli/menu/settings/collections/collection-editor.d.ts +1 -1
- package/dist/cli/menu/settings/collections/string-list-editor.d.ts +1 -2
- package/dist/cli/menu/settings/collections/string-list-editor.js +1 -1
- package/dist/cli/menu/settings/draft.d.ts +1 -1
- package/dist/cli/menu/settings/format.d.ts +1 -3
- package/dist/cli/menu/settings/format.js +6 -36
- package/dist/cli/menu/settings/index.d.ts +1 -3
- package/dist/cli/menu/settings/index.js +3 -3
- package/dist/cli/menu/settings/object-editor.d.ts +1 -1
- package/dist/cli/menu/settings/share.d.ts +36 -0
- package/dist/cli/menu/settings/share.js +131 -0
- package/dist/cli/menu/settings/value-editor.d.ts +5 -1
- package/dist/cli/menu/settings/value-editor.js +57 -54
- package/dist/cli/menu/several.js +1 -1
- package/dist/cli/menu/share-settings.d.ts +21 -0
- package/dist/cli/menu/share-settings.js +112 -0
- package/dist/cli/menu/shell.d.ts +21 -5
- package/dist/cli/menu/shell.js +31 -6
- package/dist/cli/menu/views.d.ts +7 -0
- package/dist/cli/menu/views.js +11 -0
- package/dist/cli/menu/vpn-login.js +8 -0
- package/dist/cli/operations/handler.d.ts +6 -0
- package/dist/cli/operations/handler.js +61 -0
- package/dist/cli/operations/index.d.ts +4 -0
- package/dist/cli/operations/index.js +5 -0
- package/dist/cli/operations/input.d.ts +6 -0
- package/dist/cli/operations/input.js +37 -0
- package/dist/cli/operations/io-cli.d.ts +7 -0
- package/dist/cli/operations/io-cli.js +62 -0
- package/dist/cli/operations/spec.d.ts +18 -0
- package/dist/cli/operations/spec.js +108 -0
- package/dist/cli/output/colors.d.ts +2 -2
- package/dist/cli/output/colors.js +2 -11
- package/dist/cli/output/index.d.ts +1 -0
- package/dist/cli/output/index.js +1 -0
- package/dist/cli/output/print.d.ts +3 -1
- package/dist/cli/output/print.js +7 -3
- package/dist/cli/output/progress.d.ts +15 -0
- package/dist/cli/output/progress.js +44 -0
- package/dist/cli/output/render-view.d.ts +4 -0
- package/dist/cli/output/render-view.js +63 -0
- package/dist/cli/services/agent-commands-service.d.ts +2 -0
- package/dist/cli/services/agent-commands-service.js +11 -2
- package/dist/cli/services/logs-service.js +4 -3
- package/dist/cli/tui/app.d.ts +6 -1
- package/dist/cli/tui/app.js +26 -4
- package/dist/cli/tui/list/list-screen.d.ts +14 -0
- package/dist/cli/tui/list/list-screen.js +64 -36
- package/dist/cli/tui/list/list-types.d.ts +2 -0
- package/dist/cli/tui/terminal.d.ts +7 -0
- package/dist/cli/tui/terminal.js +30 -10
- package/dist/core/config/defaults.js +8 -0
- package/dist/core/schema/config.d.ts +13 -0
- package/dist/core/schema/config.js +16 -1
- package/dist/core/schema/harness.d.ts +1 -0
- package/dist/core/schema/harness.js +5 -0
- package/dist/core/schema/sandbox.d.ts +6 -0
- package/dist/core/schema/sandbox.js +20 -3
- package/dist/core/schema/vpn.d.ts +2 -1
- package/dist/core/schema/vpn.js +3 -3
- package/dist/core/types.d.ts +23 -5
- package/dist/core/util/fs.d.ts +2 -0
- package/dist/core/util/fs.js +9 -0
- package/dist/core/util/index.d.ts +1 -1
- package/dist/core/util/index.js +1 -1
- package/dist/guest/agents/agent-files.d.ts +5 -0
- package/dist/guest/agents/agent-files.js +4 -0
- package/dist/guest/agents/local-state.d.ts +2 -0
- package/dist/guest/agents/local-state.js +21 -1
- package/dist/guest/boot/boot.js +2 -1
- package/dist/guest/daemon/daemon.js +4 -0
- package/dist/guest/daemon/sessions.d.ts +5 -0
- package/dist/guest/daemon/sessions.js +17 -3
- package/dist/guest/entries/daemon.js +3 -0
- package/dist/guest/governor/decide.d.ts +61 -0
- package/dist/guest/governor/decide.js +182 -0
- package/dist/guest/governor/governor.d.ts +42 -0
- package/dist/guest/governor/governor.js +214 -0
- package/dist/guest/governor/index.d.ts +2 -0
- package/dist/guest/governor/index.js +2 -0
- package/dist/guest/runtime/csc.d.ts +1 -0
- package/dist/guest/runtime/csc.js +1 -0
- package/dist/guest/sessions/viewer.js +3 -1
- package/dist/guest/toolchains/native/rust-extras.js +4 -2
- package/dist/guest/vpn/vpn.js +69 -21
- package/dist/guest/vpn/wireguard.d.ts +32 -0
- package/dist/guest/vpn/wireguard.js +132 -0
- package/dist/host/agent-commands/commands.d.ts +4 -0
- package/dist/host/agent-commands/commands.js +31 -7
- package/dist/host/audit/log.d.ts +1 -1
- package/dist/host/channel/attach.d.ts +3 -1
- package/dist/host/channel/attach.js +5 -2
- package/dist/host/channel/channel.d.ts +2 -0
- package/dist/host/channel/channel.js +4 -0
- package/dist/host/engines/engine.d.ts +12 -0
- package/dist/host/engines/engine.js +1 -0
- package/dist/host/engines/hcs/check.d.ts +25 -0
- package/dist/host/engines/hcs/check.js +95 -0
- package/dist/host/engines/hcs/documents.d.ts +44 -0
- package/dist/host/engines/hcs/documents.js +155 -0
- package/dist/host/engines/hcs/engine.d.ts +39 -0
- package/dist/host/engines/hcs/engine.js +171 -0
- package/dist/host/engines/hcs/helper.d.ts +43 -0
- package/dist/host/engines/hcs/helper.js +173 -0
- package/dist/host/engines/hcs/images.d.ts +37 -0
- package/dist/host/engines/hcs/images.js +152 -0
- package/dist/host/engines/index.d.ts +7 -0
- package/dist/host/engines/index.js +5 -0
- package/dist/host/engines/registry.d.ts +16 -0
- package/dist/host/engines/registry.js +30 -0
- package/dist/host/engines/types.d.ts +58 -0
- package/dist/host/engines/types.js +1 -0
- package/dist/host/engines/windows-sandbox/engine.d.ts +16 -0
- package/dist/host/engines/windows-sandbox/engine.js +63 -0
- package/dist/host/engines/windows-sandbox/wsb-text.d.ts +2 -0
- package/dist/host/engines/windows-sandbox/wsb-text.js +29 -0
- package/dist/host/launch/generate.d.ts +5 -1
- package/dist/host/launch/generate.js +50 -8
- package/dist/host/launch/index.d.ts +3 -1
- package/dist/host/launch/index.js +3 -1
- package/dist/host/launch/launch-plan.d.ts +13 -0
- package/dist/host/launch/launch-plan.js +26 -0
- package/dist/host/launch/mappings.js +2 -1
- package/dist/host/launch/risk-notes.d.ts +10 -0
- package/dist/host/launch/risk-notes.js +18 -0
- package/dist/host/launch/session-file.d.ts +2 -0
- package/dist/host/launch/session-file.js +7 -1
- package/dist/host/launch/wsb-file.d.ts +28 -4
- package/dist/host/launch/wsb-file.js +61 -42
- package/dist/host/life/crash-report.d.ts +27 -1
- package/dist/host/life/crash-report.js +173 -7
- package/dist/host/life/host-memory.d.ts +40 -0
- package/dist/host/life/host-memory.js +137 -0
- package/dist/host/life/index.d.ts +2 -0
- package/dist/host/life/index.js +2 -0
- package/dist/host/life/stop-events.d.ts +2 -0
- package/dist/host/life/stop-events.js +20 -3
- package/dist/host/life/stop-warning.js +1 -1
- package/dist/host/life/watchdog.d.ts +54 -0
- package/dist/host/life/watchdog.js +108 -0
- package/dist/host/native.d.ts +3 -2
- package/dist/host/native.js +22 -3
- package/dist/host/profile/claude-login.js +3 -2
- package/dist/host/sandbox/contained-deps.d.ts +2 -0
- package/dist/host/sandbox/contained-deps.js +17 -0
- package/dist/host/sandbox/index.d.ts +2 -0
- package/dist/host/sandbox/index.js +2 -0
- package/dist/host/sandbox/platform.d.ts +2 -0
- package/dist/host/sandbox/platform.js +7 -7
- package/dist/host/sandbox/tasklist.d.ts +13 -0
- package/dist/host/sandbox/tasklist.js +47 -0
- package/dist/host/secrets/launch-secrets.js +2 -1
- package/dist/host/sharing/current.js +2 -6
- package/dist/host/storage/contained/check.d.ts +9 -0
- package/dist/host/storage/contained/check.js +37 -0
- package/dist/host/storage/contained/contained-storage.d.ts +66 -0
- package/dist/host/storage/contained/contained-storage.js +168 -0
- package/dist/host/storage/contained/disk-port.d.ts +43 -0
- package/dist/host/storage/contained/disk-port.js +122 -0
- package/dist/host/storage/contained/index.d.ts +9 -0
- package/dist/host/storage/contained/index.js +8 -0
- package/dist/host/storage/contained/layout.d.ts +28 -0
- package/dist/host/storage/contained/layout.js +45 -0
- package/dist/host/storage/contained/resolve.d.ts +4 -0
- package/dist/host/storage/contained/resolve.js +30 -0
- package/dist/host/storage/contained/setting.d.ts +1 -0
- package/dist/host/storage/contained/setting.js +5 -0
- package/dist/host/storage/contained/state.d.ts +89 -0
- package/dist/host/storage/contained/state.js +166 -0
- package/dist/host/storage/contained/transfer.d.ts +24 -0
- package/dist/host/storage/contained/transfer.js +121 -0
- package/dist/host/storage/index.d.ts +2 -0
- package/dist/host/storage/index.js +2 -0
- package/dist/host/storage/providers/contained.d.ts +15 -0
- package/dist/host/storage/providers/contained.js +39 -0
- package/dist/host/storage/providers/folder.d.ts +12 -0
- package/dist/host/storage/providers/folder.js +20 -0
- package/dist/host/storage/providers/index.d.ts +4 -0
- package/dist/host/storage/providers/index.js +3 -0
- package/dist/host/storage/providers/registry.d.ts +9 -0
- package/dist/host/storage/providers/registry.js +14 -0
- package/dist/host/storage/providers/types.d.ts +33 -0
- package/dist/host/storage/providers/types.js +1 -0
- package/dist/host/tools/conpty.js +3 -2
- package/dist/host/tools/windows-terminal.js +8 -7
- package/dist/host/vpn/index.d.ts +1 -1
- package/dist/host/vpn/index.js +1 -1
- package/dist/host/vpn/prepare.d.ts +2 -0
- package/dist/host/vpn/prepare.js +63 -2
- package/dist/host/web/browser.d.ts +2 -0
- package/dist/host/web/browser.js +14 -1
- package/dist/host/web/index.d.ts +3 -2
- package/dist/host/web/index.js +3 -2
- package/dist/host/web/rpc.d.ts +24 -0
- package/dist/host/web/rpc.js +149 -0
- package/dist/host/web/runner.d.ts +2 -0
- package/dist/host/web/runner.js +1 -2
- package/dist/host/web/server.d.ts +7 -0
- package/dist/host/web/server.js +23 -3
- package/dist/runtime/agent.js +69 -8
- package/dist/runtime/daemon.js +1058 -624
- package/dist/runtime/db.js +69 -8
- package/dist/runtime/main.js +624 -182
- package/dist/runtime/netproxy.js +69 -8
- package/dist/runtime/view.js +87 -13
- package/dist/selftest/agent-paths.test.js +11 -1
- package/dist/selftest/app.test.d.ts +2 -0
- package/dist/selftest/app.test.js +324 -0
- package/dist/selftest/channel.test.js +13 -0
- package/dist/selftest/cli.test.js +2 -1
- package/dist/selftest/config.test.js +59 -0
- package/dist/selftest/contained.test.d.ts +2 -0
- package/dist/selftest/contained.test.js +544 -0
- package/dist/selftest/credentials.test.js +1 -1
- package/dist/selftest/engine.test.d.ts +2 -0
- package/dist/selftest/engine.test.js +127 -0
- package/dist/selftest/hcs.test.d.ts +2 -0
- package/dist/selftest/hcs.test.js +482 -0
- package/dist/selftest/heavy-load.test.d.ts +2 -0
- package/dist/selftest/heavy-load.test.js +329 -0
- package/dist/selftest/index.js +12 -0
- package/dist/selftest/layout.test.js +12 -0
- package/dist/selftest/menu-perf.test.d.ts +2 -0
- package/dist/selftest/menu-perf.test.js +248 -0
- package/dist/selftest/menu-screens.js +4 -2
- package/dist/selftest/menu.test.js +36 -8
- package/dist/selftest/support/engine-cases.d.ts +8 -0
- package/dist/selftest/support/engine-cases.js +65 -0
- package/dist/selftest/support/engine-golden.d.ts +3 -0
- package/dist/selftest/support/engine-golden.js +66 -0
- package/dist/selftest/support/fixtures.d.ts +7 -1
- package/dist/selftest/support/hcs-probes.d.ts +17 -0
- package/dist/selftest/support/hcs-probes.js +41 -0
- package/dist/selftest/vpn.test.js +136 -3
- package/dist/selftest/web.test.js +101 -3
- package/dist/shared/channel/run-dir.d.ts +4 -1
- package/dist/shared/channel/run-dir.js +7 -1
- package/dist/shared/channel/schema.d.ts +35 -1
- package/dist/shared/channel/schema.js +27 -0
- package/dist/shared/channel/secrets.d.ts +1 -0
- package/dist/shared/channel/secrets.js +6 -0
- package/dist/shared/memory/index.d.ts +1 -0
- package/dist/shared/memory/index.js +1 -0
- package/dist/shared/memory/policy.d.ts +33 -0
- package/dist/shared/memory/policy.js +55 -0
- package/dist/shared/pty/output-filter.d.ts +14 -1
- package/dist/shared/pty/output-filter.js +22 -4
- package/dist/shared/pty/terminal.d.ts +4 -1
- package/dist/shared/pty/terminal.js +4 -2
- package/dist/shared/system.d.ts +1 -0
- package/dist/shared/system.js +1 -0
- package/dist/shared/vpn/download.d.ts +16 -0
- package/dist/shared/vpn/download.js +16 -0
- package/dist/shared/vpn/wireguard.d.ts +51 -0
- package/dist/shared/vpn/wireguard.js +227 -0
- package/dist/sync/sync-cycle.js +5 -5
- package/dist/sync/watcher.d.ts +13 -0
- package/dist/sync/watcher.js +92 -3
- package/dist/web/index.html +4 -3
- package/dist/web/main-NRRS5O6Z.js +157 -0
- package/dist/web/main-T5DMCHYV.css +1 -0
- package/native/csharp/HcsHost/AssemblyInfo.cs +14 -0
- package/native/csharp/HcsHost/Guard.cs +100 -0
- package/native/csharp/HcsHost/Hcs.cs +256 -0
- package/native/csharp/HcsHost/Json.cs +78 -0
- package/native/csharp/HcsHost/Program.cs +185 -0
- package/native/csharp/HcsHost/VirtualDisk.cs +95 -0
- package/native/csharp/HostTools/AssemblyInfo.cs +14 -0
- package/native/csharp/HostTools/MemoryStatus.cs +107 -0
- package/native/csharp/HostTools/Program.cs +58 -17
- package/native/scripts/check-contained.ps1 +28 -0
- package/native/scripts/contained-common.ps1 +241 -0
- package/native/scripts/dismount-contained.ps1 +66 -0
- package/native/scripts/memory-status.ps1 +34 -0
- package/native/scripts/mount-contained.ps1 +139 -0
- package/native/scripts/run-elevated.ps1 +17 -4
- package/native/scripts/stop-events.ps1 +23 -8
- package/package.json +9 -2
- package/sandbox/csharp/MemGov.cs +398 -0
- package/sandbox/csharp/PtyHost.cs +111 -5
- package/schemas/config.schema.json +60 -6
- package/schemas/sandbox-config.schema.json +3 -3
- package/dist/cli/commands/sessions/control.d.ts +0 -4
- package/dist/cli/commands/sessions/control.js +0 -66
- package/dist/cli/commands/sessions/list.d.ts +0 -2
- package/dist/cli/commands/sessions/list.js +0 -48
- package/dist/cli/commands/sessions/status.d.ts +0 -4
- package/dist/cli/commands/sessions/status.js +0 -114
- package/dist/cli/commands/sessions/window.d.ts +0 -3
- package/dist/cli/commands/sessions/window.js +0 -18
- package/dist/cli/menu/settings/path-kind.js +0 -44
- package/dist/web/main-CXESL347.js +0 -45
- package/dist/web/main-VLR7DVQ4.css +0 -1
- /package/dist/{cli/menu/settings → app/kernel}/schema-node.d.ts +0 -0
- /package/dist/{cli/menu/settings → app/kernel}/schema-node.js +0 -0
package/dist/host/native.js
CHANGED
|
@@ -27,8 +27,9 @@ export function nativeScript(name, args = []) {
|
|
|
27
27
|
];
|
|
28
28
|
}
|
|
29
29
|
/**
|
|
30
|
-
* <storage>\bin
|
|
31
|
-
* cannot be built (no csc, a compile error): the caller then runs the PowerShell fallback.
|
|
30
|
+
* <storage>\bin\<hash of its sources>\HostTools.exe, compiled with the .NET Framework csc the first time; null when it
|
|
31
|
+
* cannot be built (no csc, a compile error): the caller then runs the PowerShell fallback. A plain name and full file
|
|
32
|
+
* properties (AssemblyInfo.cs), so antivirus software does not take a nameless, random-looking exe for malware.
|
|
32
33
|
*/
|
|
33
34
|
export function hostToolsExe(storageRoot) {
|
|
34
35
|
refuseInSelfTest('build HostTools.exe');
|
|
@@ -47,15 +48,33 @@ export function hostToolsExe(storageRoot) {
|
|
|
47
48
|
const hash = crypto.createHash('sha256');
|
|
48
49
|
for (const file of sources)
|
|
49
50
|
hash.update(fs.readFileSync(file));
|
|
50
|
-
const
|
|
51
|
+
const bin = path.join(storageRoot, 'bin');
|
|
52
|
+
const exe = path.join(bin, hash.digest('hex').slice(0, 12), 'HostTools.exe');
|
|
51
53
|
if (fs.existsSync(exe))
|
|
52
54
|
return exe;
|
|
53
55
|
if (!fs.existsSync(SYSTEM.csc()))
|
|
54
56
|
return null;
|
|
55
57
|
fs.mkdirSync(path.dirname(exe), { recursive: true });
|
|
58
|
+
removeOldHostTools(bin, path.dirname(exe));
|
|
56
59
|
const result = spawnSync(SYSTEM.csc(), ['/nologo', '/optimize', '/target:exe', '/out:' + exe, ...sources], {
|
|
57
60
|
windowsHide: true,
|
|
58
61
|
timeout: 120000,
|
|
59
62
|
});
|
|
60
63
|
return result.status === 0 && fs.existsSync(exe) ? exe : null;
|
|
61
64
|
}
|
|
65
|
+
/** Earlier builds: HostTools-<hash>.exe files and other <hash> folders in bin (best effort: one in use stays). */
|
|
66
|
+
function removeOldHostTools(bin, keep) {
|
|
67
|
+
for (const entry of fs.readdirSync(bin, { withFileTypes: true })) {
|
|
68
|
+
const full = path.join(bin, entry.name);
|
|
69
|
+
const old = (entry.isFile() && /^HostTools-[0-9a-f]{12}\.exe$/i.test(entry.name)) ||
|
|
70
|
+
(entry.isDirectory() && /^[0-9a-f]{12}$/i.test(entry.name) && full !== keep);
|
|
71
|
+
if (!old)
|
|
72
|
+
continue;
|
|
73
|
+
try {
|
|
74
|
+
fs.rmSync(full, { recursive: true, force: true });
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
// Still running (keep-awake of another sandbox): removed by a later build.
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import fs from 'node:fs';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { assertTestWritable } from '../test-guard.js';
|
|
6
|
+
import { hostDataFolder, hostDataRoot } from '../storage/index.js';
|
|
6
7
|
import { assertNoLinks, fail, fileExists, isLink, removeLink, tryReadJson, writeJson } from '../../core/util/index.js';
|
|
7
8
|
const MAX_LOGIN_BYTES = 1024 * 1024;
|
|
8
9
|
/** .claude.json holds per-project state and can be large; still capped so a hostile file cannot exhaust memory. */
|
|
@@ -10,7 +11,7 @@ export const MAX_STATE_BYTES = 256 * 1024 * 1024;
|
|
|
10
11
|
/** Caches tied to an account (Claude deletes these on /logout). */
|
|
11
12
|
const ACCOUNT_CACHES = ['remote-settings.json', 'policy-limits.json', 'policy-limits.json.stamp.json'];
|
|
12
13
|
export function sandboxClaudeDir(dataPath) {
|
|
13
|
-
return path.join(dataPath, 'state', 'claude');
|
|
14
|
+
return path.join(hostDataFolder(dataPath, 'state'), 'claude');
|
|
14
15
|
}
|
|
15
16
|
export function emailOf(oauthAccount) {
|
|
16
17
|
const email = oauthAccount?.emailAddress;
|
|
@@ -32,7 +33,7 @@ export function readLogin(claudeDir, globalStateFile) {
|
|
|
32
33
|
}
|
|
33
34
|
/** The sandbox's Claude login files (refuses when the data folder holds a link on the way). */
|
|
34
35
|
export function sandboxLogin(dataPath) {
|
|
35
|
-
assertNoLinks(dataPath, sandboxClaudeDir(dataPath));
|
|
36
|
+
assertNoLinks(hostDataRoot(dataPath), sandboxClaudeDir(dataPath));
|
|
36
37
|
return {
|
|
37
38
|
claudeDir: sandboxClaudeDir(dataPath),
|
|
38
39
|
globalStateFile: path.join(sandboxClaudeDir(dataPath), '.claude.json'),
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// The real wiring of contained storage on this PC: the permission-prompt scripts, protectPrivateFolder for new
|
|
2
|
+
// folders, your SID, and whether a sandbox runs. Lives here (not in host/storage) so storage never imports the
|
|
3
|
+
// sandbox or sharing folders, which import storage. The self-test never reaches a disk through it: the disk port
|
|
4
|
+
// refuses there.
|
|
5
|
+
import { ScriptDiskPort } from '../storage/index.js';
|
|
6
|
+
import { currentUserSid, protectPrivateFolder } from '../sharing/index.js';
|
|
7
|
+
import { isSandboxRunning } from './platform.js';
|
|
8
|
+
export function realContainedDeps(storageRoot, maxGb) {
|
|
9
|
+
return {
|
|
10
|
+
storageRoot,
|
|
11
|
+
maxGb,
|
|
12
|
+
protect: (dir) => protectPrivateFolder(dir),
|
|
13
|
+
userSid: currentUserSid,
|
|
14
|
+
port: new ScriptDiskPort(),
|
|
15
|
+
sandboxRunning: isSandboxRunning,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
@@ -5,6 +5,8 @@ import type { SandboxOwner } from '../sharing/owner.js';
|
|
|
5
5
|
*/
|
|
6
6
|
export declare function insideSandbox(): boolean;
|
|
7
7
|
export declare const GUEST_DATA_DIR = "C:\\AgentData";
|
|
8
|
+
/** Whether a Windows Sandbox program is in this process list (tasklist /NH /FO CSV output). */
|
|
9
|
+
export declare function sandboxInTasklist(tasklist: string): boolean;
|
|
8
10
|
export declare function isSandboxRunning(): boolean;
|
|
9
11
|
export declare function sandboxFeatureInstalled(): boolean;
|
|
10
12
|
/** `owner`: whose sandbox runs, when the caller knows (host/sharing); it makes the message say who started it. */
|
|
@@ -2,6 +2,7 @@ import fs from 'node:fs';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { spawn, spawnSync } from 'node:child_process';
|
|
4
4
|
import { SYSTEM } from '../../shared/system.js';
|
|
5
|
+
import { readTasklist } from './tasklist.js';
|
|
5
6
|
import { refuseInSelfTest } from '../test-guard.js';
|
|
6
7
|
import { fail, fileExists, isWin, sleep } from '../../core/util/index.js';
|
|
7
8
|
import { launchBlocked } from '../sharing/rules.js';
|
|
@@ -20,18 +21,17 @@ export function insideSandbox() {
|
|
|
20
21
|
fs.existsSync('C:\\AgentData\\run'));
|
|
21
22
|
}
|
|
22
23
|
export const GUEST_DATA_DIR = 'C:\\AgentData';
|
|
24
|
+
/** Whether a Windows Sandbox program is in this process list (tasklist /NH /FO CSV output). */
|
|
25
|
+
export function sandboxInTasklist(tasklist) {
|
|
26
|
+
const out = tasklist.toLowerCase();
|
|
27
|
+
return SANDBOX_PROCESSES.some((image) => out.includes('"' + image.toLowerCase() + '"'));
|
|
28
|
+
}
|
|
23
29
|
export function isSandboxRunning() {
|
|
24
30
|
if (!isWin)
|
|
25
31
|
return false;
|
|
26
32
|
if (insideSandbox())
|
|
27
33
|
return true;
|
|
28
|
-
|
|
29
|
-
encoding: 'utf8',
|
|
30
|
-
windowsHide: true,
|
|
31
|
-
timeout: HELPER_TIMEOUT_MS,
|
|
32
|
-
});
|
|
33
|
-
const out = (result.stdout ?? '').toLowerCase();
|
|
34
|
-
return SANDBOX_PROCESSES.some((image) => out.includes('"' + image.toLowerCase() + '"'));
|
|
34
|
+
return sandboxInTasklist(readTasklist());
|
|
35
35
|
}
|
|
36
36
|
export function sandboxFeatureInstalled() {
|
|
37
37
|
return isWin && fileExists(path.join(process.env.WINDIR ?? 'C:\\Windows', 'System32', 'WindowsSandbox.exe'));
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** Runs tasklist: the real one, or what a test put in its place. */
|
|
2
|
+
export interface TasklistRunners {
|
|
3
|
+
sync(): string;
|
|
4
|
+
async(): Promise<string>;
|
|
5
|
+
}
|
|
6
|
+
/** Replaces how tasklist runs (tests count the calls); no argument puts the real one back. */
|
|
7
|
+
export declare function useTasklistRunners(replacement?: TasklistRunners): void;
|
|
8
|
+
/** While set, a synchronous read returns this copy (undefined: none yet, run tasklist). Null removes it. */
|
|
9
|
+
export declare function useTasklistCache(read: (() => string | undefined) | null): void;
|
|
10
|
+
/** The process list now: the cached copy when the menu keeps one, else a run of tasklist the caller waits for. */
|
|
11
|
+
export declare function readTasklist(): string;
|
|
12
|
+
/** The process list from a run of tasklist that does not block the program (the menu's background refresh). */
|
|
13
|
+
export declare function readTasklistAsync(): Promise<string>;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// The process list (`tasklist /NH /FO CSV`) that "is the sandbox running?" and "whose is it?" come from. Commands
|
|
2
|
+
// ask it synchronously. The menu cannot wait for a child process on every key, so it keeps a copy fresh in the
|
|
3
|
+
// background (readTasklistAsync) and installs it with useTasklistCache: until then, and after it is removed, every
|
|
4
|
+
// question runs tasklist itself.
|
|
5
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
6
|
+
import { SYSTEM } from '../../shared/system.js';
|
|
7
|
+
/** The longest tasklist may take before it counts as stuck. */
|
|
8
|
+
const TASKLIST_TIMEOUT_MS = 15000;
|
|
9
|
+
const ARGS = ['/NH', '/FO', 'CSV'];
|
|
10
|
+
const REAL_RUNNERS = {
|
|
11
|
+
sync: () => spawnSync(SYSTEM.tasklist(), ARGS, { encoding: 'utf8', windowsHide: true, timeout: TASKLIST_TIMEOUT_MS }).stdout ??
|
|
12
|
+
'',
|
|
13
|
+
async: () => new Promise((resolve) => {
|
|
14
|
+
let out = '';
|
|
15
|
+
const child = spawn(SYSTEM.tasklist(), ARGS, { windowsHide: true, stdio: ['ignore', 'pipe', 'ignore'] });
|
|
16
|
+
const timer = setTimeout(() => child.kill(), TASKLIST_TIMEOUT_MS);
|
|
17
|
+
child.stdout.setEncoding('utf8');
|
|
18
|
+
child.stdout.on('data', (chunk) => (out += chunk));
|
|
19
|
+
// A tasklist that cannot start or is killed counts as an empty list, like the synchronous one.
|
|
20
|
+
child.on('error', () => {
|
|
21
|
+
clearTimeout(timer);
|
|
22
|
+
resolve('');
|
|
23
|
+
});
|
|
24
|
+
child.on('close', () => {
|
|
25
|
+
clearTimeout(timer);
|
|
26
|
+
resolve(out);
|
|
27
|
+
});
|
|
28
|
+
}),
|
|
29
|
+
};
|
|
30
|
+
let runners = REAL_RUNNERS;
|
|
31
|
+
let cache = null;
|
|
32
|
+
/** Replaces how tasklist runs (tests count the calls); no argument puts the real one back. */
|
|
33
|
+
export function useTasklistRunners(replacement = REAL_RUNNERS) {
|
|
34
|
+
runners = replacement;
|
|
35
|
+
}
|
|
36
|
+
/** While set, a synchronous read returns this copy (undefined: none yet, run tasklist). Null removes it. */
|
|
37
|
+
export function useTasklistCache(read) {
|
|
38
|
+
cache = read;
|
|
39
|
+
}
|
|
40
|
+
/** The process list now: the cached copy when the menu keeps one, else a run of tasklist the caller waits for. */
|
|
41
|
+
export function readTasklist() {
|
|
42
|
+
return cache?.() ?? runners.sync();
|
|
43
|
+
}
|
|
44
|
+
/** The process list from a run of tasklist that does not block the program (the menu's background refresh). */
|
|
45
|
+
export function readTasklistAsync() {
|
|
46
|
+
return runners.async();
|
|
47
|
+
}
|
|
@@ -11,7 +11,8 @@ export function writeSecrets(dataPath, id, secrets) {
|
|
|
11
11
|
!Object.keys(secrets.harnessSpecEnvironment ?? {}).length &&
|
|
12
12
|
!Object.keys(secrets.projectHarnessEnvironment ?? {}).length &&
|
|
13
13
|
!secrets.claudeCredentials &&
|
|
14
|
-
!secrets.vpn
|
|
14
|
+
!secrets.vpn &&
|
|
15
|
+
!secrets.wireguardConfig;
|
|
15
16
|
if (empty)
|
|
16
17
|
return null;
|
|
17
18
|
const run = new RunDir(dataPath);
|
|
@@ -3,18 +3,14 @@ import os from 'node:os';
|
|
|
3
3
|
import { spawnSync } from 'node:child_process';
|
|
4
4
|
import { SYSTEM } from '../../shared/system.js';
|
|
5
5
|
import { insideSandbox } from '../sandbox/platform.js';
|
|
6
|
+
import { readTasklist } from '../sandbox/tasklist.js';
|
|
6
7
|
import { activeDir } from './machine-paths.js';
|
|
7
8
|
import { ActiveRecords } from './active-record.js';
|
|
8
9
|
import { parseTasklist, whoOwnsSandbox } from './owner.js';
|
|
9
10
|
const HELPER_TIMEOUT_MS = 15000;
|
|
10
11
|
/** The rows of `tasklist /NH /FO CSV`: every process of every session (standard users may see these). */
|
|
11
12
|
export function listProcesses() {
|
|
12
|
-
|
|
13
|
-
encoding: 'utf8',
|
|
14
|
-
windowsHide: true,
|
|
15
|
-
timeout: HELPER_TIMEOUT_MS,
|
|
16
|
-
});
|
|
17
|
-
return parseTasklist(run.stdout ?? '');
|
|
13
|
+
return parseTasklist(readTasklist());
|
|
18
14
|
}
|
|
19
15
|
/** The Windows session this process runs in (0 = services, 1 = the first signed-in user ...), or null when unknown. */
|
|
20
16
|
export function sessionOf(rows, pid = process.pid) {
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ProviderCheck } from '../providers/types.js';
|
|
2
|
+
export interface ContainedFacts {
|
|
3
|
+
readonly windows: boolean;
|
|
4
|
+
readonly insideSandbox: boolean;
|
|
5
|
+
readonly diskpartPresent: boolean;
|
|
6
|
+
readonly powershellPresent: boolean;
|
|
7
|
+
}
|
|
8
|
+
export declare function containedFacts(): ContainedFacts;
|
|
9
|
+
export declare function containedAvailability(facts?: ContainedFacts): ProviderCheck;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// Can contained storage run on this PC? Windows, diskpart present, and not inside a Windows Sandbox (a sandbox has no
|
|
2
|
+
// Virtual Disk Service, and a nested one would only mount inside itself).
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import { isWin } from '../../../core/util/index.js';
|
|
5
|
+
import { SYSTEM, system32 } from '../../../shared/system.js';
|
|
6
|
+
import { insideSandbox } from '../../sandbox/platform.js';
|
|
7
|
+
export function containedFacts() {
|
|
8
|
+
return {
|
|
9
|
+
windows: isWin,
|
|
10
|
+
insideSandbox: insideSandbox(),
|
|
11
|
+
diskpartPresent: isWin && fs.existsSync(system32('diskpart.exe')),
|
|
12
|
+
powershellPresent: isWin && fs.existsSync(SYSTEM.powershell()),
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
export function containedAvailability(facts = containedFacts()) {
|
|
16
|
+
const reasons = [];
|
|
17
|
+
const fixes = [];
|
|
18
|
+
if (!facts.windows) {
|
|
19
|
+
reasons.push('Contained storage needs Windows (it uses disk images).');
|
|
20
|
+
fixes.push('Set storage.contained to "off".');
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
if (facts.insideSandbox) {
|
|
24
|
+
reasons.push('This is inside a Windows Sandbox: disk images cannot be mounted here.');
|
|
25
|
+
fixes.push('Run the command on your PC, not in a sandbox.');
|
|
26
|
+
}
|
|
27
|
+
if (!facts.diskpartPresent) {
|
|
28
|
+
reasons.push('diskpart.exe was not found in Windows\\System32.');
|
|
29
|
+
fixes.push('Repair Windows (sfc /scannow) or set storage.contained to "off".');
|
|
30
|
+
}
|
|
31
|
+
if (!facts.powershellPresent) {
|
|
32
|
+
reasons.push('Windows PowerShell 5.1 was not found.');
|
|
33
|
+
fixes.push('Repair Windows or set storage.contained to "off".');
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return { available: reasons.length === 0, reasons, fixes };
|
|
37
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { type ContainedPaths } from './layout.js';
|
|
2
|
+
import type { DiskPort } from './disk-port.js';
|
|
3
|
+
import { type ContainedDiagnosis, type MountReality } from './state.js';
|
|
4
|
+
export interface ContainedDeps {
|
|
5
|
+
readonly storageRoot: string;
|
|
6
|
+
readonly maxGb: number;
|
|
7
|
+
/** Makes a NEW, empty folder private (protectPrivateFolder). Never called on a folder that holds files. */
|
|
8
|
+
readonly protect: (dir: string) => void;
|
|
9
|
+
readonly userSid: () => string;
|
|
10
|
+
readonly port: DiskPort;
|
|
11
|
+
readonly sandboxRunning: () => boolean;
|
|
12
|
+
readonly alive?: (pid: number) => boolean;
|
|
13
|
+
/** The volume of a folder (stat.dev): a mounted image is another volume than the folder it is mounted in. */
|
|
14
|
+
readonly deviceOf?: (dir: string) => number;
|
|
15
|
+
}
|
|
16
|
+
export interface HolderId {
|
|
17
|
+
readonly id: string;
|
|
18
|
+
readonly pid: number;
|
|
19
|
+
}
|
|
20
|
+
export interface ReleaseResult {
|
|
21
|
+
readonly dismounted: boolean;
|
|
22
|
+
/** Why it stayed mounted (or could not be dismounted), when it did. */
|
|
23
|
+
readonly note?: string;
|
|
24
|
+
}
|
|
25
|
+
export declare class ContainedStorage {
|
|
26
|
+
private readonly deps;
|
|
27
|
+
readonly set: string;
|
|
28
|
+
readonly paths: ContainedPaths;
|
|
29
|
+
private readonly stateFile;
|
|
30
|
+
private readonly alive;
|
|
31
|
+
constructor(deps: ContainedDeps, set: string);
|
|
32
|
+
imageExists(): boolean;
|
|
33
|
+
/** Holders of this set that still run. */
|
|
34
|
+
liveHolders(): number;
|
|
35
|
+
recordedMounted(): boolean;
|
|
36
|
+
reality(): Promise<MountReality>;
|
|
37
|
+
diagnose(): Promise<ContainedDiagnosis>;
|
|
38
|
+
/**
|
|
39
|
+
* Makes sure the image is mounted at its folder (creating it when `create` is set and no image exists yet), records
|
|
40
|
+
* the holder and returns the mount folder, ready with the folders of the data set inside it.
|
|
41
|
+
*/
|
|
42
|
+
acquire(holder: HolderId, o?: {
|
|
43
|
+
readonly create?: boolean;
|
|
44
|
+
}): Promise<string>;
|
|
45
|
+
private mountForHolder;
|
|
46
|
+
private withHolder;
|
|
47
|
+
/** The record knows this set as mounted (the watcher only holds a mount that exists). */
|
|
48
|
+
isRecorded(): boolean;
|
|
49
|
+
/** Adds a holder to a mount that is recorded as mounted (the background watcher); false when nothing is mounted. */
|
|
50
|
+
hold(holder: HolderId): boolean;
|
|
51
|
+
/** Lets go; dismounts when nobody else holds it and no sandbox runs. Never throws: a stuck mount is for doctor. */
|
|
52
|
+
release(id: string): Promise<ReleaseResult>;
|
|
53
|
+
dismountIfIdle(): Promise<ReleaseResult>;
|
|
54
|
+
/**
|
|
55
|
+
* Deletes the image file (what `storage contained off` offers last, after the files were moved out). Only an image
|
|
56
|
+
* that is not mounted and not attached; never through a link. The folders around it stay.
|
|
57
|
+
*/
|
|
58
|
+
deleteImage(): Promise<void>;
|
|
59
|
+
/** Doctor/repair: the record says mounted, nothing is (clears the record; no permission prompt needed). */
|
|
60
|
+
clearRecord(): void;
|
|
61
|
+
private requireOk;
|
|
62
|
+
/** A mounted image is another volume than its folder; the same id would mean writes land on this PC's own disk. */
|
|
63
|
+
private assertReallyMounted;
|
|
64
|
+
/** The folders the image needs on this PC. Only folders this call creates are made private, never one with files. */
|
|
65
|
+
private ensureFolders;
|
|
66
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
// One data set's contained storage: make sure its image is mounted for a launch (creating it the first time), keep the
|
|
2
|
+
// record of who holds the mount, and dismount when the last holder lets go and no sandbox runs. Disks are only touched
|
|
3
|
+
// through the DiskPort; folders only in the places the layout names.
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { assertNoLinks, fail } from '../../../core/util/index.js';
|
|
7
|
+
import { assertTestWritable } from '../../test-guard.js';
|
|
8
|
+
import { CONTAINED_FOLDERS, containedPaths } from './layout.js';
|
|
9
|
+
import { addHolder, diagnose, markMounted, processAlive, pruneHolders, removeHolder, setStateOf, StateFile, } from './state.js';
|
|
10
|
+
const deviceOfDefault = (dir) => fs.statSync(dir).dev;
|
|
11
|
+
export class ContainedStorage {
|
|
12
|
+
deps;
|
|
13
|
+
set;
|
|
14
|
+
paths;
|
|
15
|
+
stateFile;
|
|
16
|
+
alive;
|
|
17
|
+
constructor(deps, set) {
|
|
18
|
+
this.deps = deps;
|
|
19
|
+
this.set = set;
|
|
20
|
+
this.paths = containedPaths(deps.storageRoot, set);
|
|
21
|
+
this.alive = deps.alive ?? processAlive;
|
|
22
|
+
this.stateFile = new StateFile(this.paths, this.alive);
|
|
23
|
+
}
|
|
24
|
+
imageExists() {
|
|
25
|
+
return fs.existsSync(this.paths.image);
|
|
26
|
+
}
|
|
27
|
+
/** Holders of this set that still run. */
|
|
28
|
+
liveHolders() {
|
|
29
|
+
return setStateOf(this.stateFile.read(), this.set).holders.filter((h) => this.alive(h.pid)).length;
|
|
30
|
+
}
|
|
31
|
+
recordedMounted() {
|
|
32
|
+
return setStateOf(this.stateFile.read(), this.set).mounted;
|
|
33
|
+
}
|
|
34
|
+
reality() {
|
|
35
|
+
return this.deps.port.reality(this.paths);
|
|
36
|
+
}
|
|
37
|
+
async diagnose() {
|
|
38
|
+
const reality = await this.reality();
|
|
39
|
+
return diagnose({
|
|
40
|
+
state: setStateOf(this.stateFile.read(), this.set),
|
|
41
|
+
reality,
|
|
42
|
+
live: this.liveHolders(),
|
|
43
|
+
sandboxRunning: this.deps.sandboxRunning(),
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Makes sure the image is mounted at its folder (creating it when `create` is set and no image exists yet), records
|
|
48
|
+
* the holder and returns the mount folder, ready with the folders of the data set inside it.
|
|
49
|
+
*/
|
|
50
|
+
async acquire(holder, o = {}) {
|
|
51
|
+
this.ensureFolders();
|
|
52
|
+
// Others that run now vouch for a mount that is there; this holder is recorded first, so a watcher that is just
|
|
53
|
+
// finishing cannot dismount between the check and the use.
|
|
54
|
+
const others = this.liveHolders();
|
|
55
|
+
this.stateFile.update((s) => this.withHolder(s, holder));
|
|
56
|
+
try {
|
|
57
|
+
return await this.mountForHolder(others > 0, o.create === true);
|
|
58
|
+
}
|
|
59
|
+
catch (e) {
|
|
60
|
+
this.stateFile.update((s) => pruneHolders(removeHolder(s, this.set, holder.id), this.set, this.alive));
|
|
61
|
+
throw e;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
async mountForHolder(vouched, create) {
|
|
65
|
+
const { paths } = this;
|
|
66
|
+
const before = await this.reality();
|
|
67
|
+
const mountedAlready = vouched && this.recordedMounted() && before.mountIsMountPoint;
|
|
68
|
+
if (!mountedAlready) {
|
|
69
|
+
if (!before.imageExists && !create)
|
|
70
|
+
fail('The contained storage image is missing: ' + paths.image, 'Run sandbox storage contained on to make it again, or sandbox storage contained off to go back to folders.');
|
|
71
|
+
const outcome = await this.deps.port.mount({
|
|
72
|
+
paths,
|
|
73
|
+
maxGb: this.deps.maxGb,
|
|
74
|
+
create: !before.imageExists,
|
|
75
|
+
userSid: this.deps.userSid(),
|
|
76
|
+
});
|
|
77
|
+
this.requireOk(outcome, 'mount');
|
|
78
|
+
}
|
|
79
|
+
this.assertReallyMounted();
|
|
80
|
+
for (const name of CONTAINED_FOLDERS)
|
|
81
|
+
fs.mkdirSync(path.join(paths.mount, name), { recursive: true });
|
|
82
|
+
this.stateFile.update((s) => markMounted(s, this.set, true));
|
|
83
|
+
return paths.mount;
|
|
84
|
+
}
|
|
85
|
+
withHolder(state, holder) {
|
|
86
|
+
return addHolder(pruneHolders(state, this.set, this.alive), this.set, {
|
|
87
|
+
...holder,
|
|
88
|
+
since: new Date().toISOString(),
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
/** The record knows this set as mounted (the watcher only holds a mount that exists). */
|
|
92
|
+
isRecorded() {
|
|
93
|
+
return this.recordedMounted();
|
|
94
|
+
}
|
|
95
|
+
/** Adds a holder to a mount that is recorded as mounted (the background watcher); false when nothing is mounted. */
|
|
96
|
+
hold(holder) {
|
|
97
|
+
if (!this.recordedMounted())
|
|
98
|
+
return false;
|
|
99
|
+
this.stateFile.update((s) => this.withHolder(s, holder));
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
/** Lets go; dismounts when nobody else holds it and no sandbox runs. Never throws: a stuck mount is for doctor. */
|
|
103
|
+
async release(id) {
|
|
104
|
+
this.stateFile.update((s) => pruneHolders(removeHolder(s, this.set, id), this.set, this.alive));
|
|
105
|
+
return this.dismountIfIdle();
|
|
106
|
+
}
|
|
107
|
+
async dismountIfIdle() {
|
|
108
|
+
if (this.liveHolders() > 0)
|
|
109
|
+
return { dismounted: false, note: 'another command still uses it' };
|
|
110
|
+
if (this.deps.sandboxRunning())
|
|
111
|
+
return { dismounted: false, note: 'a sandbox is running' };
|
|
112
|
+
try {
|
|
113
|
+
// Nothing mounted: no permission prompt for nothing.
|
|
114
|
+
if (!(await this.reality()).mountIsMountPoint) {
|
|
115
|
+
if (this.recordedMounted())
|
|
116
|
+
this.clearRecord();
|
|
117
|
+
return { dismounted: false, note: 'it was not mounted' };
|
|
118
|
+
}
|
|
119
|
+
const outcome = await this.deps.port.dismount({ paths: this.paths, userSid: this.deps.userSid() });
|
|
120
|
+
if (!outcome.ok)
|
|
121
|
+
return { dismounted: false, note: outcome.message };
|
|
122
|
+
this.stateFile.update((s) => markMounted(s, this.set, false));
|
|
123
|
+
return { dismounted: true };
|
|
124
|
+
}
|
|
125
|
+
catch (e) {
|
|
126
|
+
return { dismounted: false, note: e instanceof Error ? e.message : String(e) };
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Deletes the image file (what `storage contained off` offers last, after the files were moved out). Only an image
|
|
131
|
+
* that is not mounted and not attached; never through a link. The folders around it stay.
|
|
132
|
+
*/
|
|
133
|
+
async deleteImage() {
|
|
134
|
+
assertTestWritable(this.paths.image);
|
|
135
|
+
const reality = await this.reality();
|
|
136
|
+
if (reality.mountIsMountPoint || reality.attached === true)
|
|
137
|
+
fail('The image is still mounted.', 'Dismount it first (sandbox repair), then try again.');
|
|
138
|
+
assertNoLinks(this.paths.storageRoot, this.paths.image);
|
|
139
|
+
fs.rmSync(this.paths.image, { force: true });
|
|
140
|
+
if (fs.existsSync(this.paths.image))
|
|
141
|
+
fail('The image could not be deleted: ' + this.paths.image, 'Close programs that use it.');
|
|
142
|
+
}
|
|
143
|
+
/** Doctor/repair: the record says mounted, nothing is (clears the record; no permission prompt needed). */
|
|
144
|
+
clearRecord() {
|
|
145
|
+
this.stateFile.update((s) => markMounted(pruneHolders(s, this.set, this.alive), this.set, false));
|
|
146
|
+
}
|
|
147
|
+
requireOk(outcome, what) {
|
|
148
|
+
if (outcome.ok)
|
|
149
|
+
return;
|
|
150
|
+
fail('Could not ' + what + ' the contained storage: ' + outcome.message, 'Run sandbox doctor (it checks the image and the mount folder), or turn contained storage off with: sandbox storage contained off');
|
|
151
|
+
}
|
|
152
|
+
/** A mounted image is another volume than its folder; the same id would mean writes land on this PC's own disk. */
|
|
153
|
+
assertReallyMounted() {
|
|
154
|
+
const deviceOf = this.deps.deviceOf ?? deviceOfDefault;
|
|
155
|
+
if (deviceOf(this.paths.mount) === deviceOf(this.paths.mountsDir))
|
|
156
|
+
fail('The disk image is not mounted at ' + this.paths.mount + ', so nothing was written there.', 'Run sandbox doctor.');
|
|
157
|
+
}
|
|
158
|
+
/** The folders the image needs on this PC. Only folders this call creates are made private, never one with files. */
|
|
159
|
+
ensureFolders() {
|
|
160
|
+
const { paths } = this;
|
|
161
|
+
for (const dir of [paths.imagesDir, paths.mountsDir, paths.mount]) {
|
|
162
|
+
if (fs.existsSync(dir))
|
|
163
|
+
continue;
|
|
164
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
165
|
+
this.deps.protect(dir);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { ContainedPaths } from './layout.js';
|
|
2
|
+
import type { MountReality } from './state.js';
|
|
3
|
+
export interface ScriptOutcome {
|
|
4
|
+
readonly ok: boolean;
|
|
5
|
+
readonly message: string;
|
|
6
|
+
/** mount only: the image did not exist and was made (and formatted) by this run. */
|
|
7
|
+
readonly created: boolean;
|
|
8
|
+
/** mount only: it was mounted before this run. */
|
|
9
|
+
readonly alreadyMounted: boolean;
|
|
10
|
+
readonly notes: readonly string[];
|
|
11
|
+
}
|
|
12
|
+
export interface MountRequest {
|
|
13
|
+
readonly paths: ContainedPaths;
|
|
14
|
+
readonly maxGb: number;
|
|
15
|
+
/** true only the first time: no image file exists yet. The script refuses to create over a file. */
|
|
16
|
+
readonly create: boolean;
|
|
17
|
+
readonly userSid: string;
|
|
18
|
+
}
|
|
19
|
+
export interface DismountRequest {
|
|
20
|
+
readonly paths: ContainedPaths;
|
|
21
|
+
readonly userSid: string;
|
|
22
|
+
}
|
|
23
|
+
export interface DiskPort {
|
|
24
|
+
mount(request: MountRequest): Promise<ScriptOutcome>;
|
|
25
|
+
dismount(request: DismountRequest): Promise<ScriptOutcome>;
|
|
26
|
+
reality(paths: ContainedPaths): Promise<MountReality>;
|
|
27
|
+
}
|
|
28
|
+
export interface RunResult {
|
|
29
|
+
readonly status: number | null;
|
|
30
|
+
readonly stdout: string;
|
|
31
|
+
readonly stderr: string;
|
|
32
|
+
}
|
|
33
|
+
export type Runner = (file: string, args: readonly string[], timeoutMs: number) => Promise<RunResult>;
|
|
34
|
+
export declare const spawnRunner: Runner;
|
|
35
|
+
export declare class ScriptDiskPort implements DiskPort {
|
|
36
|
+
private readonly run;
|
|
37
|
+
constructor(run?: Runner);
|
|
38
|
+
mount(request: MountRequest): Promise<ScriptOutcome>;
|
|
39
|
+
dismount(request: DismountRequest): Promise<ScriptOutcome>;
|
|
40
|
+
reality(paths: ContainedPaths): Promise<MountReality>;
|
|
41
|
+
/** One run of an elevated script: the request goes in a file in a private temp folder, the answer comes back so. */
|
|
42
|
+
private elevated;
|
|
43
|
+
}
|