Add env var to configure system settings path (#4339)

This commit is contained in:
christine betts 2025-07-21 20:14:07 +00:00 committed by GitHub
parent 74d0f4c79f
commit f95674e646
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 60 additions and 15 deletions

View File

@ -24,7 +24,7 @@ Gemini CLI uses `settings.json` files for persistent configuration. There are th
- **Location:** `.gemini/settings.json` within your project's root directory.
- **Scope:** Applies only when running Gemini CLI from that specific project. Project settings override user settings.
- **System settings file:**
- **Location:** `/etc/gemini-cli/settings.json` (Linux), `C:\ProgramData\gemini-cli\settings.json` (Windows) or `/Library/Application Support/GeminiCli/settings.json` (macOS).
- **Location:** `/etc/gemini-cli/settings.json` (Linux), `C:\ProgramData\gemini-cli\settings.json` (Windows) or `/Library/Application Support/GeminiCli/settings.json` (macOS). The path can be overridden using the `GEMINI_CLI_SYSTEM_SETTINGS_PATH` environment variable.
- **Scope:** Applies to all Gemini CLI sessions on the system, for all users. System settings override user and project settings. May be useful for system administrators at enterprises to have controls over users' Gemini CLI setups.
**Note on environment variables in settings:** String values within your `settings.json` files can reference environment variables using either `$VAR_NAME` or `${VAR_NAME}` syntax. These variables will be automatically resolved when the settings are loaded. For example, if you have an environment variable `MY_API_TOKEN`, you could use it in `settings.json` like this: `"apiKey": "$MY_API_TOKEN"`.

View File

@ -46,7 +46,7 @@ import stripJsonComments from 'strip-json-comments'; // Will be mocked separatel
import {
loadSettings,
USER_SETTINGS_PATH, // This IS the mocked path.
SYSTEM_SETTINGS_PATH,
getSystemSettingsPath,
SETTINGS_DIRECTORY_NAME, // This is from the original module, but used by the mock.
SettingScope,
} from './settings.js';
@ -104,7 +104,7 @@ describe('Settings Loading and Merging', () => {
it('should load system settings if only system file exists', () => {
(mockFsExistsSync as Mock).mockImplementation(
(p: fs.PathLike) => p === SYSTEM_SETTINGS_PATH,
(p: fs.PathLike) => p === getSystemSettingsPath(),
);
const systemSettingsContent = {
theme: 'system-default',
@ -112,7 +112,7 @@ describe('Settings Loading and Merging', () => {
};
(fs.readFileSync as Mock).mockImplementation(
(p: fs.PathOrFileDescriptor) => {
if (p === SYSTEM_SETTINGS_PATH)
if (p === getSystemSettingsPath())
return JSON.stringify(systemSettingsContent);
return '{}';
},
@ -121,7 +121,7 @@ describe('Settings Loading and Merging', () => {
const settings = loadSettings(MOCK_WORKSPACE_DIR);
expect(fs.readFileSync).toHaveBeenCalledWith(
SYSTEM_SETTINGS_PATH,
getSystemSettingsPath(),
'utf-8',
);
expect(settings.system.settings).toEqual(systemSettingsContent);
@ -257,7 +257,7 @@ describe('Settings Loading and Merging', () => {
(fs.readFileSync as Mock).mockImplementation(
(p: fs.PathOrFileDescriptor) => {
if (p === SYSTEM_SETTINGS_PATH)
if (p === getSystemSettingsPath())
return JSON.stringify(systemSettingsContent);
if (p === USER_SETTINGS_PATH)
return JSON.stringify(userSettingsContent);
@ -743,7 +743,7 @@ describe('Settings Loading and Merging', () => {
(fs.readFileSync as Mock).mockImplementation(
(p: fs.PathOrFileDescriptor) => {
if (p === SYSTEM_SETTINGS_PATH) {
if (p === getSystemSettingsPath()) {
process.env.SHARED_VAR = 'system_value_for_system_read'; // Set for system settings read
return JSON.stringify(systemSettingsContent);
}
@ -913,6 +913,50 @@ describe('Settings Loading and Merging', () => {
delete process.env.TEST_HOST;
delete process.env.TEST_PORT;
});
describe('when GEMINI_CLI_SYSTEM_SETTINGS_PATH is set', () => {
const MOCK_ENV_SYSTEM_SETTINGS_PATH = '/mock/env/system/settings.json';
beforeEach(() => {
process.env.GEMINI_CLI_SYSTEM_SETTINGS_PATH =
MOCK_ENV_SYSTEM_SETTINGS_PATH;
});
afterEach(() => {
delete process.env.GEMINI_CLI_SYSTEM_SETTINGS_PATH;
});
it('should load system settings from the path specified in the environment variable', () => {
(mockFsExistsSync as Mock).mockImplementation(
(p: fs.PathLike) => p === MOCK_ENV_SYSTEM_SETTINGS_PATH,
);
const systemSettingsContent = {
theme: 'env-var-theme',
sandbox: true,
};
(fs.readFileSync as Mock).mockImplementation(
(p: fs.PathOrFileDescriptor) => {
if (p === MOCK_ENV_SYSTEM_SETTINGS_PATH)
return JSON.stringify(systemSettingsContent);
return '{}';
},
);
const settings = loadSettings(MOCK_WORKSPACE_DIR);
expect(fs.readFileSync).toHaveBeenCalledWith(
MOCK_ENV_SYSTEM_SETTINGS_PATH,
'utf-8',
);
expect(settings.system.path).toBe(MOCK_ENV_SYSTEM_SETTINGS_PATH);
expect(settings.system.settings).toEqual(systemSettingsContent);
expect(settings.merged).toEqual({
...systemSettingsContent,
customThemes: {},
mcpServers: {},
});
});
});
});
describe('LoadedSettings class', () => {

View File

@ -25,7 +25,10 @@ export const SETTINGS_DIRECTORY_NAME = '.gemini';
export const USER_SETTINGS_DIR = path.join(homedir(), SETTINGS_DIRECTORY_NAME);
export const USER_SETTINGS_PATH = path.join(USER_SETTINGS_DIR, 'settings.json');
function getSystemSettingsPath(): string {
export function getSystemSettingsPath(): string {
if (process.env.GEMINI_CLI_SYSTEM_SETTINGS_PATH) {
return process.env.GEMINI_CLI_SYSTEM_SETTINGS_PATH;
}
if (platform() === 'darwin') {
return '/Library/Application Support/GeminiCli/settings.json';
} else if (platform() === 'win32') {
@ -35,8 +38,6 @@ function getSystemSettingsPath(): string {
}
}
export const SYSTEM_SETTINGS_PATH = getSystemSettingsPath();
export enum SettingScope {
User = 'User',
Workspace = 'Workspace',
@ -297,11 +298,11 @@ export function loadSettings(workspaceDir: string): LoadedSettings {
let userSettings: Settings = {};
let workspaceSettings: Settings = {};
const settingsErrors: SettingsError[] = [];
const systemSettingsPath = getSystemSettingsPath();
// Load system settings
try {
if (fs.existsSync(SYSTEM_SETTINGS_PATH)) {
const systemContent = fs.readFileSync(SYSTEM_SETTINGS_PATH, 'utf-8');
if (fs.existsSync(systemSettingsPath)) {
const systemContent = fs.readFileSync(systemSettingsPath, 'utf-8');
const parsedSystemSettings = JSON.parse(
stripJsonComments(systemContent),
) as Settings;
@ -310,7 +311,7 @@ export function loadSettings(workspaceDir: string): LoadedSettings {
} catch (error: unknown) {
settingsErrors.push({
message: getErrorMessage(error),
path: SYSTEM_SETTINGS_PATH,
path: systemSettingsPath,
});
}
@ -368,7 +369,7 @@ export function loadSettings(workspaceDir: string): LoadedSettings {
return new LoadedSettings(
{
path: SYSTEM_SETTINGS_PATH,
path: systemSettingsPath,
settings: systemSettings,
},
{