windows-development-sandbox 1.17.3 → 1.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/config.example.json +97 -4
- package/dist/app/features/accounts/accounts.def.d.ts +116 -2
- package/dist/app/features/accounts/accounts.def.js +80 -12
- package/dist/app/features/accounts/accounts.run.d.ts +29 -0
- package/dist/app/features/accounts/accounts.run.js +36 -6
- package/dist/app/features/accounts/index.d.ts +2 -0
- package/dist/app/features/accounts/index.js +4 -1
- package/dist/app/features/accounts/services.def.d.ts +111 -0
- package/dist/app/features/accounts/services.def.js +187 -0
- package/dist/app/features/accounts/services.run.d.ts +65 -0
- package/dist/app/features/accounts/services.run.js +212 -0
- package/dist/app/features/defs.d.ts +112 -1
- package/dist/app/features/defs.js +5 -0
- package/dist/app/features/settings/categories.def.js +21 -4
- package/dist/app/registry.js +5 -1
- package/dist/bin/agent-sandbox.js +2 -2
- package/dist/bin/{chunk-QLGGAG3D.js → chunk-BKF43GJ3.js} +51 -16
- package/dist/bin/{chunk-REE3EMHD.js → chunk-YD3A67RS.js} +3812 -1702
- package/dist/bin/{chunk-XYUVIVJT.js → chunk-Z5D32FN3.js} +1 -1
- package/dist/bin/claude-host.js +2 -2
- package/dist/bin/claude-sbx.js +2 -2
- package/dist/bin/codex-sbx.js +2 -2
- package/dist/bin/gemini-sbx.js +2 -2
- package/dist/bin/sandbox.js +2 -2
- package/dist/bin/sbx-hide.js +2 -2
- package/dist/bin/sbx-show.js +2 -2
- package/dist/cli/commands/launch/launch.js +30 -1
- package/dist/cli/manifest.d.ts +93 -65
- package/dist/cli/manifest.js +23 -5
- package/dist/cli/menu/pages/accounts-page.js +31 -16
- package/dist/cli/services/accounts-service.d.ts +14 -4
- package/dist/cli/services/accounts-service.js +19 -6
- package/dist/cli/services/data-collect.js +26 -1
- package/dist/core/config/defaults.js +47 -4
- package/dist/core/detect/toolchains.d.ts +15 -1
- package/dist/core/detect/toolchains.js +45 -12
- package/dist/core/local-config/overrides.js +6 -0
- package/dist/core/schema/accounts.d.ts +27 -0
- package/dist/core/schema/accounts.js +32 -0
- package/dist/core/schema/config.d.ts +239 -0
- package/dist/core/schema/config.js +6 -0
- package/dist/core/schema/pc-installs.d.ts +7 -0
- package/dist/core/schema/pc-installs.js +17 -0
- package/dist/core/schema/toolchains.d.ts +61 -0
- package/dist/core/schema/toolchains.js +106 -0
- package/dist/core/types.d.ts +18 -0
- package/dist/guest/accounts/adapter.d.ts +25 -0
- package/dist/guest/accounts/adapter.js +21 -0
- package/dist/guest/accounts/adapters.d.ts +5 -0
- package/dist/guest/accounts/adapters.js +18 -0
- package/dist/guest/accounts/apply.d.ts +41 -0
- package/dist/guest/accounts/apply.js +47 -0
- package/dist/guest/accounts/kinds/appstore.d.ts +2 -0
- package/dist/guest/accounts/kinds/appstore.js +7 -0
- package/dist/guest/accounts/kinds/cfx.d.ts +2 -0
- package/dist/guest/accounts/kinds/cfx.js +7 -0
- package/dist/guest/accounts/kinds/epic.d.ts +2 -0
- package/dist/guest/accounts/kinds/epic.js +26 -0
- package/dist/guest/accounts/kinds/expo.d.ts +2 -0
- package/dist/guest/accounts/kinds/expo.js +7 -0
- package/dist/guest/accounts/kinds/keystore.d.ts +2 -0
- package/dist/guest/accounts/kinds/keystore.js +7 -0
- package/dist/guest/accounts/kinds/play.d.ts +2 -0
- package/dist/guest/accounts/kinds/play.js +7 -0
- package/dist/guest/accounts/kinds/steam.d.ts +4 -0
- package/dist/guest/accounts/kinds/steam.js +17 -0
- package/dist/guest/accounts/kinds/unity.d.ts +2 -0
- package/dist/guest/accounts/kinds/unity.js +7 -0
- package/dist/guest/accounts/watch.d.ts +28 -0
- package/dist/guest/accounts/watch.js +78 -0
- package/dist/guest/apps/resolvers-gamedev.d.ts +2 -0
- package/dist/guest/apps/resolvers-gamedev.js +1 -0
- package/dist/guest/apps/resolvers-mobile.d.ts +2 -0
- package/dist/guest/apps/resolvers-mobile.js +1 -0
- package/dist/guest/apps/resolvers.js +4 -0
- package/dist/guest/boot/boot.d.ts +5 -0
- package/dist/guest/boot/boot.js +35 -1
- package/dist/guest/daemon/accounts.d.ts +3 -0
- package/dist/guest/daemon/accounts.js +25 -0
- package/dist/guest/daemon/daemon.js +2 -0
- package/dist/guest/daemon/idle-shutdown.js +3 -1
- package/dist/guest/daemon/sessions.js +2 -0
- package/dist/guest/devtools/android.d.ts +2 -0
- package/dist/guest/devtools/android.js +7 -0
- package/dist/guest/devtools/devtool.d.ts +9 -0
- package/dist/guest/devtools/devtool.js +5 -0
- package/dist/guest/devtools/fivem.d.ts +2 -0
- package/dist/guest/devtools/fivem.js +7 -0
- package/dist/guest/devtools/registry.d.ts +6 -0
- package/dist/guest/devtools/registry.js +12 -0
- package/dist/guest/devtools/steam.d.ts +2 -0
- package/dist/guest/devtools/steam.js +228 -0
- package/dist/guest/devtools/unity.d.ts +2 -0
- package/dist/guest/devtools/unity.js +7 -0
- package/dist/guest/entries/daemon.js +2 -0
- package/dist/guest/entries/tool.d.ts +1 -0
- package/dist/guest/entries/tool.js +17 -0
- package/dist/guest/toolchains/engines/godot.d.ts +4 -0
- package/dist/guest/toolchains/engines/godot.js +4 -0
- package/dist/guest/toolchains/engines/unity-release.d.ts +15 -0
- package/dist/guest/toolchains/engines/unity-release.js +6 -0
- package/dist/guest/toolchains/engines/unity.d.ts +4 -0
- package/dist/guest/toolchains/engines/unity.js +4 -0
- package/dist/guest/toolchains/engines/unreal-engines.d.ts +4 -0
- package/dist/guest/toolchains/engines/unreal-engines.js +4 -0
- package/dist/guest/toolchains/fivem/artifacts.d.ts +6 -0
- package/dist/guest/toolchains/fivem/artifacts.js +7 -0
- package/dist/guest/toolchains/fivem/fivem.d.ts +4 -0
- package/dist/guest/toolchains/fivem/fivem.js +4 -0
- package/dist/guest/toolchains/installers.d.ts +12 -0
- package/dist/guest/toolchains/installers.js +39 -0
- package/dist/guest/toolchains/languages/java.js +1 -1
- package/dist/guest/toolchains/languages/kotlin.d.ts +3 -0
- package/dist/guest/toolchains/languages/kotlin.js +6 -0
- package/dist/guest/toolchains/mobile/android-repo.d.ts +11 -0
- package/dist/guest/toolchains/mobile/android-repo.js +10 -0
- package/dist/guest/toolchains/mobile/android.d.ts +17 -0
- package/dist/guest/toolchains/mobile/android.js +16 -0
- package/dist/guest/toolchains/mobile/dart.d.ts +4 -0
- package/dist/guest/toolchains/mobile/dart.js +4 -0
- package/dist/guest/toolchains/mobile/flutter.d.ts +4 -0
- package/dist/guest/toolchains/mobile/flutter.js +4 -0
- package/dist/guest/toolchains/mobile/react-native.d.ts +4 -0
- package/dist/guest/toolchains/mobile/react-native.js +4 -0
- package/dist/guest/toolchains/native/unreal.d.ts +3 -7
- package/dist/guest/toolchains/native/unreal.js +16 -167
- package/dist/guest/toolchains/native/visual-studio.d.ts +15 -0
- package/dist/guest/toolchains/native/visual-studio.js +159 -0
- package/dist/guest/toolchains/native/vs-request.d.ts +19 -0
- package/dist/guest/toolchains/native/vs-request.js +46 -0
- package/dist/guest/toolchains/not-yet.d.ts +2 -0
- package/dist/guest/toolchains/not-yet.js +6 -0
- package/dist/guest/toolchains/persisted.d.ts +48 -0
- package/dist/guest/toolchains/persisted.js +159 -0
- package/dist/guest/toolchains/plan.js +80 -4
- package/dist/guest/toolchains/steam/apps.d.ts +27 -0
- package/dist/guest/toolchains/steam/apps.js +112 -0
- package/dist/guest/toolchains/steam/client.d.ts +5 -0
- package/dist/guest/toolchains/steam/client.js +67 -0
- package/dist/guest/toolchains/steam/commands.d.ts +58 -0
- package/dist/guest/toolchains/steam/commands.js +155 -0
- package/dist/guest/toolchains/steam/steam.d.ts +4 -0
- package/dist/guest/toolchains/steam/steam.js +61 -0
- package/dist/guest/toolchains/steam/steamcmd-install.d.ts +29 -0
- package/dist/guest/toolchains/steam/steamcmd-install.js +177 -0
- package/dist/guest/toolchains/steam/steamcmd.d.ts +6 -0
- package/dist/guest/toolchains/steam/steamcmd.js +23 -0
- package/dist/guest/toolchains/step.d.ts +10 -0
- package/dist/host/accounts/deliver.d.ts +26 -0
- package/dist/host/accounts/deliver.js +80 -0
- package/dist/host/accounts/handoff.d.ts +17 -0
- package/dist/host/accounts/handoff.js +110 -0
- package/dist/host/accounts/index.d.ts +6 -0
- package/dist/host/accounts/index.js +8 -0
- package/dist/host/accounts/kinds/appstore.d.ts +2 -0
- package/dist/host/accounts/kinds/appstore.js +10 -0
- package/dist/host/accounts/kinds/cfx.d.ts +2 -0
- package/dist/host/accounts/kinds/cfx.js +7 -0
- package/dist/host/accounts/kinds/epic.d.ts +2 -0
- package/dist/host/accounts/kinds/epic.js +15 -0
- package/dist/host/accounts/kinds/expo.d.ts +2 -0
- package/dist/host/accounts/kinds/expo.js +7 -0
- package/dist/host/accounts/kinds/keystore.d.ts +2 -0
- package/dist/host/accounts/kinds/keystore.js +7 -0
- package/dist/host/accounts/kinds/play.d.ts +2 -0
- package/dist/host/accounts/kinds/play.js +7 -0
- package/dist/host/accounts/kinds/steam.d.ts +6 -0
- package/dist/host/accounts/kinds/steam.js +31 -0
- package/dist/host/accounts/kinds/unity.d.ts +2 -0
- package/dist/host/accounts/kinds/unity.js +13 -0
- package/dist/host/accounts/leases.d.ts +40 -0
- package/dist/host/accounts/leases.js +62 -0
- package/dist/host/accounts/source.d.ts +21 -0
- package/dist/host/accounts/source.js +15 -0
- package/dist/host/accounts/sources.d.ts +5 -0
- package/dist/host/accounts/sources.js +18 -0
- package/dist/host/accounts/store.d.ts +23 -0
- package/dist/host/accounts/store.js +74 -0
- package/dist/host/audit/log.d.ts +1 -1
- package/dist/host/launch/generate.d.ts +2 -0
- package/dist/host/launch/generate.js +16 -1
- package/dist/host/launch/hints/android.d.ts +3 -0
- package/dist/host/launch/hints/android.js +3 -0
- package/dist/host/launch/hints/engines.d.ts +3 -0
- package/dist/host/launch/hints/engines.js +3 -0
- package/dist/host/launch/hints/fivem.d.ts +3 -0
- package/dist/host/launch/hints/fivem.js +3 -0
- package/dist/host/launch/hints/hint.d.ts +2 -0
- package/dist/host/launch/hints/hint.js +1 -0
- package/dist/host/launch/hints/index.d.ts +5 -0
- package/dist/host/launch/hints/index.js +29 -0
- package/dist/host/launch/hints/mobile.d.ts +3 -0
- package/dist/host/launch/hints/mobile.js +3 -0
- package/dist/host/launch/hints/steam.d.ts +7 -0
- package/dist/host/launch/hints/steam.js +59 -0
- package/dist/host/launch/index.d.ts +2 -2
- package/dist/host/launch/index.js +2 -2
- package/dist/host/launch/mappings.js +2 -0
- package/dist/host/launch/session-file.js +2 -0
- package/dist/host/launch/stage.js +10 -1
- package/dist/host/launch/toolchains.d.ts +7 -0
- package/dist/host/launch/toolchains.js +11 -0
- package/dist/host/pc-installs/discover.d.ts +19 -0
- package/dist/host/pc-installs/discover.js +59 -0
- package/dist/host/pc-installs/epic.d.ts +2 -0
- package/dist/host/pc-installs/epic.js +7 -0
- package/dist/host/pc-installs/index.d.ts +3 -0
- package/dist/host/pc-installs/index.js +2 -0
- package/dist/host/pc-installs/source.d.ts +9 -0
- package/dist/host/pc-installs/source.js +1 -0
- package/dist/host/pc-installs/steam.d.ts +16 -0
- package/dist/host/pc-installs/steam.js +143 -0
- package/dist/host/pc-installs/unity.d.ts +2 -0
- package/dist/host/pc-installs/unity.js +7 -0
- package/dist/host/pc-installs/unreal.d.ts +2 -0
- package/dist/host/pc-installs/unreal.js +7 -0
- package/dist/host/profile/accounts.d.ts +21 -6
- package/dist/host/profile/accounts.js +108 -28
- package/dist/host/profile/claude-login.d.ts +32 -2
- package/dist/host/profile/claude-login.js +100 -8
- package/dist/host/profile/index.d.ts +1 -1
- package/dist/host/profile/index.js +1 -1
- package/dist/host/secrets/launch-secrets.js +1 -0
- package/dist/runtime/agent.js +321 -9
- package/dist/runtime/daemon.js +1987 -604
- package/dist/runtime/db.js +334 -11
- package/dist/runtime/main.js +2519 -723
- package/dist/runtime/netproxy.js +321 -9
- package/dist/runtime/tool.js +28549 -0
- package/dist/runtime/view.js +321 -9
- package/dist/selftest/accounts.test.js +172 -1
- package/dist/selftest/agent-paths.test.js +1 -0
- package/dist/selftest/android.test.d.ts +2 -0
- package/dist/selftest/android.test.js +7 -0
- package/dist/selftest/app.test.js +1 -1
- package/dist/selftest/engines.test.d.ts +2 -0
- package/dist/selftest/engines.test.js +7 -0
- package/dist/selftest/fivem.test.d.ts +2 -0
- package/dist/selftest/fivem.test.js +7 -0
- package/dist/selftest/generate.test.js +1 -1
- package/dist/selftest/index.js +18 -0
- package/dist/selftest/menu-screens.js +1 -0
- package/dist/selftest/menu.test.js +2 -1
- package/dist/selftest/mobile.test.d.ts +2 -0
- package/dist/selftest/mobile.test.js +7 -0
- package/dist/selftest/pc-installs.test.d.ts +2 -0
- package/dist/selftest/pc-installs.test.js +111 -0
- package/dist/selftest/persisted-installs.test.d.ts +2 -0
- package/dist/selftest/persisted-installs.test.js +107 -0
- package/dist/selftest/projects.test.js +6 -4
- package/dist/selftest/service-accounts.test.d.ts +2 -0
- package/dist/selftest/service-accounts.test.js +320 -0
- package/dist/selftest/steam.test.d.ts +2 -0
- package/dist/selftest/steam.test.js +316 -0
- package/dist/selftest/storage-helper.test.js +31 -8
- package/dist/selftest/support/engine-golden.js +3 -0
- package/dist/selftest/support/fixtures.d.ts +87 -0
- package/dist/selftest/support/fixtures.js +4 -3
- package/dist/selftest/support/pc-memory.d.ts +2 -0
- package/dist/selftest/support/pc-memory.js +17 -0
- package/dist/selftest/support/web-preview.js +20 -1
- package/dist/selftest/unity.test.d.ts +2 -0
- package/dist/selftest/unity.test.js +7 -0
- package/dist/shared/accounts/fingerprint.d.ts +1 -0
- package/dist/shared/accounts/fingerprint.js +20 -0
- package/dist/shared/accounts/index.d.ts +5 -0
- package/dist/shared/accounts/index.js +7 -0
- package/dist/shared/accounts/kinds/epic.d.ts +10 -0
- package/dist/shared/accounts/kinds/epic.js +56 -0
- package/dist/shared/accounts/kinds/steam.d.ts +28 -0
- package/dist/shared/accounts/kinds/steam.js +99 -0
- package/dist/shared/accounts/kinds.d.ts +41 -0
- package/dist/shared/accounts/kinds.js +154 -0
- package/dist/shared/accounts/payloads.d.ts +151 -0
- package/dist/shared/accounts/payloads.js +128 -0
- package/dist/shared/apps/catalogue-gamedev.d.ts +2 -0
- package/dist/shared/apps/catalogue-gamedev.js +1 -0
- package/dist/shared/apps/catalogue-mobile.d.ts +2 -0
- package/dist/shared/apps/catalogue-mobile.js +1 -0
- package/dist/shared/apps/catalogue-modding.d.ts +2 -0
- package/dist/shared/apps/catalogue-modding.js +1 -0
- package/dist/shared/apps/catalogue.d.ts +1 -0
- package/dist/shared/apps/catalogue.js +6 -1
- package/dist/shared/channel/schema.d.ts +183 -0
- package/dist/shared/channel/secrets.d.ts +42 -0
- package/dist/shared/channel/secrets.js +3 -0
- package/dist/shared/download/downloader.d.ts +7 -0
- package/dist/shared/download/downloader.js +10 -3
- package/dist/shared/installs.d.ts +19 -0
- package/dist/shared/installs.js +13 -0
- package/dist/shared/io/bounded.d.ts +6 -0
- package/dist/shared/io/bounded.js +38 -0
- package/dist/shared/io/index.d.ts +2 -0
- package/dist/shared/io/index.js +2 -0
- package/dist/shared/io/text.d.ts +9 -0
- package/dist/shared/io/text.js +18 -0
- package/dist/shared/paths/guest-paths.js +1 -1
- package/dist/shared/paths/index.d.ts +1 -0
- package/dist/shared/paths/index.js +1 -0
- package/dist/shared/paths/pc-installs.d.ts +20 -0
- package/dist/shared/paths/pc-installs.js +19 -0
- package/dist/shared/vdf.d.ts +25 -0
- package/dist/shared/vdf.js +151 -0
- package/dist/sync/sync-cycle.js +9 -0
- package/dist/web/index.html +1 -1
- package/dist/web/{main-JTH3FT4O.js → main-CWIF4VQI.js} +63 -61
- package/native/csharp/HcsHost/AssemblyInfo.cs +3 -3
- package/native/csharp/HostTools/AssemblyInfo.cs +3 -3
- package/native/csharp/StorageHelper/AssemblyInfo.cs +3 -3
- package/package.json +1 -1
- package/sandbox/scripts/add-firewall-port.ps1 +13 -0
- package/sandbox/templates/fivem/README.txt +2 -0
- package/schemas/config.schema.json +485 -0
- package/schemas/sandbox-config.schema.json +416 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// What a sandbox hands back: a sign-in it renewed (or one you made there), left in its data folder as
|
|
2
|
+
// state\accounts\<kind>.json. Taken on every sync cycle: sealed into the store when the kind returns, the launch holds
|
|
3
|
+
// its lease and the file is a plain file within the size limit of the right shape; deleted unsealed otherwise. The
|
|
4
|
+
// last sync of a sandbox releases its leases. The state folder is found through hostDataFolder (contained storage
|
|
5
|
+
// keeps it in the image).
|
|
6
|
+
import fs from 'node:fs';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import { assertNoLinks, isLink } from '../../core/util/index.js';
|
|
9
|
+
import { HANDOFF_PARTS, handoffSchema, SERVICE_KINDS, SERVICE_SPECS, } from '../../shared/accounts/index.js';
|
|
10
|
+
import { readBoundedFile } from '../../shared/io/index.js';
|
|
11
|
+
import { hostDataFolder, hostDataRoot, setNameOf, storageOf } from '../storage/index.js';
|
|
12
|
+
import { AccountLeases } from './leases.js';
|
|
13
|
+
import { HOST_SOURCES } from './sources.js';
|
|
14
|
+
import { ServiceAccountStore } from './store.js';
|
|
15
|
+
/** Room for the handoff's own fields around the payload. */
|
|
16
|
+
const ENVELOPE_BYTES = 1024;
|
|
17
|
+
/** state\accounts in a data set, as this PC sees it. */
|
|
18
|
+
export function handoffDir(dataPath) {
|
|
19
|
+
return path.join(hostDataFolder(dataPath, HANDOFF_PARTS[0]), HANDOFF_PARTS[1]);
|
|
20
|
+
}
|
|
21
|
+
function exists(file) {
|
|
22
|
+
try {
|
|
23
|
+
fs.lstatSync(file);
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Removes a handoff file without following anything the sandbox planted: a link is removed itself, and nothing is
|
|
32
|
+
* touched when a folder on the way is a link. False when it could not be removed.
|
|
33
|
+
*/
|
|
34
|
+
function drop(root, file) {
|
|
35
|
+
try {
|
|
36
|
+
if (isLink(file)) {
|
|
37
|
+
fs.unlinkSync(file);
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
assertNoLinks(root, file);
|
|
41
|
+
fs.rmSync(file, { force: true });
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
export function takeHandoffs(dataPath, final, store = new ServiceAccountStore(storageOf(dataPath)), leases = new AccountLeases(storageOf(dataPath))) {
|
|
49
|
+
const taken = [];
|
|
50
|
+
const dropped = [];
|
|
51
|
+
const root = hostDataRoot(dataPath);
|
|
52
|
+
const dir = handoffDir(dataPath);
|
|
53
|
+
const dataSet = setNameOf(dataPath);
|
|
54
|
+
for (const kind of SERVICE_KINDS) {
|
|
55
|
+
const file = path.join(dir, kind + '.json');
|
|
56
|
+
if (!exists(file))
|
|
57
|
+
continue;
|
|
58
|
+
let ok = false;
|
|
59
|
+
try {
|
|
60
|
+
assertNoLinks(root, file);
|
|
61
|
+
const spec = SERVICE_SPECS[kind];
|
|
62
|
+
const bytes = readBoundedFile(file, spec.maxBytes + ENVELOPE_BYTES);
|
|
63
|
+
const parsed = bytes ? handoffSchema(kind).safeParse(JSON.parse(bytes.toString('utf8'))) : null;
|
|
64
|
+
const lease = leases.holder(kind);
|
|
65
|
+
if (parsed?.success &&
|
|
66
|
+
spec.returns &&
|
|
67
|
+
lease &&
|
|
68
|
+
lease.dataSet === dataSet &&
|
|
69
|
+
lease.launchId === parsed.data.launchId) {
|
|
70
|
+
const payload = parsed.data.payload;
|
|
71
|
+
store.save(kind, payload, 'sandbox', HOST_SOURCES[kind].summary(payload));
|
|
72
|
+
ok = true;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
// A link on the way, not JSON, or a store that cannot be written: dropped below, the saved one stays.
|
|
77
|
+
}
|
|
78
|
+
drop(root, file);
|
|
79
|
+
(ok ? taken : dropped).push(kind);
|
|
80
|
+
}
|
|
81
|
+
if (final)
|
|
82
|
+
leases.releaseAll(dataSet);
|
|
83
|
+
return { taken, dropped };
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Forgets a sign-in everywhere: the saved one, its lease (what a running sandbox hands back afterwards is refused),
|
|
87
|
+
* and every copy a sandbox left in a data set and the PC has not taken yet.
|
|
88
|
+
*/
|
|
89
|
+
export function forgetEverywhere(kind, storageRoot, store = new ServiceAccountStore(storageRoot), leases = new AccountLeases(storageRoot)) {
|
|
90
|
+
const removed = store.remove(kind);
|
|
91
|
+
leases.revoke(kind);
|
|
92
|
+
let pendingDeleted = 0;
|
|
93
|
+
const sets = path.join(storageRoot, 'data');
|
|
94
|
+
let names = [];
|
|
95
|
+
try {
|
|
96
|
+
names = fs.readdirSync(sets);
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
// No data sets yet.
|
|
100
|
+
}
|
|
101
|
+
for (const name of names) {
|
|
102
|
+
const dataPath = path.join(sets, name);
|
|
103
|
+
if (isLink(dataPath))
|
|
104
|
+
continue;
|
|
105
|
+
const file = path.join(handoffDir(dataPath), kind + '.json');
|
|
106
|
+
if (exists(file) && drop(hostDataRoot(dataPath), file))
|
|
107
|
+
pendingDeleted++;
|
|
108
|
+
}
|
|
109
|
+
return { removed, pendingDeleted };
|
|
110
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { accountsDir, ServiceAccountStore, type AccountInfo, type AccountState } from './store.js';
|
|
2
|
+
export { AccountLeases, type ClaimResult, type Lease } from './leases.js';
|
|
3
|
+
export { prepareServiceAccounts, type AccountDelivery, type DeliveryInput } from './deliver.js';
|
|
4
|
+
export { forgetEverywhere, handoffDir, takeHandoffs } from './handoff.js';
|
|
5
|
+
export { notAvailableYet, pcEnv, type HostSource, type PcEnv } from './source.js';
|
|
6
|
+
export { HOST_SOURCES } from './sources.js';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Service sign-ins kept for sandboxes (accounts.<kind>.share): the sealed store, the one-holder leases, what a launch
|
|
2
|
+
// hands over, what a sandbox hands back, and where each kind's sign-in comes from on this PC.
|
|
3
|
+
export { accountsDir, ServiceAccountStore } from './store.js';
|
|
4
|
+
export { AccountLeases } from './leases.js';
|
|
5
|
+
export { prepareServiceAccounts } from './deliver.js';
|
|
6
|
+
export { forgetEverywhere, handoffDir, takeHandoffs } from './handoff.js';
|
|
7
|
+
export { notAvailableYet, pcEnv } from './source.js';
|
|
8
|
+
export { HOST_SOURCES } from './sources.js';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Epic Games: this PC's launcher sign-in ("Remember me" in its GameUserSettings.ini, often UTF-16). Taking it signs
|
|
2
|
+
// the launcher on this PC out the first time a sandbox uses it (a sign-in works in one place at a time).
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { epicSettingsFile, MAX_EPIC_INI_BYTES, readEpicLogin, } from '../../../shared/accounts/index.js';
|
|
5
|
+
import { decodeText, readBoundedFile } from '../../../shared/io/index.js';
|
|
6
|
+
export const epicSource = {
|
|
7
|
+
fromPc(pc, from) {
|
|
8
|
+
const localAppData = from ? path.resolve(from) : pc.localAppData;
|
|
9
|
+
if (!localAppData)
|
|
10
|
+
return null;
|
|
11
|
+
const bytes = readBoundedFile(epicSettingsFile(localAppData), MAX_EPIC_INI_BYTES);
|
|
12
|
+
return bytes ? readEpicLogin(decodeText(bytes).text) : null;
|
|
13
|
+
},
|
|
14
|
+
summary: () => null,
|
|
15
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { HostSource } from '../source.js';
|
|
2
|
+
/** Where SteamCMD usually is on a PC (its own folder: it has no installer). */
|
|
3
|
+
export declare const PC_STEAMCMD_DIR = "C:\\steamcmd";
|
|
4
|
+
/** The SteamCMD folder a --from path names: the folder, its steamcmd.exe, or its config\config.vdf. */
|
|
5
|
+
export declare function steamCmdFolderOf(from: string): string;
|
|
6
|
+
export declare const steamSource: HostSource<'steam'>;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Steam: SteamCMD's cached login (config\config.vdf and the ssfn sentry files) from a steamcmd folder on this PC
|
|
2
|
+
// (`sandbox account import steam --from C:\steamcmd`); no password is ever kept. `--from` may also name steamcmd.exe
|
|
3
|
+
// or the config.vdf itself. Without it, C:\steamcmd is looked in.
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { readSteamCmdLogin } from '../../../shared/accounts/index.js';
|
|
7
|
+
/** Where SteamCMD usually is on a PC (its own folder: it has no installer). */
|
|
8
|
+
export const PC_STEAMCMD_DIR = 'C:\\steamcmd';
|
|
9
|
+
/** The SteamCMD folder a --from path names: the folder, its steamcmd.exe, or its config\config.vdf. */
|
|
10
|
+
export function steamCmdFolderOf(from) {
|
|
11
|
+
const full = path.resolve(from);
|
|
12
|
+
let isFile = false;
|
|
13
|
+
try {
|
|
14
|
+
isFile = fs.statSync(full).isFile();
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return full;
|
|
18
|
+
}
|
|
19
|
+
if (!isFile)
|
|
20
|
+
return full;
|
|
21
|
+
const dir = path.dirname(full);
|
|
22
|
+
return path.basename(full).toLowerCase() === 'config.vdf' && path.basename(dir).toLowerCase() === 'config'
|
|
23
|
+
? path.dirname(dir)
|
|
24
|
+
: dir;
|
|
25
|
+
}
|
|
26
|
+
export const steamSource = {
|
|
27
|
+
fromPc(_pc, from) {
|
|
28
|
+
return readSteamCmdLogin(from ? steamCmdFolderOf(from) : PC_STEAMCMD_DIR);
|
|
29
|
+
},
|
|
30
|
+
summary: (p) => p.account,
|
|
31
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { notAvailableYet } from '../source.js';
|
|
2
|
+
export const unitySource = {
|
|
3
|
+
fromPc(_pc, _from) {
|
|
4
|
+
return notAvailableYet('unity');
|
|
5
|
+
},
|
|
6
|
+
fromFile(_bytes, _answers) {
|
|
7
|
+
return notAvailableYet('unity');
|
|
8
|
+
},
|
|
9
|
+
fromAnswers(_answers) {
|
|
10
|
+
return notAvailableYet('unity');
|
|
11
|
+
},
|
|
12
|
+
summary: () => null,
|
|
13
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { type ServiceKind } from '../../shared/accounts/index.js';
|
|
3
|
+
declare const LeaseSchema: z.ZodObject<{
|
|
4
|
+
kind: z.ZodEnum<{
|
|
5
|
+
unity: "unity";
|
|
6
|
+
steam: "steam";
|
|
7
|
+
epic: "epic";
|
|
8
|
+
cfx: "cfx";
|
|
9
|
+
keystore: "keystore";
|
|
10
|
+
play: "play";
|
|
11
|
+
expo: "expo";
|
|
12
|
+
appstore: "appstore";
|
|
13
|
+
}>;
|
|
14
|
+
dataSet: z.ZodString;
|
|
15
|
+
launchId: z.ZodString;
|
|
16
|
+
issuedAt: z.ZodString;
|
|
17
|
+
delivered: z.ZodNullable<z.ZodString>;
|
|
18
|
+
}, z.core.$strip>;
|
|
19
|
+
export type Lease = Readonly<z.output<typeof LeaseSchema>>;
|
|
20
|
+
export type ClaimResult = {
|
|
21
|
+
ok: true;
|
|
22
|
+
} | {
|
|
23
|
+
ok: false;
|
|
24
|
+
heldBy: string;
|
|
25
|
+
};
|
|
26
|
+
export declare class AccountLeases {
|
|
27
|
+
private readonly file;
|
|
28
|
+
constructor(storageRoot: string);
|
|
29
|
+
private all;
|
|
30
|
+
private keep;
|
|
31
|
+
holder(kind: ServiceKind): Lease | null;
|
|
32
|
+
/** The lease for this data set and launch, unless another data set's sandbox still runs with it. */
|
|
33
|
+
claim(kind: ServiceKind, dataSet: string, launchId: string, delivered: string | null, isRunning: (dataSet: string) => boolean): ClaimResult;
|
|
34
|
+
release(kind: ServiceKind, dataSet: string): void;
|
|
35
|
+
/** Every lease of a data set (its sandbox stopped). */
|
|
36
|
+
releaseAll(dataSet: string): void;
|
|
37
|
+
/** Drops the kind's lease (you forgot the sign-in): what a sandbox hands back afterwards is refused. */
|
|
38
|
+
revoke(kind: ServiceKind): Lease[];
|
|
39
|
+
}
|
|
40
|
+
export {};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// Which data set holds a one-holder sign-in (Epic, Steam, Unity work in one place at a time), in host-only storage
|
|
2
|
+
// (<storage>\accounts\leases.json). A launch claims the lease even when nothing is saved yet, so a first sign-in made
|
|
3
|
+
// in that sandbox can come back; a lease whose sandbox no longer runs is stale and is taken over.
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
import { JsonFile } from '../../shared/io/index.js';
|
|
7
|
+
import { SERVICE_KINDS } from '../../shared/accounts/index.js';
|
|
8
|
+
import { accountsDir } from './store.js';
|
|
9
|
+
const LeaseSchema = z.object({
|
|
10
|
+
kind: z.enum(SERVICE_KINDS),
|
|
11
|
+
dataSet: z.string().min(1).max(200),
|
|
12
|
+
launchId: z.string().min(1).max(64),
|
|
13
|
+
issuedAt: z.string().max(64),
|
|
14
|
+
/** The fingerprint of what was handed over (null: nothing saved yet). */
|
|
15
|
+
delivered: z.string().max(128).nullable(),
|
|
16
|
+
});
|
|
17
|
+
const LeasesSchema = z.object({ leases: z.array(LeaseSchema).max(SERVICE_KINDS.length) });
|
|
18
|
+
export class AccountLeases {
|
|
19
|
+
file;
|
|
20
|
+
constructor(storageRoot) {
|
|
21
|
+
this.file = new JsonFile(LeasesSchema, path.join(accountsDir(storageRoot), 'leases.json'));
|
|
22
|
+
}
|
|
23
|
+
all() {
|
|
24
|
+
return this.file.read()?.leases ?? [];
|
|
25
|
+
}
|
|
26
|
+
keep(leases) {
|
|
27
|
+
this.file.write({ leases: [...leases] });
|
|
28
|
+
}
|
|
29
|
+
holder(kind) {
|
|
30
|
+
return this.all().find((l) => l.kind === kind) ?? null;
|
|
31
|
+
}
|
|
32
|
+
/** The lease for this data set and launch, unless another data set's sandbox still runs with it. */
|
|
33
|
+
claim(kind, dataSet, launchId, delivered, isRunning) {
|
|
34
|
+
const held = this.holder(kind);
|
|
35
|
+
if (held && held.dataSet !== dataSet && isRunning(held.dataSet))
|
|
36
|
+
return { ok: false, heldBy: held.dataSet };
|
|
37
|
+
const lease = { kind, dataSet, launchId, issuedAt: new Date().toISOString(), delivered };
|
|
38
|
+
this.keep([...this.all().filter((l) => l.kind !== kind), lease]);
|
|
39
|
+
return { ok: true };
|
|
40
|
+
}
|
|
41
|
+
release(kind, dataSet) {
|
|
42
|
+
const all = this.all();
|
|
43
|
+
const kept = all.filter((l) => !(l.kind === kind && l.dataSet === dataSet));
|
|
44
|
+
if (kept.length !== all.length)
|
|
45
|
+
this.keep(kept);
|
|
46
|
+
}
|
|
47
|
+
/** Every lease of a data set (its sandbox stopped). */
|
|
48
|
+
releaseAll(dataSet) {
|
|
49
|
+
const all = this.all();
|
|
50
|
+
const kept = all.filter((l) => l.dataSet !== dataSet);
|
|
51
|
+
if (kept.length !== all.length)
|
|
52
|
+
this.keep(kept);
|
|
53
|
+
}
|
|
54
|
+
/** Drops the kind's lease (you forgot the sign-in): what a sandbox hands back afterwards is refused. */
|
|
55
|
+
revoke(kind) {
|
|
56
|
+
const all = this.all();
|
|
57
|
+
const gone = all.filter((l) => l.kind === kind);
|
|
58
|
+
if (gone.length)
|
|
59
|
+
this.keep(all.filter((l) => l.kind !== kind));
|
|
60
|
+
return gone;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Payload, ServiceKind } from '../../shared/accounts/index.js';
|
|
2
|
+
/** This PC's profile folders (passed in, so the self-test uses its own). */
|
|
3
|
+
export interface PcEnv {
|
|
4
|
+
readonly localAppData: string;
|
|
5
|
+
readonly appData: string;
|
|
6
|
+
readonly programData: string;
|
|
7
|
+
}
|
|
8
|
+
export interface HostSource<K extends ServiceKind> {
|
|
9
|
+
/** This PC's program's sign-in (`from`: another folder to read it from), or null when it has none. */
|
|
10
|
+
fromPc?(pc: PcEnv, from?: string): Payload<K> | null;
|
|
11
|
+
/** A file you named (its bytes, bounded by the caller) and the answers to the kind's fields. */
|
|
12
|
+
fromFile?(bytes: Buffer, answers: Readonly<Record<string, string>>): Payload<K>;
|
|
13
|
+
/** The answers to the kind's fields. */
|
|
14
|
+
fromAnswers?(answers: Readonly<Record<string, string>>): Payload<K>;
|
|
15
|
+
/** A short summary for the status (e.g. the Steam account name); never the secret. */
|
|
16
|
+
summary(p: Payload<K>): string | null;
|
|
17
|
+
}
|
|
18
|
+
/** This PC's folders from the environment (never HOME or USERPROFILE). */
|
|
19
|
+
export declare function pcEnv(): PcEnv;
|
|
20
|
+
/** What a kind whose host side is not written yet says. */
|
|
21
|
+
export declare function notAvailableYet(kind: ServiceKind): never;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { fail } from '../../core/util/index.js';
|
|
2
|
+
import { SERVICE_SPECS } from '../../shared/accounts/index.js';
|
|
3
|
+
/** This PC's folders from the environment (never HOME or USERPROFILE). */
|
|
4
|
+
export function pcEnv() {
|
|
5
|
+
const env = process.env;
|
|
6
|
+
return {
|
|
7
|
+
localAppData: env.LOCALAPPDATA ?? '',
|
|
8
|
+
appData: env.APPDATA ?? '',
|
|
9
|
+
programData: env.ProgramData ?? env.PROGRAMDATA ?? 'C:\\ProgramData',
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
/** What a kind whose host side is not written yet says. */
|
|
13
|
+
export function notAvailableYet(kind) {
|
|
14
|
+
return fail('Saving a ' + SERVICE_SPECS[kind].label + ' sign-in is not available yet.', 'It comes in a later version of the sandbox.');
|
|
15
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { appstoreSource } from './kinds/appstore.js';
|
|
2
|
+
import { cfxSource } from './kinds/cfx.js';
|
|
3
|
+
import { epicSource } from './kinds/epic.js';
|
|
4
|
+
import { expoSource } from './kinds/expo.js';
|
|
5
|
+
import { keystoreSource } from './kinds/keystore.js';
|
|
6
|
+
import { playSource } from './kinds/play.js';
|
|
7
|
+
import { steamSource } from './kinds/steam.js';
|
|
8
|
+
import { unitySource } from './kinds/unity.js';
|
|
9
|
+
export const HOST_SOURCES = {
|
|
10
|
+
epic: epicSource,
|
|
11
|
+
steam: steamSource,
|
|
12
|
+
unity: unitySource,
|
|
13
|
+
cfx: cfxSource,
|
|
14
|
+
keystore: keystoreSource,
|
|
15
|
+
play: playSource,
|
|
16
|
+
expo: expoSource,
|
|
17
|
+
appstore: appstoreSource,
|
|
18
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type AccountSource, type Payload, type ServiceKind } from '../../shared/accounts/index.js';
|
|
2
|
+
import { SecretStore } from '../secrets/index.js';
|
|
3
|
+
export type AccountState = 'none' | 'saved' | 'unreadable';
|
|
4
|
+
export interface AccountInfo {
|
|
5
|
+
readonly savedAt: string;
|
|
6
|
+
readonly source: AccountSource;
|
|
7
|
+
readonly summary: string | null;
|
|
8
|
+
}
|
|
9
|
+
/** The folder of the store, the leases and nothing else (host-only storage, never mapped). */
|
|
10
|
+
export declare function accountsDir(storageRoot: string): string;
|
|
11
|
+
export declare class ServiceAccountStore {
|
|
12
|
+
private readonly storageRoot;
|
|
13
|
+
private readonly secrets;
|
|
14
|
+
constructor(storageRoot: string, secrets?: SecretStore);
|
|
15
|
+
private file;
|
|
16
|
+
/** Whether a sign-in is saved and can be opened by this Windows user (it is never returned here). */
|
|
17
|
+
state(kind: ServiceKind): AccountState;
|
|
18
|
+
info(kind: ServiceKind): AccountInfo | null;
|
|
19
|
+
/** The sign-in, decrypted. Only the launch calls this; throws when this Windows user cannot open it. */
|
|
20
|
+
read<K extends ServiceKind>(kind: K): Payload<K> | null;
|
|
21
|
+
save<K extends ServiceKind>(kind: K, payload: Payload<K>, source: AccountSource, summary?: string | null): void;
|
|
22
|
+
remove(kind: ServiceKind): boolean;
|
|
23
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// Your saved service sign-ins for sandboxes (accounts.<kind>.share), one file per kind in host-only storage
|
|
2
|
+
// (<storage>\accounts\<kind>.json): when and where it was saved, a short summary (an account name, never a secret) and
|
|
3
|
+
// the sign-in sealed with Windows DPAPI. Never printed, logged or returned to a surface: only the launch reads it.
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
import { JsonFile } from '../../shared/io/index.js';
|
|
8
|
+
import { ACCOUNT_SOURCES, PAYLOADS, SERVICE_SPECS, } from '../../shared/accounts/index.js';
|
|
9
|
+
import { fail } from '../../core/util/index.js';
|
|
10
|
+
import { SecretStore } from '../secrets/index.js';
|
|
11
|
+
const SavedSchema = z.object({
|
|
12
|
+
savedAt: z.string().max(64),
|
|
13
|
+
source: z.enum(ACCOUNT_SOURCES),
|
|
14
|
+
summary: z.string().max(200).nullable(),
|
|
15
|
+
sealed: z.string().max(4 * 1024 * 1024),
|
|
16
|
+
});
|
|
17
|
+
/** The folder of the store, the leases and nothing else (host-only storage, never mapped). */
|
|
18
|
+
export function accountsDir(storageRoot) {
|
|
19
|
+
return path.join(storageRoot, 'accounts');
|
|
20
|
+
}
|
|
21
|
+
export class ServiceAccountStore {
|
|
22
|
+
storageRoot;
|
|
23
|
+
secrets;
|
|
24
|
+
constructor(storageRoot, secrets = SecretStore.standard(storageRoot)) {
|
|
25
|
+
this.storageRoot = storageRoot;
|
|
26
|
+
this.secrets = secrets;
|
|
27
|
+
}
|
|
28
|
+
file(kind) {
|
|
29
|
+
return new JsonFile(SavedSchema, path.join(accountsDir(this.storageRoot), kind + '.json'));
|
|
30
|
+
}
|
|
31
|
+
/** Whether a sign-in is saved and can be opened by this Windows user (it is never returned here). */
|
|
32
|
+
state(kind) {
|
|
33
|
+
if (!this.file(kind).read())
|
|
34
|
+
return 'none';
|
|
35
|
+
try {
|
|
36
|
+
return this.read(kind) ? 'saved' : 'none';
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return 'unreadable';
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
info(kind) {
|
|
43
|
+
const saved = this.file(kind).read();
|
|
44
|
+
return saved ? { savedAt: saved.savedAt, source: saved.source, summary: saved.summary } : null;
|
|
45
|
+
}
|
|
46
|
+
/** The sign-in, decrypted. Only the launch calls this; throws when this Windows user cannot open it. */
|
|
47
|
+
read(kind) {
|
|
48
|
+
const saved = this.file(kind).read();
|
|
49
|
+
if (!saved)
|
|
50
|
+
return null;
|
|
51
|
+
const payload = PAYLOADS[kind].safeParse(this.secrets.openJson(saved.sealed));
|
|
52
|
+
if (!payload.success)
|
|
53
|
+
fail('The saved ' + SERVICE_SPECS[kind].label + ' sign-in is damaged.', 'Save it again: sandbox account import ' + kind + ' (or login).');
|
|
54
|
+
return payload.data;
|
|
55
|
+
}
|
|
56
|
+
save(kind, payload, source, summary = null) {
|
|
57
|
+
const checked = PAYLOADS[kind].parse(payload);
|
|
58
|
+
if (JSON.stringify(checked).length > SERVICE_SPECS[kind].maxBytes)
|
|
59
|
+
fail('That ' + SERVICE_SPECS[kind].label + ' sign-in is too big to keep.');
|
|
60
|
+
this.file(kind).write({
|
|
61
|
+
savedAt: new Date().toISOString(),
|
|
62
|
+
source,
|
|
63
|
+
summary: summary === null ? null : summary.slice(0, 200),
|
|
64
|
+
sealed: this.secrets.sealJson(checked),
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
remove(kind) {
|
|
68
|
+
const file = this.file(kind).file;
|
|
69
|
+
if (!fs.existsSync(file))
|
|
70
|
+
return false;
|
|
71
|
+
fs.rmSync(file, { force: true });
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
}
|
package/dist/host/audit/log.d.ts
CHANGED
|
@@ -28,8 +28,8 @@ export declare const AuditEventSchema: z.ZodObject<{
|
|
|
28
28
|
ag: z.ZodOptional<z.ZodString>;
|
|
29
29
|
pj: z.ZodOptional<z.ZodString>;
|
|
30
30
|
sv: z.ZodOptional<z.ZodEnum<{
|
|
31
|
-
p: "p";
|
|
32
31
|
t: "t";
|
|
32
|
+
p: "p";
|
|
33
33
|
}>>;
|
|
34
34
|
h: z.ZodOptional<z.ZodString>;
|
|
35
35
|
au: z.ZodOptional<z.ZodString>;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { GenerateOptions, Generated } from '../../core/types.js';
|
|
2
2
|
import { type EnginePlan, type SandboxEngine } from '../engines/index.js';
|
|
3
|
+
/** A new launch's id: the time and 6 random hex digits. */
|
|
4
|
+
export declare function newLaunchId(): string;
|
|
3
5
|
export declare function generateSandbox(given: GenerateOptions,
|
|
4
6
|
/** The engine launch.ts opened (hcs needs a look at this PC first); Windows Sandbox when left out. */
|
|
5
7
|
opened?: SandboxEngine): Generated & {
|
|
@@ -4,6 +4,7 @@ import fs from 'node:fs';
|
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import crypto from 'node:crypto';
|
|
6
6
|
import os from 'node:os';
|
|
7
|
+
import { isServiceKind } from '../../shared/accounts/index.js';
|
|
7
8
|
import { widenForProjects } from '../../core/local-config/index.js';
|
|
8
9
|
import { dirExists, ensureDir, fail, writeJson } from '../../core/util/index.js';
|
|
9
10
|
import { engineFor, realLiveFolders } from '../engines/index.js';
|
|
@@ -22,6 +23,14 @@ import { copyRuntime, stageGitIdentity, stageHooks } from './stage.js';
|
|
|
22
23
|
import { decideProjectToolchains } from './toolchains.js';
|
|
23
24
|
import { buildLaunchPlan } from './launch-plan.js';
|
|
24
25
|
import { cappedMemoryMb, decideMemory, GUEST, memoryNote, residentNote, resolveMemoryMb } from './wsb-file.js';
|
|
26
|
+
/** The handed-over sign-ins still shared in the configs the sandbox boots with, as the secrets-file field. */
|
|
27
|
+
function withSharedAccounts(given, config, others) {
|
|
28
|
+
const kept = Object.fromEntries(Object.entries(given ?? {}).filter(([kind, payload]) => payload !== undefined &&
|
|
29
|
+
isServiceKind(kind) &&
|
|
30
|
+
config.accounts[kind].share &&
|
|
31
|
+
others.every((p) => p.config.accounts[kind].share)));
|
|
32
|
+
return Object.keys(kept).length ? { serviceAccounts: kept } : {};
|
|
33
|
+
}
|
|
25
34
|
const NO_PROFILE = { hostDir: null, staged: [], skipped: [], credentials: false, secretsRemoved: [] };
|
|
26
35
|
/** Moves the staged copy of this PC's Claude login out of the launch folder; its text goes in the one-shot file. */
|
|
27
36
|
function takeStagedCredentials(launchPath) {
|
|
@@ -32,6 +41,10 @@ function takeStagedCredentials(launchPath) {
|
|
|
32
41
|
fs.rmSync(file, { force: true });
|
|
33
42
|
return text;
|
|
34
43
|
}
|
|
44
|
+
/** A new launch's id: the time and 6 random hex digits. */
|
|
45
|
+
export function newLaunchId() {
|
|
46
|
+
return Date.now() + '-' + crypto.randomBytes(3).toString('hex');
|
|
47
|
+
}
|
|
35
48
|
export function generateSandbox(given,
|
|
36
49
|
/** The engine launch.ts opened (hcs needs a look at this PC first); Windows Sandbox when left out. */
|
|
37
50
|
opened) {
|
|
@@ -63,7 +76,7 @@ opened) {
|
|
|
63
76
|
const askedMb = Math.max(resolveMemoryMb(config.sandbox, o.freeMemoryBytes, undefined, commitAvailableMb), ...others.map((p) => (typeof p.config.sandbox.memoryMb === 'number' ? p.config.sandbox.memoryMb : 0)));
|
|
64
77
|
// Never more than this PC can keep in RAM for it, whatever was asked.
|
|
65
78
|
const memoryMb = cappedMemoryMb(askedMb, config.sandbox, Math.floor(os.totalmem() / 1048576));
|
|
66
|
-
const launchId =
|
|
79
|
+
const launchId = o.launchId ?? newLaunchId();
|
|
67
80
|
const launchPath = path.join(storageRoot, 'launches', launchId);
|
|
68
81
|
// Whatever must stay secret goes in a one-shot file in the data folder, never into the launch folder (the sandbox
|
|
69
82
|
// maps it read-only and it is kept for days). Leftovers of earlier launches go first.
|
|
@@ -116,6 +129,8 @@ opened) {
|
|
|
116
129
|
...(o.vpn?.wireguardConfig ? { wireguardConfig: o.vpn.wireguardConfig } : {}),
|
|
117
130
|
// Checked again on the configs the sandbox boots with: never when a project in it has git access below "full".
|
|
118
131
|
...(o.githubToken && githubTokenAllowed(config, others) ? { githubToken: o.githubToken } : {}),
|
|
132
|
+
// Checked again here: only the kinds you share, and none a project in this sandbox turned off.
|
|
133
|
+
...withSharedAccounts(o.serviceAccounts, config, others),
|
|
119
134
|
});
|
|
120
135
|
fs.writeFileSync(path.join(launchPath, 'env.cmd'), logonEnv(session.workspace), 'utf8');
|
|
121
136
|
copyRuntime(o.packageRoot, launchPath);
|