summaryrefslogtreecommitdiff
path: root/packages/cli/src/config/sandboxConfig.ts
diff options
context:
space:
mode:
authorBrandon Keiji <[email protected]>2025-06-18 10:01:00 -0700
committerGitHub <[email protected]>2025-06-18 17:01:00 +0000
commit332512853e40e6c9b826b60057a389e9d34453fd (patch)
tree7ee45ec5d7f27cb5664c353379580133b223da77 /packages/cli/src/config/sandboxConfig.ts
parent30d1662128e688bf94653a0144ef96c311fae40b (diff)
feat: consolidate sandbox configurations into a single object (#1154)
Diffstat (limited to 'packages/cli/src/config/sandboxConfig.ts')
-rw-r--r--packages/cli/src/config/sandboxConfig.ts102
1 files changed, 102 insertions, 0 deletions
diff --git a/packages/cli/src/config/sandboxConfig.ts b/packages/cli/src/config/sandboxConfig.ts
new file mode 100644
index 00000000..69a54900
--- /dev/null
+++ b/packages/cli/src/config/sandboxConfig.ts
@@ -0,0 +1,102 @@
+/**
+ * @license
+ * Copyright 2025 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import { SandboxConfig } from '@gemini-cli/core';
+import commandExists from 'command-exists';
+import * as os from 'node:os';
+import { getPackageJson } from '../utils/package.js';
+import { Settings } from './settings.js';
+
+// This is a stripped-down version of the CliArgs interface from config.ts
+// to avoid circular dependencies.
+interface SandboxCliArgs {
+ sandbox?: boolean | string;
+ 'sandbox-image'?: string;
+}
+
+const VALID_SANDBOX_COMMANDS: ReadonlyArray<SandboxConfig['command']> = [
+ 'docker',
+ 'podman',
+ 'sandbox-exec',
+];
+
+function isSandboxCommand(value: string): value is SandboxConfig['command'] {
+ return (VALID_SANDBOX_COMMANDS as readonly string[]).includes(value);
+}
+
+function getSandboxCommand(
+ sandbox?: boolean | string,
+): SandboxConfig['command'] | '' {
+ // note environment variable takes precedence over argument (from command line or settings)
+ sandbox = process.env.GEMINI_SANDBOX?.toLowerCase().trim() ?? sandbox;
+ if (sandbox === '1' || sandbox === 'true') sandbox = true;
+ else if (sandbox === '0' || sandbox === 'false') sandbox = false;
+
+ if (sandbox === false) {
+ return '';
+ }
+
+ if (typeof sandbox === 'string' && sandbox !== '') {
+ if (!isSandboxCommand(sandbox)) {
+ console.error(
+ `ERROR: invalid sandbox command '${sandbox}'. Must be one of ${VALID_SANDBOX_COMMANDS.join(
+ ', ',
+ )}`,
+ );
+ process.exit(1);
+ }
+ // confirm that specfied command exists
+ if (commandExists.sync(sandbox)) {
+ return sandbox;
+ }
+ console.error(
+ `ERROR: missing sandbox command '${sandbox}' (from GEMINI_SANDBOX)`,
+ );
+ process.exit(1);
+ }
+
+ // look for seatbelt, docker, or podman, in that order
+ // for container-based sandboxing, require sandbox to be enabled explicitly
+ if (os.platform() === 'darwin' && commandExists.sync('sandbox-exec')) {
+ return 'sandbox-exec';
+ } else if (commandExists.sync('docker') && sandbox === true) {
+ return 'docker';
+ } else if (commandExists.sync('podman') && sandbox === true) {
+ return 'podman';
+ }
+
+ // throw an error if user requested sandbox but no command was found
+ if (sandbox === true) {
+ console.error(
+ 'ERROR: GEMINI_SANDBOX is true but failed to determine command for sandbox; ' +
+ 'install docker or podman or specify command in GEMINI_SANDBOX',
+ );
+ process.exit(1);
+ }
+
+ return '';
+}
+
+export async function loadSandboxConfig(
+ settings: Settings,
+ argv: SandboxCliArgs,
+): Promise<SandboxConfig | undefined> {
+ const sandboxOption = argv.sandbox ?? settings.sandbox;
+ const sandboxCommand = getSandboxCommand(sandboxOption);
+ if (!sandboxCommand) {
+ return undefined;
+ }
+
+ const packageJson = await getPackageJson();
+ return {
+ command: sandboxCommand,
+ image:
+ argv['sandbox-image'] ??
+ process.env.GEMINI_SANDBOX_IMAGE ??
+ packageJson?.config?.sandboxImageUri ??
+ 'gemini-cli-sandbox',
+ };
+}