windows-development-sandbox 1.20.0 → 1.23.1
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 +8 -1
- package/dist/app/features/access/access.def.js +5 -2
- package/dist/app/features/access/access.run.js +7 -3
- package/dist/app/features/accounts/accounts.def.d.ts +29 -1
- package/dist/app/features/accounts/accounts.def.js +10 -4
- package/dist/app/features/accounts/accounts.run.d.ts +1 -0
- package/dist/app/features/accounts/accounts.run.js +7 -2
- package/dist/app/features/accounts/services.def.d.ts +4 -1
- package/dist/app/features/accounts/services.def.js +15 -14
- package/dist/app/features/accounts/services.run.d.ts +1 -0
- package/dist/app/features/accounts/services.run.js +41 -15
- package/dist/app/features/defs.d.ts +39 -2
- package/dist/app/features/engine/engine.run.js +1 -1
- package/dist/app/features/github/github.def.d.ts +44 -3
- package/dist/app/features/github/github.def.js +145 -63
- package/dist/app/features/github/github.run.d.ts +9 -2
- package/dist/app/features/github/github.run.js +43 -5
- package/dist/app/features/health/health.def.js +8 -2
- package/dist/app/features/logs/logs.def.js +0 -8
- package/dist/app/features/projects/projects.def.js +2 -2
- package/dist/app/features/projects/projects.run.js +5 -5
- package/dist/app/features/sandbox/sandbox.def.js +7 -3
- package/dist/app/features/sessions/sessions.def.d.ts +4 -4
- package/dist/app/features/sessions/sessions.def.js +0 -5
- package/dist/app/features/sessions/sessions.run.d.ts +1 -1
- package/dist/app/features/sessions/sessions.run.js +2 -2
- package/dist/app/features/settings/categories.def.js +2 -0
- package/dist/app/features/settings/settings-rows.js +1 -1
- package/dist/app/features/settings/settings.def.js +1 -1
- package/dist/app/features/settings/settings.run.js +3 -3
- package/dist/app/features/storage/storage.run.js +18 -9
- package/dist/app/features/updates/update-lines.js +2 -2
- package/dist/app/features/updates/updates.def.js +1 -1
- package/dist/app/features/updates/updates.run.js +1 -1
- package/dist/app/features/web/web.def.js +2 -2
- package/dist/app/features/web/web.run.js +12 -4
- package/dist/app/kernel/errors.d.ts +7 -0
- package/dist/app/kernel/errors.js +18 -3
- package/dist/app/kernel/rpc.d.ts +5 -0
- package/dist/app/kernel/view.d.ts +11 -1
- package/dist/bin/agent-sandbox.js +2 -2
- package/dist/bin/{chunk-DK4NTBUF.js → chunk-7YPEDAJD.js} +172 -48
- package/dist/bin/{chunk-2IMT7ZCT.js → chunk-QFSJX2KW.js} +1 -1
- package/dist/bin/{chunk-Z2P5OGKL.js → chunk-USTRSHIE.js} +2436 -1693
- 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/manifest.d.ts +2 -2
- package/dist/cli/manifest.js +2 -1
- package/dist/cli/menu/coverage-exempt.d.ts +17 -0
- package/dist/cli/menu/coverage-exempt.js +61 -0
- package/dist/cli/menu/io-menu.d.ts +2 -0
- package/dist/cli/menu/io-menu.js +50 -5
- package/dist/cli/menu/pages/accounts-page.js +1 -1
- package/dist/cli/menu/pages/agents-page.js +8 -2
- package/dist/cli/menu/pages/maintenance-page.d.ts +6 -0
- package/dist/cli/menu/pages/maintenance-page.js +9 -2
- package/dist/cli/menu/pages/sessions-page.d.ts +2 -0
- package/dist/cli/menu/pages/sessions-page.js +12 -4
- package/dist/cli/menu/pages/settings-page.js +12 -1
- package/dist/cli/menu/remote-menu.js +1 -1
- package/dist/cli/menu/shell.d.ts +5 -0
- package/dist/cli/menu/shell.js +13 -0
- package/dist/cli/report.js +2 -0
- package/dist/cli/tui/app.d.ts +5 -0
- package/dist/cli/tui/app.js +2 -0
- package/dist/core/config/defaults.js +3 -1
- package/dist/core/schema/accounts.d.ts +6 -0
- package/dist/core/schema/accounts.js +13 -11
- package/dist/core/schema/config.d.ts +20 -8
- package/dist/core/schema/config.js +4 -4
- package/dist/core/schema/github.js +1 -1
- package/dist/core/schema/profile.js +8 -4
- package/dist/core/schema/resume.js +1 -1
- package/dist/core/schema/sandbox.js +3 -3
- package/dist/core/schema/vpn.js +1 -1
- package/dist/core/schema/web.js +3 -3
- package/dist/core/types.d.ts +6 -0
- package/dist/core/util/errors.d.ts +11 -1
- package/dist/core/util/errors.js +7 -1
- package/dist/core/util/index.d.ts +1 -1
- package/dist/core/util/index.js +1 -1
- package/dist/guest/accounts/adapter.d.ts +2 -0
- package/dist/guest/accounts/adapters.js +4 -0
- package/dist/guest/accounts/apply.d.ts +6 -2
- package/dist/guest/accounts/kinds/cfx.d.ts +7 -0
- package/dist/guest/accounts/kinds/cfx.js +33 -3
- package/dist/guest/accounts/kinds/comet.d.ts +7 -0
- package/dist/guest/accounts/kinds/comet.js +50 -0
- package/dist/guest/accounts/kinds/perplexity.d.ts +2 -0
- package/dist/guest/accounts/kinds/perplexity.js +12 -0
- package/dist/guest/accounts/watch.d.ts +2 -0
- package/dist/guest/accounts/watch.js +12 -2
- package/dist/guest/agents/agent-installer.js +1 -1
- package/dist/guest/agents/profile-links.d.ts +2 -0
- package/dist/guest/agents/profile-links.js +18 -4
- package/dist/guest/apps/after-install.js +19 -0
- package/dist/guest/apps/app-installer.d.ts +11 -0
- package/dist/guest/apps/app-installer.js +69 -6
- package/dist/guest/apps/comet-unpack.d.ts +20 -0
- package/dist/guest/apps/comet-unpack.js +127 -0
- package/dist/guest/apps/index.d.ts +1 -1
- package/dist/guest/apps/index.js +1 -1
- package/dist/guest/apps/resolvers-agents-web.d.ts +2 -0
- package/dist/guest/apps/resolvers-agents-web.js +7 -0
- package/dist/guest/apps/resolvers.js +2 -0
- package/dist/guest/apps/shortcuts.d.ts +2 -0
- package/dist/guest/apps/shortcuts.js +1 -0
- package/dist/guest/apps/unpack.js +14 -4
- package/dist/guest/boot/boot.d.ts +45 -16
- package/dist/guest/boot/boot.js +316 -145
- package/dist/guest/boot/graph.d.ts +252 -0
- package/dist/guest/boot/graph.js +257 -0
- package/dist/guest/boot/guide-lines.d.ts +25 -0
- package/dist/guest/boot/guide-lines.js +41 -0
- package/dist/guest/boot/guide.d.ts +1 -4
- package/dist/guest/boot/guide.js +6 -8
- package/dist/guest/boot/runner.d.ts +77 -0
- package/dist/guest/boot/runner.js +215 -0
- package/dist/guest/browser/cdp.d.ts +71 -0
- package/dist/guest/browser/cdp.js +306 -0
- package/dist/guest/browser/comet.d.ts +28 -0
- package/dist/guest/browser/comet.js +155 -0
- package/dist/guest/daemon/project-provisioner.js +6 -1
- package/dist/guest/devtools/fivem.d.ts +1 -1
- package/dist/guest/devtools/fivem.js +243 -3
- package/dist/guest/entries/daemon.js +2 -0
- package/dist/guest/runtime/log.d.ts +12 -0
- package/dist/guest/runtime/log.js +29 -5
- package/dist/guest/runtime/pools.d.ts +24 -0
- package/dist/guest/runtime/pools.js +60 -0
- package/dist/guest/runtime/process.d.ts +4 -0
- package/dist/guest/runtime/process.js +38 -1
- package/dist/guest/runtime/status.d.ts +5 -1
- package/dist/guest/runtime/status.js +19 -0
- package/dist/guest/setup/addon-mcp.d.ts +1 -1
- package/dist/guest/setup/addon-mcp.js +52 -12
- package/dist/guest/setup/agent-addons.d.ts +3 -2
- package/dist/guest/setup/agent-addons.js +12 -3
- package/dist/guest/setup/environment.d.ts +1 -1
- package/dist/guest/setup/environment.js +25 -12
- package/dist/guest/setup/headroom.d.ts +8 -3
- package/dist/guest/setup/headroom.js +44 -15
- package/dist/guest/setup/path-plan.d.ts +28 -0
- package/dist/guest/setup/path-plan.js +69 -2
- package/dist/guest/setup/shell-path.d.ts +1 -1
- package/dist/guest/setup/shell-path.js +3 -3
- package/dist/guest/toolchains/fivem/artifacts.d.ts +48 -1
- package/dist/guest/toolchains/fivem/artifacts.js +97 -4
- package/dist/guest/toolchains/fivem/fivem.d.ts +32 -1
- package/dist/guest/toolchains/fivem/fivem.js +168 -3
- package/dist/guest/toolchains/fivem/scaffold.d.ts +35 -0
- package/dist/guest/toolchains/fivem/scaffold.js +105 -0
- package/dist/guest/toolchains/fivem/serve-plan.d.ts +34 -0
- package/dist/guest/toolchains/fivem/serve-plan.js +61 -0
- package/dist/guest/toolchains/languages/go.js +1 -1
- package/dist/guest/toolchains/languages/node.js +2 -1
- package/dist/guest/toolchains/languages/php.d.ts +2 -0
- package/dist/guest/toolchains/languages/php.js +13 -1
- package/dist/guest/toolchains/languages/python-tools.js +1 -1
- package/dist/guest/toolchains/languages/ruby.js +1 -1
- package/dist/guest/toolchains/native/binstall.js +1 -1
- package/dist/guest/toolchains/native/msvc/packages.js +10 -4
- package/dist/guest/toolchains/native/rust.d.ts +2 -0
- package/dist/guest/toolchains/native/rust.js +11 -4
- package/dist/guest/toolchains/native/unreal.d.ts +2 -0
- package/dist/guest/toolchains/native/unreal.js +16 -6
- package/dist/guest/toolchains/native/vcruntime.d.ts +2 -0
- package/dist/guest/toolchains/native/vcruntime.js +10 -2
- package/dist/guest/toolchains/native/visual-studio.d.ts +3 -0
- package/dist/guest/toolchains/native/visual-studio.js +44 -26
- package/dist/guest/toolchains/native/vs-request.d.ts +2 -0
- package/dist/guest/toolchains/native/vs-request.js +2 -0
- package/dist/guest/toolchains/plan.js +19 -8
- package/dist/guest/toolchains/scheduler.d.ts +2 -21
- package/dist/guest/toolchains/scheduler.js +7 -121
- package/dist/guest/toolchains/setup.d.ts +15 -2
- package/dist/guest/toolchains/setup.js +39 -20
- package/dist/guest/toolchains/split.d.ts +28 -0
- package/dist/guest/toolchains/split.js +60 -0
- package/dist/guest/toolchains/steam/client.js +2 -1
- package/dist/guest/toolchains/step-scheduler.d.ts +51 -0
- package/dist/guest/toolchains/step-scheduler.js +147 -0
- package/dist/guest/toolchains/step.d.ts +13 -0
- package/dist/guest/toolchains/tools/git.js +18 -0
- package/dist/guest/vpn/openvpn.js +3 -4
- package/dist/guest/vpn/wireguard.js +11 -4
- package/dist/host/accounts/deliver.js +7 -0
- package/dist/host/accounts/handoff.js +2 -6
- package/dist/host/accounts/index.d.ts +1 -1
- package/dist/host/accounts/index.js +1 -1
- package/dist/host/accounts/kinds/cfx.d.ts +1 -1
- package/dist/host/accounts/kinds/cfx.js +47 -4
- package/dist/host/accounts/kinds/comet.d.ts +2 -0
- package/dist/host/accounts/kinds/comet.js +6 -0
- package/dist/host/accounts/kinds/perplexity.d.ts +2 -0
- package/dist/host/accounts/kinds/perplexity.js +30 -0
- package/dist/host/accounts/leases.d.ts +7 -1
- package/dist/host/accounts/leases.js +13 -2
- package/dist/host/accounts/source.d.ts +7 -1
- package/dist/host/accounts/source.js +27 -0
- package/dist/host/accounts/sources.js +4 -0
- package/dist/host/github/deliver.d.ts +1 -1
- package/dist/host/github/deliver.js +20 -1
- package/dist/host/github/index.d.ts +2 -1
- package/dist/host/github/index.js +1 -0
- package/dist/host/github/token.d.ts +13 -1
- package/dist/host/github/token.js +19 -2
- package/dist/host/github/verify.d.ts +27 -0
- package/dist/host/github/verify.js +61 -0
- package/dist/host/launch/hints/fivem.d.ts +3 -1
- package/dist/host/launch/hints/fivem.js +68 -2
- package/dist/host/launch/session-file.js +4 -0
- package/dist/host/launch/toolchains.d.ts +2 -1
- package/dist/host/launch/toolchains.js +5 -3
- package/dist/host/profile/claude-login.d.ts +2 -2
- package/dist/host/profile/claude-login.js +4 -3
- package/dist/host/vpn/nordvpn.d.ts +2 -4
- package/dist/host/vpn/nordvpn.js +2 -24
- package/dist/runtime/agent.js +405 -90
- package/dist/runtime/daemon.js +3010 -1383
- package/dist/runtime/db.js +333 -90
- package/dist/runtime/main.js +4720 -1954
- package/dist/runtime/netproxy.js +286 -69
- package/dist/runtime/tool.js +2852 -1780
- package/dist/runtime/view.js +286 -69
- package/dist/selftest/accounts.test.js +1 -1
- package/dist/selftest/agent-paths.test.js +39 -1
- package/dist/selftest/agent-settings.test.js +6 -0
- package/dist/selftest/agents-web.test.d.ts +2 -0
- package/dist/selftest/agents-web.test.js +647 -0
- package/dist/selftest/app.test.js +5 -2
- package/dist/selftest/boot-runner.test.d.ts +2 -0
- package/dist/selftest/boot-runner.test.js +456 -0
- package/dist/selftest/channel.test.js +15 -0
- package/dist/selftest/fivem.test.js +249 -3
- package/dist/selftest/github.test.js +94 -9
- package/dist/selftest/guest-boot.test.js +3 -0
- package/dist/selftest/health.test.js +1 -1
- package/dist/selftest/index.js +8 -0
- package/dist/selftest/layout.test.js +5 -0
- package/dist/selftest/menu-coverage.test.d.ts +2 -0
- package/dist/selftest/menu-coverage.test.js +46 -0
- package/dist/selftest/menu-screens.js +37 -1
- package/dist/selftest/persisted-installs.test.js +6 -3
- package/dist/selftest/service-accounts.test.js +3 -2
- package/dist/selftest/support/engine-golden.js +27 -0
- package/dist/selftest/support/fixtures.d.ts +6 -0
- package/dist/selftest/support/web-preview.d.ts +34 -1
- package/dist/selftest/support/web-preview.js +21 -18
- package/dist/selftest/toolchains.test.js +23 -1
- package/dist/selftest/ui-text.test.d.ts +7 -0
- package/dist/selftest/ui-text.test.js +300 -0
- package/dist/shared/accounts/anthropic.d.ts +1 -1
- package/dist/shared/accounts/anthropic.js +9 -8
- package/dist/shared/accounts/index.d.ts +2 -0
- package/dist/shared/accounts/index.js +2 -0
- package/dist/shared/accounts/kinds/comet.d.ts +12 -0
- package/dist/shared/accounts/kinds/comet.js +64 -0
- package/dist/shared/accounts/kinds/perplexity.d.ts +8 -0
- package/dist/shared/accounts/kinds/perplexity.js +37 -0
- package/dist/shared/accounts/kinds.d.ts +24 -5
- package/dist/shared/accounts/kinds.js +86 -4
- package/dist/shared/accounts/payloads.d.ts +81 -0
- package/dist/shared/accounts/payloads.js +39 -1
- package/dist/shared/apps/agent-addons.d.ts +33 -5
- package/dist/shared/apps/agent-addons.js +95 -3
- package/dist/shared/apps/catalogue-agents-web.d.ts +14 -0
- package/dist/shared/apps/catalogue-agents-web.js +112 -0
- package/dist/shared/apps/catalogue-modding.js +32 -1
- package/dist/shared/apps/catalogue.d.ts +27 -3
- package/dist/shared/apps/catalogue.js +2 -1
- package/dist/shared/apps/index.d.ts +3 -1
- package/dist/shared/apps/index.js +3 -1
- package/dist/shared/channel/schema.d.ts +21 -3
- package/dist/shared/channel/schema.js +7 -0
- package/dist/shared/channel/secrets.d.ts +19 -0
- package/dist/shared/concurrency/index.d.ts +3 -0
- package/dist/shared/concurrency/index.js +3 -0
- package/dist/shared/concurrency/installer-retry.d.ts +6 -0
- package/dist/shared/concurrency/installer-retry.js +10 -0
- package/dist/shared/concurrency/limiter.d.ts +19 -0
- package/dist/shared/concurrency/limiter.js +67 -0
- package/dist/shared/concurrency/pools.d.ts +7 -0
- package/dist/shared/concurrency/pools.js +9 -0
- package/dist/shared/download/archive.d.ts +2 -0
- package/dist/shared/download/archive.js +11 -1
- package/dist/shared/download/bounded-fetch.d.ts +13 -0
- package/dist/shared/download/bounded-fetch.js +40 -0
- package/dist/shared/download/downloader.d.ts +5 -0
- package/dist/shared/download/downloader.js +50 -38
- package/dist/shared/download/index.d.ts +2 -1
- package/dist/shared/download/index.js +2 -1
- package/dist/shared/readiness.d.ts +2 -2
- package/dist/shared/readiness.js +2 -2
- package/dist/web/index.html +1 -1
- package/dist/web/{main-OUUQYNK4.js → main-CCILLX2M.js} +67 -67
- 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/addons/browser-use-mcp/requirements.in +1 -0
- package/sandbox/addons/browser-use-mcp/requirements.txt +2350 -0
- package/sandbox/addons/chrome-devtools-mcp/package-lock.json +38 -0
- package/sandbox/addons/chrome-devtools-mcp/package.json +7 -0
- package/sandbox/addons/comet-mcp/package-lock.json +1277 -0
- package/sandbox/addons/comet-mcp/package.json +7 -0
- package/sandbox/addons/perplexity-mcp/package-lock.json +1655 -0
- package/sandbox/addons/perplexity-mcp/package.json +7 -0
- package/sandbox/scripts/add-firewall-port.ps1 +18 -4
- package/sandbox/scripts/set-machine-environment.ps1 +30 -15
- package/sandbox/templates/fivem/README.txt +6 -2
- package/sandbox/templates/fivem/cs/Client/Client.csproj +15 -0
- package/sandbox/templates/fivem/cs/Client/ClientMain.cs +20 -0
- package/sandbox/templates/fivem/cs/Server/Server.csproj +14 -0
- package/sandbox/templates/fivem/cs/Server/ServerMain.cs +20 -0
- package/sandbox/templates/fivem/cs/fxmanifest.lua +11 -0
- package/sandbox/templates/fivem/js/client.js +12 -0
- package/sandbox/templates/fivem/js/fxmanifest.lua +9 -0
- package/sandbox/templates/fivem/js/server.js +6 -0
- package/sandbox/templates/fivem/lua/client.lua +8 -0
- package/sandbox/templates/fivem/lua/fxmanifest.lua +10 -0
- package/sandbox/templates/fivem/lua/server.lua +6 -0
- package/sandbox/templates/fivem/server.cfg +37 -0
- package/sandbox/templates/fivem/ts/build.js +17 -0
- package/sandbox/templates/fivem/ts/fxmanifest.lua +9 -0
- package/sandbox/templates/fivem/ts/package.json +16 -0
- package/sandbox/templates/fivem/ts/src/client/client.ts +12 -0
- package/sandbox/templates/fivem/ts/src/client/tsconfig.json +13 -0
- package/sandbox/templates/fivem/ts/src/server/server.ts +6 -0
- package/sandbox/templates/fivem/ts/src/server/tsconfig.json +13 -0
- package/schemas/config.schema.json +56 -27
- package/schemas/sandbox-config.schema.json +37 -14
|
@@ -1,25 +1,44 @@
|
|
|
1
|
-
import
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import { log, runQuiet } from '../runtime/log.js';
|
|
3
|
+
import { toolsDir } from '../runtime/paths.js';
|
|
4
|
+
import { installedToolPaths } from '../setup/path-plan.js';
|
|
2
5
|
import { mkdirp } from '../runtime/fs.js';
|
|
3
|
-
import {
|
|
6
|
+
import { tool } from './tool-env.js';
|
|
4
7
|
import { plan } from './plan.js';
|
|
5
8
|
import { scheduler } from './scheduler.js';
|
|
6
|
-
|
|
7
|
-
|
|
9
|
+
import { mergeParts, runSplit } from './split.js';
|
|
10
|
+
/** The parts of these steps merged in the order of `names` (and the config's own variables on top). */
|
|
11
|
+
export function mergeToolchains(config, names, parts, planNames = names) {
|
|
12
|
+
const map = parts instanceof Map ? parts : new Map(Object.entries(parts));
|
|
13
|
+
return {
|
|
14
|
+
...mergeParts(names, map, { environment: config.environment, bin: mkdirp(tool('bin')) }),
|
|
15
|
+
names: [...planNames],
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Provisions every enabled toolchain and database, and returns the combined environment for the agent. `later` is
|
|
20
|
+
* set when steps were left for the background: it resolves with the result of those steps alone.
|
|
21
|
+
*/
|
|
22
|
+
export async function setupToolchains(config, versions, options, run = {}, extras = {}) {
|
|
8
23
|
const steps = plan(config, versions, options).filter((s) => s.enabled);
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
24
|
+
const split = await runSplit(scheduler, steps, run, { ...extras, quiet: runQuiet });
|
|
25
|
+
const result = mergeToolchains(config, split.nowNames, split.now, steps.map((s) => s.name));
|
|
26
|
+
// Python and rust already installed in the persistent tools are on PATH for every project (at the end, nothing is
|
|
27
|
+
// installed): the project this session is for may not have asked for them, but its scripts and neighbours use them.
|
|
28
|
+
const have = new Set(steps.map((s) => s.name));
|
|
29
|
+
const installed = installedToolPaths(toolsDir, { exists: fs.existsSync, list: (dir) => (fs.existsSync(dir) ? fs.readdirSync(dir) : []) }, new Set([...(have.has('Python') ? ['python'] : []), ...(have.has('Rust') ? ['rust'] : [])]));
|
|
30
|
+
result.append.push(...installed.append);
|
|
31
|
+
result.summary.push(...installed.summary);
|
|
32
|
+
for (const [name, value] of Object.entries(installed.vars))
|
|
33
|
+
result.vars[name] ??= value;
|
|
34
|
+
log('Set up ' +
|
|
35
|
+
split.nowNames.length +
|
|
36
|
+
' toolchains and databases' +
|
|
37
|
+
(split.laterNames.length ? ' (' + split.laterNames.length + ' more later)' : '') +
|
|
38
|
+
'.');
|
|
39
|
+
const later = split.later
|
|
40
|
+
? split.later.then((parts) => mergeToolchains(config, split.laterNames, parts, steps.map((s) => s.name)))
|
|
41
|
+
: null;
|
|
42
|
+
later?.catch(() => undefined);
|
|
43
|
+
return Object.assign(result, { later });
|
|
25
44
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type ToolEnv } from './tool-env.js';
|
|
2
|
+
import type { ToolchainStep } from './step.js';
|
|
3
|
+
import type { RunOptions, StepScheduler } from './step-scheduler.js';
|
|
4
|
+
/** The steps that wait for the gate: the ones marked `background`, and every step that comes after one. */
|
|
5
|
+
export declare function backgroundNames(steps: readonly ToolchainStep[]): Set<string>;
|
|
6
|
+
export interface SplitRun {
|
|
7
|
+
/** What the foreground steps contributed, in plan order. */
|
|
8
|
+
readonly now: Map<string, ToolEnv>;
|
|
9
|
+
readonly nowNames: readonly string[];
|
|
10
|
+
readonly laterNames: readonly string[];
|
|
11
|
+
/** The background steps' parts, once the gate opened and they ran; null when nothing was left for later. */
|
|
12
|
+
readonly later: Promise<Map<string, ToolEnv>> | null;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Runs the foreground steps and returns; the background steps (their downloads start now, behind the foreground ones)
|
|
16
|
+
* run after `gate`, inside `quiet` (file-only logging).
|
|
17
|
+
*/
|
|
18
|
+
export declare function runSplit(scheduler: StepScheduler, steps: readonly ToolchainStep[], run: RunOptions, o?: {
|
|
19
|
+
gate?: Promise<void>;
|
|
20
|
+
quiet?: <T>(work: () => T) => T;
|
|
21
|
+
}): Promise<SplitRun>;
|
|
22
|
+
/** The parts of these steps merged in the order of `names`, so PATH order never depends on which finished first. */
|
|
23
|
+
export declare function mergeParts(names: readonly string[], parts: ReadonlyMap<string, ToolEnv>, o: {
|
|
24
|
+
environment: Readonly<Record<string, string>>;
|
|
25
|
+
bin: string;
|
|
26
|
+
}): ToolEnv & {
|
|
27
|
+
steps: Record<string, ToolEnv>;
|
|
28
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// Splits a plan into the steps that run now and the ones that wait for a gate (the background: Visual Studio and what
|
|
2
|
+
// needs it), and merges the steps' parts in plan order. Pure apart from the scheduler it is given.
|
|
3
|
+
import { asBackground } from '../../shared/concurrency/index.js';
|
|
4
|
+
import { emptyEnv } from './tool-env.js';
|
|
5
|
+
/** The steps that wait for the gate: the ones marked `background`, and every step that comes after one. */
|
|
6
|
+
export function backgroundNames(steps) {
|
|
7
|
+
const later = new Set(steps.filter((s) => s.background).map((s) => s.name));
|
|
8
|
+
for (let grew = later.size > 0; grew;) {
|
|
9
|
+
grew = false;
|
|
10
|
+
for (const s of steps)
|
|
11
|
+
if (!later.has(s.name) && (s.after ?? []).some((n) => later.has(n))) {
|
|
12
|
+
later.add(s.name);
|
|
13
|
+
grew = true;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return later;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Runs the foreground steps and returns; the background steps (their downloads start now, behind the foreground ones)
|
|
20
|
+
* run after `gate`, inside `quiet` (file-only logging).
|
|
21
|
+
*/
|
|
22
|
+
export async function runSplit(scheduler, steps, run, o = {}) {
|
|
23
|
+
const waiting = backgroundNames(steps);
|
|
24
|
+
const now = steps.filter((s) => !waiting.has(s.name));
|
|
25
|
+
const rest = steps.filter((s) => waiting.has(s.name));
|
|
26
|
+
asBackground(() => scheduler.startFetches(rest));
|
|
27
|
+
const parts = await scheduler.run(now, run);
|
|
28
|
+
const quiet = o.quiet ?? ((work) => work());
|
|
29
|
+
const later = rest.length
|
|
30
|
+
? (async () => {
|
|
31
|
+
await o.gate;
|
|
32
|
+
// A progress callback that does nothing: the scheduler must not rewrite the boot's status as "ready".
|
|
33
|
+
return quiet(() => asBackground(() => scheduler.run(rest, { progress: () => undefined })));
|
|
34
|
+
})()
|
|
35
|
+
: null;
|
|
36
|
+
// Nobody may see a failed background run as an unhandled rejection before the boot awaits it.
|
|
37
|
+
later?.catch(() => undefined);
|
|
38
|
+
return { now: parts, nowNames: now.map((s) => s.name), laterNames: rest.map((s) => s.name), later };
|
|
39
|
+
}
|
|
40
|
+
/** The parts of these steps merged in the order of `names`, so PATH order never depends on which finished first. */
|
|
41
|
+
export function mergeParts(names, parts, o) {
|
|
42
|
+
const present = names.filter((n) => parts.has(n));
|
|
43
|
+
const env = {
|
|
44
|
+
...emptyEnv(),
|
|
45
|
+
steps: Object.fromEntries(present.map((n) => [n, parts.get(n)])),
|
|
46
|
+
};
|
|
47
|
+
env.prepend.push(o.bin);
|
|
48
|
+
for (const name of present) {
|
|
49
|
+
const p = parts.get(name);
|
|
50
|
+
Object.assign(env.vars, p.vars);
|
|
51
|
+
env.prepend.push(...p.prepend);
|
|
52
|
+
env.append.push(...p.append);
|
|
53
|
+
env.summary.push(...p.summary);
|
|
54
|
+
}
|
|
55
|
+
Object.assign(env.vars, o.environment);
|
|
56
|
+
// The git guard's variables (AGENT_GIT_ACCESS, GIT_DIR, GIT_CONFIG_*, BASH_ENV) always win over
|
|
57
|
+
// toolchains.environment.
|
|
58
|
+
Object.assign(env.vars, parts.get('Git')?.vars);
|
|
59
|
+
return env;
|
|
60
|
+
}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import fs from 'node:fs';
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
import { errMsg } from '../../../core/util/errors.js';
|
|
7
|
+
import { heavy } from '../../../shared/concurrency/index.js';
|
|
7
8
|
import { moveIntoPlace, spawnAsync, stagingFolder } from '../../../shared/download/index.js';
|
|
8
9
|
import { safeRemove } from '../../../shared/io/safe-remove.js';
|
|
9
10
|
import { exists } from '../../runtime/fs.js';
|
|
@@ -38,7 +39,7 @@ async function installSteamClient() {
|
|
|
38
39
|
try {
|
|
39
40
|
log('Unpacking the Steam client into tools\\steam ...');
|
|
40
41
|
// NSIS: /S silent, /D= the folder (last, unquoted: the staging path has no spaces).
|
|
41
|
-
const code = await spawnAsync(setup, ['/S', '/D=' + work], { timeout: SETUP_TIMEOUT_MS });
|
|
42
|
+
const code = await heavy.run(() => spawnAsync(setup, ['/S', '/D=' + work], { timeout: SETUP_TIMEOUT_MS }));
|
|
42
43
|
const unpacked = path.join(work, 'steam.exe');
|
|
43
44
|
if (!exists(unpacked))
|
|
44
45
|
throw new Error('SteamSetup.exe did not unpack steam.exe (exit code ' + code + ')');
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { type ToolEnv } from './tool-env.js';
|
|
2
|
+
import type { ToolchainStep } from './step.js';
|
|
3
|
+
/** Where the scheduler's warnings and the boot status go (injected, so tests need neither the log nor session.json). */
|
|
4
|
+
export interface SchedulerSink {
|
|
5
|
+
warn(message: string): void;
|
|
6
|
+
status(message: string, progress: {
|
|
7
|
+
active: {
|
|
8
|
+
name: string;
|
|
9
|
+
percent?: number;
|
|
10
|
+
}[];
|
|
11
|
+
done: number;
|
|
12
|
+
total: number;
|
|
13
|
+
}): void;
|
|
14
|
+
}
|
|
15
|
+
export interface RunOptions {
|
|
16
|
+
/** Progress lines ("Installing Go, Rust (3 of 9 done)"); default: the sandbox's boot status. */
|
|
17
|
+
report?: (message: string) => void;
|
|
18
|
+
/** What machine-wide (shared) steps contributed at boot: reused instead of run again. */
|
|
19
|
+
reuse?: Record<string, ToolEnv>;
|
|
20
|
+
/** The boot: gets what runs now instead of the scheduler writing the sandbox status itself. */
|
|
21
|
+
progress?: (p: {
|
|
22
|
+
active: {
|
|
23
|
+
name: string;
|
|
24
|
+
percent?: number;
|
|
25
|
+
}[];
|
|
26
|
+
done: number;
|
|
27
|
+
total: number;
|
|
28
|
+
}) => void;
|
|
29
|
+
/** A step finished (installed, failed or reused). */
|
|
30
|
+
onDone?: (name: string) => void;
|
|
31
|
+
}
|
|
32
|
+
export declare class StepScheduler {
|
|
33
|
+
private readonly sink;
|
|
34
|
+
private readonly parallel;
|
|
35
|
+
/** One lock per kind of step ("Node 20" and "Node 22" share nvm's folders): held while that step runs, in any run. */
|
|
36
|
+
private readonly locks;
|
|
37
|
+
/** The step a download belongs to (several run at once), for its progress. */
|
|
38
|
+
private readonly currentStep;
|
|
39
|
+
/** Downloads started ahead of their steps (by name), until the step has used them. */
|
|
40
|
+
private readonly fetches;
|
|
41
|
+
constructor(sink: SchedulerSink, parallel?: number);
|
|
42
|
+
/**
|
|
43
|
+
* Starts the downloads of these steps now (once per step); `run` waits for a step's own download before it installs.
|
|
44
|
+
* Failures are not reported here: the install downloads again and reports the real reason.
|
|
45
|
+
*/
|
|
46
|
+
startFetches(steps: readonly ToolchainStep[]): void;
|
|
47
|
+
/** Runs the steps (each with its own environment) and returns each step's environment, in plan order. */
|
|
48
|
+
run(steps: readonly ToolchainStep[], o: RunOptions): Promise<Map<string, ToolEnv>>;
|
|
49
|
+
/** Runs `work` once no other step of the same kind (first word of the name) is running. */
|
|
50
|
+
private exclusive;
|
|
51
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
// Runs the planned steps in parallel, a few at a time, after the steps they depend on. One lock per kind of step is
|
|
2
|
+
// held across runs: the boot and projects that join later never install two Nodes (or two Gos) at once.
|
|
3
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
4
|
+
import { downloader } from '../../shared/download/index.js';
|
|
5
|
+
import { errMsg } from '../../core/util/errors.js';
|
|
6
|
+
import { emptyEnv } from './tool-env.js';
|
|
7
|
+
/** How many installers run at once: enough to overlap downloads with installs, few enough not to thrash the disk. */
|
|
8
|
+
const PARALLEL = 4;
|
|
9
|
+
/** Runs one installer; a failure is reported and skipped so it never blocks the other toolchains. */
|
|
10
|
+
async function runReported(sink, name, env, work) {
|
|
11
|
+
try {
|
|
12
|
+
await work();
|
|
13
|
+
}
|
|
14
|
+
catch (e) {
|
|
15
|
+
sink.warn(name +
|
|
16
|
+
' could not be set up and is skipped; everything else still works, and the next start tries again. Reason: ' +
|
|
17
|
+
errMsg(e));
|
|
18
|
+
env.summary.push(name + ': FAILED');
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export class StepScheduler {
|
|
22
|
+
sink;
|
|
23
|
+
parallel;
|
|
24
|
+
/** One lock per kind of step ("Node 20" and "Node 22" share nvm's folders): held while that step runs, in any run. */
|
|
25
|
+
locks = new Map();
|
|
26
|
+
/** The step a download belongs to (several run at once), for its progress. */
|
|
27
|
+
currentStep = new AsyncLocalStorage();
|
|
28
|
+
/** Downloads started ahead of their steps (by name), until the step has used them. */
|
|
29
|
+
fetches = new Map();
|
|
30
|
+
constructor(sink, parallel = PARALLEL) {
|
|
31
|
+
this.sink = sink;
|
|
32
|
+
this.parallel = parallel;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Starts the downloads of these steps now (once per step); `run` waits for a step's own download before it installs.
|
|
36
|
+
* Failures are not reported here: the install downloads again and reports the real reason.
|
|
37
|
+
*/
|
|
38
|
+
startFetches(steps) {
|
|
39
|
+
for (const s of steps) {
|
|
40
|
+
if (!s.fetch || this.fetches.has(s.name))
|
|
41
|
+
continue;
|
|
42
|
+
this.fetches.set(s.name, this.currentStep
|
|
43
|
+
.run(s.name, async () => s.fetch())
|
|
44
|
+
.catch(() => {
|
|
45
|
+
/* install downloads again and says why it failed */
|
|
46
|
+
}));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/** Runs the steps (each with its own environment) and returns each step's environment, in plan order. */
|
|
50
|
+
async run(steps, o) {
|
|
51
|
+
const envs = new Map(steps.map((s) => [s.name, emptyEnv()]));
|
|
52
|
+
const finished = new Map();
|
|
53
|
+
const running = new Set();
|
|
54
|
+
const waiting = [];
|
|
55
|
+
let done = 0;
|
|
56
|
+
// The boot (no `report`) also publishes structured progress: what runs now, how far its download is, how many are
|
|
57
|
+
// done.
|
|
58
|
+
const percent = new Map();
|
|
59
|
+
const active = () => [...running].map((name) => ({ name, ...(percent.has(name) ? { percent: percent.get(name) } : {}) }));
|
|
60
|
+
const say = o.report ??
|
|
61
|
+
((message) => o.progress
|
|
62
|
+
? o.progress({ active: active(), done, total: steps.length })
|
|
63
|
+
: this.sink.status(message, { active: active(), done, total: steps.length }));
|
|
64
|
+
const report = () => {
|
|
65
|
+
if (running.size)
|
|
66
|
+
say('Installing ' + [...running].join(', ') + ' (' + done + ' of ' + steps.length + ' done)');
|
|
67
|
+
};
|
|
68
|
+
this.startFetches(steps.filter((s) => !(s.shared && o.reuse?.[s.name])));
|
|
69
|
+
if (!o.report)
|
|
70
|
+
downloader.watch(() => {
|
|
71
|
+
const name = this.currentStep.getStore();
|
|
72
|
+
return name
|
|
73
|
+
? (fraction) => {
|
|
74
|
+
percent.set(name, Math.round(fraction * 100));
|
|
75
|
+
report();
|
|
76
|
+
}
|
|
77
|
+
: null;
|
|
78
|
+
});
|
|
79
|
+
for (const s of steps) {
|
|
80
|
+
// Dependencies first (they are earlier in the plan, so their promises exist), then a free slot.
|
|
81
|
+
finished.set(s.name, (async () => {
|
|
82
|
+
await Promise.all((s.after ?? []).map((name) => finished.get(name)));
|
|
83
|
+
const env = envs.get(s.name);
|
|
84
|
+
const reused = s.shared ? o.reuse?.[s.name] : undefined;
|
|
85
|
+
if (reused) {
|
|
86
|
+
envs.set(s.name, structuredClone(reused));
|
|
87
|
+
done++;
|
|
88
|
+
o.onDone?.(s.name);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
// Its download (started above, before the dependencies finished) is in by now, or still coming.
|
|
92
|
+
await this.fetches.get(s.name);
|
|
93
|
+
this.fetches.delete(s.name);
|
|
94
|
+
while (running.size >= this.parallel)
|
|
95
|
+
await new Promise((r) => waiting.push(r));
|
|
96
|
+
running.add(s.name);
|
|
97
|
+
report();
|
|
98
|
+
try {
|
|
99
|
+
await this.currentStep.run(s.name, () => this.exclusive(s.name, () => runReported(this.sink, s.name, env, async () => s.install(env))));
|
|
100
|
+
}
|
|
101
|
+
finally {
|
|
102
|
+
running.delete(s.name);
|
|
103
|
+
percent.delete(s.name);
|
|
104
|
+
done++;
|
|
105
|
+
report();
|
|
106
|
+
waiting.shift()?.();
|
|
107
|
+
o.onDone?.(s.name);
|
|
108
|
+
}
|
|
109
|
+
})());
|
|
110
|
+
}
|
|
111
|
+
try {
|
|
112
|
+
await Promise.all(finished.values());
|
|
113
|
+
}
|
|
114
|
+
finally {
|
|
115
|
+
if (!o.report)
|
|
116
|
+
downloader.watch(null);
|
|
117
|
+
}
|
|
118
|
+
o.progress?.({ active: [], done, total: steps.length });
|
|
119
|
+
if (!o.report && !o.progress)
|
|
120
|
+
this.sink.status('Set up ' + steps.length + ' toolchains and databases', {
|
|
121
|
+
active: [],
|
|
122
|
+
done,
|
|
123
|
+
total: steps.length,
|
|
124
|
+
});
|
|
125
|
+
return envs;
|
|
126
|
+
}
|
|
127
|
+
/** Runs `work` once no other step of the same kind (first word of the name) is running. */
|
|
128
|
+
async exclusive(name, work) {
|
|
129
|
+
const key = name.split(' ')[0].toLowerCase();
|
|
130
|
+
const previous = this.locks.get(key) ?? Promise.resolve();
|
|
131
|
+
let release = () => undefined;
|
|
132
|
+
const mine = new Promise((r) => {
|
|
133
|
+
release = r;
|
|
134
|
+
});
|
|
135
|
+
const chained = previous.then(() => mine);
|
|
136
|
+
this.locks.set(key, chained);
|
|
137
|
+
await previous;
|
|
138
|
+
try {
|
|
139
|
+
return await work();
|
|
140
|
+
}
|
|
141
|
+
finally {
|
|
142
|
+
release();
|
|
143
|
+
if (this.locks.get(key) === chained)
|
|
144
|
+
this.locks.delete(key);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
@@ -11,6 +11,12 @@ export interface ToolchainOptions {
|
|
|
11
11
|
apps?: Config['apps'];
|
|
12
12
|
/** pcInstalls: this PC's games, engines and editors mapped read-only (session.json). */
|
|
13
13
|
pcInstalls?: PcInstallManifest;
|
|
14
|
+
/**
|
|
15
|
+
* Boot only: the heavy installs (Visual Studio, and what depends on it) run in the background after the agent has
|
|
16
|
+
* started. Off with a VPN or network.allowOnly (their firewall would cut the downloads), in a dry run and when a
|
|
17
|
+
* project joins a running sandbox.
|
|
18
|
+
*/
|
|
19
|
+
deferHeavy?: boolean;
|
|
14
20
|
}
|
|
15
21
|
/** What a toolchain module's step gets besides its settings. */
|
|
16
22
|
export interface ToolCtx {
|
|
@@ -30,5 +36,12 @@ export interface ToolchainStep {
|
|
|
30
36
|
* git guard): a project added after boot reuses what the boot's run contributed instead of running it again.
|
|
31
37
|
*/
|
|
32
38
|
readonly shared?: boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Download only what `install` will need (no-op when it is there). The scheduler starts every step's fetch at once,
|
|
41
|
+
* outside the step slots and `after`; only the net pool limits them. A failure is ignored: install downloads again.
|
|
42
|
+
*/
|
|
43
|
+
fetch?(): Promise<void>;
|
|
44
|
+
/** At boot, runs after the agent has started (the steps that depend on it follow it). */
|
|
45
|
+
readonly background?: boolean;
|
|
33
46
|
install(env: ToolEnv): Promise<void> | void;
|
|
34
47
|
}
|
|
@@ -94,6 +94,22 @@ const pointAtOwnCertificates = (root) => {
|
|
|
94
94
|
if (fixed !== text)
|
|
95
95
|
fs.writeFileSync(config, fixed);
|
|
96
96
|
};
|
|
97
|
+
/**
|
|
98
|
+
* Coreutils' link.exe (hard links) next to MSVC's linker: Git Bash puts its own /usr/bin first on PATH, whatever the
|
|
99
|
+
* Windows PATH says, so a msvc Rust build ran the wrong `link` (os error 1392). Renamed; nobody needs it here.
|
|
100
|
+
*/
|
|
101
|
+
const hideCoreutilsLink = (usrBin) => {
|
|
102
|
+
const link = path.join(usrBin, 'link.exe');
|
|
103
|
+
if (!exists(link))
|
|
104
|
+
return;
|
|
105
|
+
try {
|
|
106
|
+
fs.rmSync(path.join(usrBin, 'link-coreutils.exe'), { force: true });
|
|
107
|
+
fs.renameSync(link, path.join(usrBin, 'link-coreutils.exe'));
|
|
108
|
+
}
|
|
109
|
+
catch (e) {
|
|
110
|
+
warn("Could not rename Git's coreutils link.exe (MSVC link may be shadowed in Git Bash): " + errMsg(e));
|
|
111
|
+
}
|
|
112
|
+
};
|
|
97
113
|
export async function setupGit(env, hostCopy, refresh, access) {
|
|
98
114
|
const root = tool('git');
|
|
99
115
|
const bash = path.join(root, 'bin', 'bash.exe');
|
|
@@ -130,6 +146,8 @@ export async function setupGit(env, hostCopy, refresh, access) {
|
|
|
130
146
|
throw new Error('bash.exe missing after installing Git');
|
|
131
147
|
pointAtOwnCertificates(root);
|
|
132
148
|
env.vars.CLAUDE_CODE_GIT_BASH_PATH = bash;
|
|
149
|
+
// usr\bin is on PATH for Git's tools in cmd and PowerShell, last (path-plan.ts keeps MSVC before it).
|
|
150
|
+
hideCoreutilsLink(path.join(root, 'usr', 'bin'));
|
|
133
151
|
env.append.push(path.join(root, 'usr', 'bin'));
|
|
134
152
|
const version = execOutput(gitExe, ['--version']).replace(/^git version /, '');
|
|
135
153
|
if (access === 'full') {
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
import crypto from 'node:crypto';
|
|
5
5
|
import fs from 'node:fs';
|
|
6
6
|
import path from 'node:path';
|
|
7
|
-
import {
|
|
7
|
+
import { runInstaller } from '../runtime/pools.js';
|
|
8
|
+
import { downloader } from '../../shared/download/index.js';
|
|
8
9
|
import { SYSTEM } from '../../shared/system.js';
|
|
9
10
|
import { dryRun, isWin, toolsDir } from '../runtime/paths.js';
|
|
10
11
|
import { exists, mkdirp } from '../runtime/fs.js';
|
|
@@ -48,9 +49,7 @@ export async function ensureOpenVpn() {
|
|
|
48
49
|
repairServiceQueries();
|
|
49
50
|
log('VPN: installing OpenVPN');
|
|
50
51
|
const logFile = path.join(toolsDir, 'downloads', 'openvpn-install.log');
|
|
51
|
-
const code = await
|
|
52
|
-
stdio: 'ignore',
|
|
53
|
-
});
|
|
52
|
+
const code = await runInstaller(SYSTEM.msiexec(), ['/i', msi, '/qn', '/norestart', '/l*v', logFile]);
|
|
54
53
|
// 3010: installed, a restart is wanted (nothing here needs one).
|
|
55
54
|
if (code !== 0 && code !== 3010)
|
|
56
55
|
throw new Error('The OpenVPN installer failed (exit ' + code + '). Its log: ' + logFile);
|
|
@@ -7,7 +7,8 @@ import crypto from 'node:crypto';
|
|
|
7
7
|
import fs from 'node:fs';
|
|
8
8
|
import os from 'node:os';
|
|
9
9
|
import path from 'node:path';
|
|
10
|
-
import {
|
|
10
|
+
import { runInstaller } from '../runtime/pools.js';
|
|
11
|
+
import { downloader } from '../../shared/download/index.js';
|
|
11
12
|
import { SYSTEM } from '../../shared/system.js';
|
|
12
13
|
import { WIREGUARD_MSI } from '../../shared/vpn/download.js';
|
|
13
14
|
import { handshakeDone, serviceRunning } from '../../shared/vpn/wireguard.js';
|
|
@@ -46,9 +47,15 @@ export async function ensureWireguard() {
|
|
|
46
47
|
log('VPN: installing WireGuard');
|
|
47
48
|
const logFile = path.join(toolsDir, 'downloads', 'wireguard-install.log');
|
|
48
49
|
// DO_NOT_LAUNCH: the installer would otherwise start the manager window.
|
|
49
|
-
const code = await
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
const code = await runInstaller(SYSTEM.msiexec(), [
|
|
51
|
+
'/i',
|
|
52
|
+
msi,
|
|
53
|
+
'/qn',
|
|
54
|
+
'/norestart',
|
|
55
|
+
'DO_NOT_LAUNCH=1',
|
|
56
|
+
'/l*v',
|
|
57
|
+
logFile,
|
|
58
|
+
]);
|
|
52
59
|
if (code !== 0 && code !== 3010)
|
|
53
60
|
throw new Error('The WireGuard installer failed (exit ' + code + '). Its log: ' + logFile);
|
|
54
61
|
if (!exists(exe))
|
|
@@ -9,6 +9,8 @@ function howToSave(spec) {
|
|
|
9
9
|
return "sign in to the Epic Games Launcher in this sandbox once, or take this PC's: sandbox account import epic";
|
|
10
10
|
if (spec.kind === 'steam')
|
|
11
11
|
return 'sign in once in this sandbox with sbx-steam login';
|
|
12
|
+
if (spec.kind === 'comet')
|
|
13
|
+
return 'sign in to Comet once in this sandbox, and it is kept for the next ones';
|
|
12
14
|
if (spec.returns)
|
|
13
15
|
return 'sign in once in this sandbox, or: sandbox account import ' + spec.kind;
|
|
14
16
|
return spec.sources.includes('typed')
|
|
@@ -56,6 +58,11 @@ export function prepareServiceAccounts(i) {
|
|
|
56
58
|
if (spec.returns)
|
|
57
59
|
leased.push(kind);
|
|
58
60
|
}
|
|
61
|
+
else if (spec.returns) {
|
|
62
|
+
// Several sandboxes may use it at once: each gets its own lease, so each may hand a renewed one back.
|
|
63
|
+
leases.claimShared(kind, dataSet, i.launchId, payload ? fingerprint(payload) : null);
|
|
64
|
+
leased.push(kind);
|
|
65
|
+
}
|
|
59
66
|
if (unreadable) {
|
|
60
67
|
notes.push('Your saved ' +
|
|
61
68
|
spec.label +
|
|
@@ -61,12 +61,8 @@ export function takeHandoffs(dataPath, final, store = new ServiceAccountStore(st
|
|
|
61
61
|
const spec = SERVICE_SPECS[kind];
|
|
62
62
|
const bytes = readBoundedFile(file, spec.maxBytes + ENVELOPE_BYTES);
|
|
63
63
|
const parsed = bytes ? handoffSchema(kind).safeParse(JSON.parse(bytes.toString('utf8'))) : null;
|
|
64
|
-
const lease = leases.
|
|
65
|
-
if (parsed?.success &&
|
|
66
|
-
spec.returns &&
|
|
67
|
-
lease &&
|
|
68
|
-
lease.dataSet === dataSet &&
|
|
69
|
-
lease.launchId === parsed.data.launchId) {
|
|
64
|
+
const lease = leases.leaseOf(kind, dataSet);
|
|
65
|
+
if (parsed?.success && spec.returns && lease && lease.launchId === parsed.data.launchId) {
|
|
70
66
|
const payload = parsed.data.payload;
|
|
71
67
|
store.save(kind, payload, 'sandbox', HOST_SOURCES[kind].summary(payload));
|
|
72
68
|
ok = true;
|
|
@@ -2,5 +2,5 @@ export { accountsDir, ServiceAccountStore, type AccountInfo, type AccountState }
|
|
|
2
2
|
export { AccountLeases, type ClaimResult, type Lease } from './leases.js';
|
|
3
3
|
export { prepareServiceAccounts, type AccountDelivery, type DeliveryInput } from './deliver.js';
|
|
4
4
|
export { forgetEverywhere, handoffDir, takeHandoffs } from './handoff.js';
|
|
5
|
-
export { notAvailableYet, pcEnv, type HostSource, type PcEnv } from './source.js';
|
|
5
|
+
export { notAvailableYet, parseRegValue, pcEnv, type HostSource, type PcEnv } from './source.js';
|
|
6
6
|
export { HOST_SOURCES } from './sources.js';
|
|
@@ -4,5 +4,5 @@ export { accountsDir, ServiceAccountStore } from './store.js';
|
|
|
4
4
|
export { AccountLeases } from './leases.js';
|
|
5
5
|
export { prepareServiceAccounts } from './deliver.js';
|
|
6
6
|
export { forgetEverywhere, handoffDir, takeHandoffs } from './handoff.js';
|
|
7
|
-
export { notAvailableYet, pcEnv } from './source.js';
|
|
7
|
+
export { notAvailableYet, parseRegValue, pcEnv } from './source.js';
|
|
8
8
|
export { HOST_SOURCES } from './sources.js';
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { HostSource } from '../source.js';
|
|
2
2
|
export declare const cfxSource: HostSource<'cfx'>;
|
|
@@ -1,7 +1,50 @@
|
|
|
1
|
-
|
|
1
|
+
// Cfx.re: a FiveM / RedM server licence key from the keymaster (https://portal.cfx.re), and an optional Steam Web API
|
|
2
|
+
// key. Typed with `sandbox account login cfx`, or read from a server.cfg with `sandbox account import cfx --from`.
|
|
3
|
+
import { CfxPayloadSchema } from '../../../shared/accounts/index.js';
|
|
4
|
+
import { fail } from '../../../core/util/index.js';
|
|
5
|
+
import { decodeText } from '../../../shared/io/index.js';
|
|
6
|
+
const MAX_LINES = 20_000;
|
|
7
|
+
const LICENSE_LINE = /^\s*(?:set[rs]?\s+)?sv_licenseKey\s+"?([^"\s]+)"?/i;
|
|
8
|
+
const STEAM_LINE = /^\s*set[rs]?\s+steam_webApiKey\s+"?([A-Fa-f0-9]{32})"?/i;
|
|
9
|
+
/** The line without a `#` or `//` comment (quotes are respected). */
|
|
10
|
+
function withoutComment(line) {
|
|
11
|
+
let quoted = false;
|
|
12
|
+
for (let i = 0; i < line.length; i++) {
|
|
13
|
+
const c = line[i];
|
|
14
|
+
if (c === '"')
|
|
15
|
+
quoted = !quoted;
|
|
16
|
+
else if (!quoted && (c === '#' || (c === '/' && line[i + 1] === '/')))
|
|
17
|
+
return line.slice(0, i);
|
|
18
|
+
}
|
|
19
|
+
return line;
|
|
20
|
+
}
|
|
2
21
|
export const cfxSource = {
|
|
3
|
-
|
|
4
|
-
|
|
22
|
+
fromFile(bytes) {
|
|
23
|
+
let licenseKey = '';
|
|
24
|
+
let steamWebApiKey = '';
|
|
25
|
+
for (const raw of decodeText(bytes).text.split(/\r?\n/, MAX_LINES)) {
|
|
26
|
+
const line = withoutComment(raw);
|
|
27
|
+
const key = LICENSE_LINE.exec(line);
|
|
28
|
+
if (key)
|
|
29
|
+
licenseKey = key[1];
|
|
30
|
+
const steam = STEAM_LINE.exec(line);
|
|
31
|
+
if (steam)
|
|
32
|
+
steamWebApiKey = steam[1].toUpperCase();
|
|
33
|
+
}
|
|
34
|
+
if (!licenseKey)
|
|
35
|
+
fail('That server.cfg has no sv_licenseKey.', 'Type the key instead: sandbox account login cfx');
|
|
36
|
+
const parsed = CfxPayloadSchema.safeParse({ licenseKey, ...(steamWebApiKey ? { steamWebApiKey } : {}) });
|
|
37
|
+
if (!parsed.success)
|
|
38
|
+
fail('The sv_licenseKey in that server.cfg is not a Cfx.re server key (cfxk_...).', 'Copy the key from https://portal.cfx.re (Server keys) and run: sandbox account login cfx');
|
|
39
|
+
return parsed.data;
|
|
5
40
|
},
|
|
6
|
-
|
|
41
|
+
fromAnswers(answers) {
|
|
42
|
+
const licenseKey = (answers.licenseKey ?? '').trim();
|
|
43
|
+
const steamWebApiKey = (answers.steamWebApiKey ?? '').trim().toUpperCase();
|
|
44
|
+
const parsed = CfxPayloadSchema.safeParse({ licenseKey, ...(steamWebApiKey ? { steamWebApiKey } : {}) });
|
|
45
|
+
if (!parsed.success)
|
|
46
|
+
fail('That is not a Cfx.re server key (cfxk_...) or a Steam Web API key (32 hex characters).', 'Copy the key from https://portal.cfx.re (Server keys) and run: sandbox account login cfx');
|
|
47
|
+
return parsed.data;
|
|
48
|
+
},
|
|
49
|
+
summary: (p) => 'server key' + (p.steamWebApiKey ? ' and Steam Web API key' : ''),
|
|
7
50
|
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// Comet: nothing to take from this PC. Its sign-in there is sealed to your Windows account (and reading another
|
|
2
|
+
// browser's cookies is what password stealers do), so it only ever comes from a sandbox you signed in in.
|
|
3
|
+
import { cometSummary } from '../../../shared/accounts/index.js';
|
|
4
|
+
export const cometSource = {
|
|
5
|
+
summary: (p) => cometSummary(p),
|
|
6
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// Perplexity: an API key, taken from this PC (`sandbox account import perplexity`: PERPLEXITY_API_KEY of this
|
|
2
|
+
// terminal, else of your saved Windows user environment), from a file that holds it (`--from .env`: only its
|
|
3
|
+
// PERPLEXITY_API_KEY line is read), or typed (`sandbox account login perplexity`). The key is never printed.
|
|
4
|
+
import { fail } from '../../../core/util/index.js';
|
|
5
|
+
import { PERPLEXITY_ENV, PerplexityPayloadSchema, perplexityKeyFromText, perplexitySummary, } from '../../../shared/accounts/index.js';
|
|
6
|
+
import { decodeText } from '../../../shared/io/index.js';
|
|
7
|
+
/** A key value as it may sit in a variable (quotes and spaces around it taken off). */
|
|
8
|
+
const keyFrom = (value) => {
|
|
9
|
+
const parsed = PerplexityPayloadSchema.safeParse({ apiKey: (value ?? '').trim().replace(/^(["'])(.*)\1$/, '$2') });
|
|
10
|
+
return parsed.success ? parsed.data : null;
|
|
11
|
+
};
|
|
12
|
+
export const perplexitySource = {
|
|
13
|
+
/** PERPLEXITY_API_KEY of this PC: this terminal's environment first, then your saved user environment. */
|
|
14
|
+
fromPc(pc) {
|
|
15
|
+
return keyFrom(pc.env?.[PERPLEXITY_ENV]) ?? keyFrom(pc.userVariable?.(PERPLEXITY_ENV));
|
|
16
|
+
},
|
|
17
|
+
fromFile(bytes) {
|
|
18
|
+
const key = perplexityKeyFromText(decodeText(bytes).text);
|
|
19
|
+
if (!key)
|
|
20
|
+
fail('That file holds no Perplexity API key.', 'Name a .env file with a line PERPLEXITY_API_KEY=pplx-..., or type the key: sandbox account login perplexity');
|
|
21
|
+
return key;
|
|
22
|
+
},
|
|
23
|
+
fromAnswers(answers) {
|
|
24
|
+
const parsed = PerplexityPayloadSchema.safeParse({ apiKey: (answers.apiKey ?? '').trim() });
|
|
25
|
+
if (!parsed.success)
|
|
26
|
+
fail('That is not a Perplexity API key.', 'Copy it from perplexity.ai/account/api/keys: it starts with pplx-.');
|
|
27
|
+
return parsed.data;
|
|
28
|
+
},
|
|
29
|
+
summary: (p) => perplexitySummary(p),
|
|
30
|
+
};
|