Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 86 additions & 0 deletions patches/remote-secret-storage.diff
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
AI: store secrets on remote for VS Code Web
Index: code-server/lib/vscode/src/vs/platform/secrets/common/secrets.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/platform/secrets/common/secrets.ts
+++ code-server/lib/vscode/src/vs/platform/secrets/common/secrets.ts
@@ -139,8 +139,13 @@ export class BaseSecretStorageService ex
return await readEncryptedSecret(
key,
(fullKey) => this.getValueFromStorage(key, fullKey, storageService),
- // If the storage service is in-memory, we don't need to decrypt
- this._type === 'in-memory' ? (v) => Promise.resolve(v) : (v) => this._encryptionService.decrypt(v),
+ // Don't decrypt if storage is in-memory or if encryption service is not available
+ async (v) => {
+ if (this._type === 'in-memory' || !await this._encryptionService.isEncryptionAvailable()) {
+ return v;
+ }
+ return this._encryptionService.decrypt(v);
+ },
this._logService,
);
} catch (e) {
@@ -160,8 +165,13 @@ export class BaseSecretStorageService ex
key,
value,
(fullKey, encrypted) => this.setValueInStorage(key, fullKey, encrypted, storageService),
- // If the storage service is in-memory, we don't need to encrypt
- this._type === 'in-memory' ? (v) => Promise.resolve(v) : (v) => this._encryptionService.encrypt(v),
+ // Don't encrypt if storage is in-memory or if encryption service is not available
+ async (v) => {
+ if (this._type === 'in-memory' || !await this._encryptionService.isEncryptionAvailable()) {
+ return v;
+ }
+ return this._encryptionService.encrypt(v);
+ },
this._logService,
);
} catch (e) {
@@ -194,8 +204,9 @@ export class BaseSecretStorageService ex

private async initialize(): Promise<IStorageService> {
let storageService;
- if (!this._useInMemoryStorage && await this._encryptionService.isEncryptionAvailable()) {
- this._logService.trace(`[SecretStorageService] Encryption is available, using persisted storage`);
+ if (!this._useInMemoryStorage) {
+ // Use persisted storage when not forced to use in-memory
+ this._logService.trace(`[SecretStorageService] Using persisted storage`);
this._type = 'persisted';
storageService = this._storageService;
} else {
@@ -203,7 +214,7 @@ export class BaseSecretStorageService ex
if (this._type === 'in-memory') {
return this._storageService;
}
- this._logService.trace('[SecretStorageService] Encryption is not available, falling back to in-memory storage');
+ this._logService.trace('[SecretStorageService] Falling back to in-memory storage');
this._type = 'in-memory';
storageService = this._register(new InMemoryStorageService());
}
Index: code-server/lib/vscode/src/vs/workbench/services/secrets/browser/secretStorageService.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/workbench/services/secrets/browser/secretStorageService.ts
+++ code-server/lib/vscode/src/vs/workbench/services/secrets/browser/secretStorageService.ts
@@ -22,9 +22,9 @@ export class BrowserSecretStorageService
@IBrowserWorkbenchEnvironmentService environmentService: IBrowserWorkbenchEnvironmentService,
@ILogService logService: ILogService
) {
- // We don't have encryption in the browser so instead we use the
- // in-memory base class implementation instead.
- super(true, storageService, encryptionService, logService);
+ // Use remote storage if enabled, otherwise use in-memory storage
+ const useRemoteStorage = environmentService.options?.remoteStorageEnabled ?? false;
+ super(!useRemoteStorage, storageService, encryptionService, logService);

if (environmentService.options?.secretStorageProvider) {
this._secretStorageProvider = environmentService.options.secretStorageProvider;
Index: code-server/lib/vscode/src/vs/code/browser/workbench/workbench.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/code/browser/workbench/workbench.ts
+++ code-server/lib/vscode/src/vs/code/browser/workbench/workbench.ts
@@ -625,2 +625,2 @@
- secretStorageProvider: config.remoteAuthority && !secretStorageKeyPath
- ? undefined /* with a remote without embedder-preferred storage, store on the remote */
+ secretStorageProvider: config.remoteStorageEnabled || (config.remoteAuthority && !secretStorageKeyPath)
+ ? undefined /* with remote storage enabled, or without embedder-prefered storage, store on the remote */
});
})();
122 changes: 122 additions & 0 deletions patches/remote-storage.diff
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
Reuse VS Code's Electron storage service for remote persistence

--- code-server.orig/lib/vscode/src/vs/platform/storage/electron-main/storageMainService.ts
+++ code-server/lib/vscode/src/vs/platform/storage/electron-main/storageMainService.ts
@@ -14 +14,6 @@
-import { ILifecycleMainService, LifecycleMainPhase, ShutdownReason } from '../../lifecycle/electron-main/lifecycleMainService.js';
+import type { ILifecycleMainService, ShutdownReason } from '../../lifecycle/electron-main/lifecycleMainService.js';
+
+// Keep these values local so the server does not load lifecycleMainService at runtime;
+// that module imports Electron, which is not present in the code-server server image.
+const LIFECYCLE_MAIN_PHASE_AFTER_WINDOW_OPEN = 3;
+const SHUTDOWN_REASON_KILL = 2;
@@ -101 +101 @@
- @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService,
+ private readonly lifecycleMainService: ILifecycleMainService,
@@ -117 +117 @@
- await this.lifecycleMainService.when(LifecycleMainPhase.AfterWindowOpen);
+ await this.lifecycleMainService.when(LIFECYCLE_MAIN_PHASE_AFTER_WINDOW_OPEN);
@@ -256 +256 @@
- if (this.shutdownReason === ShutdownReason.KILL) {
+ if (this.shutdownReason === SHUTDOWN_REASON_KILL) {
@@ -295 +295 @@
- if (this.shutdownReason === ShutdownReason.KILL) {
+ if (this.shutdownReason === SHUTDOWN_REASON_KILL) {

--- code-server.orig/lib/vscode/src/vs/workbench/services/storage/electron-browser/storageService.ts
+++ code-server/lib/vscode/src/vs/workbench/services/storage/electron-browser/storageService.ts
@@ -6 +6 @@
-import { IMainProcessService } from '../../../../platform/ipc/common/mainProcessService.js';
+import { IRemoteService } from '../../../../platform/ipc/common/services.js';
@@ -22 +22 @@
- mainProcessService: IMainProcessService,
+ remoteService: Pick<IRemoteService, 'getChannel'>,
@@ -25 +25 @@
- super(workspace, { currentProfile: userDataProfileService.currentProfile, defaultProfile: userDataProfilesService.defaultProfile }, mainProcessService, workbenchEnvironmentService);
+ super(workspace, { currentProfile: userDataProfileService.currentProfile, defaultProfile: userDataProfilesService.defaultProfile }, remoteService as IRemoteService, workbenchEnvironmentService);
--- code-server.orig/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
+++ code-server/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
@@ -68,0 +69 @@
+ 'enable-remote-storage': { type: 'boolean', cat: 'o', description: nls.localize('enable-remote-storage', 'Persist browser storage (settings, state, etc.) on the server instead of browser IndexedDB. Enables state portability across browsers/devices.') },
@@ -214,0 +216 @@
+ 'enable-remote-storage'?: boolean;
--- code-server.orig/lib/vscode/src/vs/server/node/serverServices.ts
+++ code-server/lib/vscode/src/vs/server/node/serverServices.ts
@@ -107,0 +108,4 @@
+import { StorageDatabaseChannel } from '../../platform/storage/electron-main/storageIpc.js';
+import { StorageMainService } from '../../platform/storage/electron-main/storageMainService.js';
+import type { ILifecycleMainService } from '../../platform/lifecycle/electron-main/lifecycleMainService.js';
+import type { IUserDataProfilesMainService } from '../../platform/userDataProfile/electron-main/userDataProfile.js';
@@ -240,0 +245,15 @@
+ // Reuse the Electron storage service on the remote. The server has no
+ // windows, so its lifecycle is always ready and never loads a window.
+ const storageMainService = disposables.add(new StorageMainService(
+ logService,
+ environmentService,
+ userDataProfilesService as unknown as IUserDataProfilesMainService,
+ {
+ when: () => Promise.resolve(),
+ onWillLoadWindow: Event.None,
+ onWillShutdown: Event.None,
+ } as unknown as ILifecycleMainService,
+ fileService,
+ uriIdentityService,
+ ));
+
@@ -419,6 +439,7 @@ export async function setupServerServices(connectionToken: ServerConnectionToken

const languagePackChannel = ProxyChannel.fromService<RemoteAgentConnectionContext>(accessor.get(ILanguagePackService), disposables);
socketServer.registerChannel('languagePacks', languagePackChannel);
+ socketServer.registerChannel('storage', disposables.add(new StorageDatabaseChannel(logService, storageMainService)));

// clean up extensions folder
remoteExtensionsScanner.whenExtensionsReady().then(() => extensionManagementService.cleanUp());
--- code-server.orig/lib/vscode/src/vs/server/node/webClientServer.ts
+++ code-server/lib/vscode/src/vs/server/node/webClientServer.ts
@@ -411 +411,2 @@
- callbackRoute: callbackRoute
+ callbackRoute: callbackRoute,
+ remoteStorageEnabled: this._environmentService.args['enable-remote-storage'] ? true : undefined
--- code-server.orig/lib/vscode/src/vs/workbench/browser/web.api.ts
+++ code-server/lib/vscode/src/vs/workbench/browser/web.api.ts
@@ -413,0 +414,5 @@
+ /**
+ * Persist workbench storage on the remote server instead of browser IndexedDB.
+ */
+ readonly remoteStorageEnabled?: boolean;
+
--- code-server.orig/lib/vscode/src/vs/workbench/browser/web.main.ts
+++ code-server/lib/vscode/src/vs/workbench/browser/web.main.ts
@@ -35,0 +36 @@
+import { NativeWorkbenchStorageService } from '../services/storage/electron-browser/storageService.js';
@@ -479 +480 @@
- this.createStorageService(workspace, logService, userDataProfileService).then(service => {
+ this.createStorageService(workspace, environmentService, logService, userDataProfileService, userDataProfilesService, remoteAgentService).then(service => {
@@ -601 +602,15 @@
- protected async createStorageService(workspace: IAnyWorkspaceIdentifier, logService: ILogService, userDataProfileService: IUserDataProfileService): Promise<IStorageService> {
+ protected async createStorageService(workspace: IAnyWorkspaceIdentifier, environmentService: IBrowserWorkbenchEnvironmentService, logService: ILogService, userDataProfileService: IUserDataProfileService, userDataProfilesService: IUserDataProfilesService, remoteAgentService: IRemoteAgentService): Promise<IStorageService> {
+ const connection = remoteAgentService.getConnection();
+ if (this.configuration.remoteStorageEnabled && connection) {
+ const storageService = new NativeWorkbenchStorageService(workspace, userDataProfileService, userDataProfilesService, connection, environmentService);
+
+ try {
+ await storageService.initialize();
+ this.onWillShutdownDisposables.add(toDisposable(() => storageService.close()));
+ return storageService;
+ } catch (error) {
+ onUnexpectedError(error);
+ logService.error('Remote storage service initialization failed, falling back to browser storage', error);
+ }
+ }
+
--- code-server.orig/lib/vscode/src/vs/sessions/browser/web.main.ts
+++ code-server/lib/vscode/src/vs/sessions/browser/web.main.ts
@@ -42 +42 @@
- _userDataProfilesService: BrowserUserDataProfilesService,
+ userDataProfilesService: BrowserUserDataProfilesService,
@@ -44 +44 @@
- _remoteAgentService: IRemoteAgentService,
+ remoteAgentService: IRemoteAgentService,
@@ -85 +85 @@
- const storageService = await this.createStorageService(workspaceIdentifier, logService, userDataProfileService);
+ const storageService = await this.createStorageService(workspaceIdentifier, environmentService, logService, userDataProfileService, userDataProfilesService, remoteAgentService);
2 changes: 2 additions & 0 deletions patches/series
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,5 @@ signature-verification.diff
copilot.diff
app-name.diff
csp-hashes.diff
remote-storage.diff
remote-secret-storage.diff
5 changes: 5 additions & 0 deletions src/node/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export interface UserProvidedCodeArgs {
"show-versions"?: boolean
category?: string
"github-auth"?: string
"enable-remote-storage"?: boolean
"disable-update-check"?: boolean
"disable-file-downloads"?: boolean
"disable-file-uploads"?: boolean
Expand Down Expand Up @@ -274,6 +275,10 @@ export const options: Options<Required<UserProvidedArgs>> = {
type: "string",
description: "GitHub authentication token (can only be passed in via $GITHUB_TOKEN or the config file).",
},
"enable-remote-storage": {
type: "boolean",
description: "Persist VS Code workbench storage on the remote server instead of browser storage.",
},
"proxy-domain": { type: "string[]", description: "Domain used for proxying ports." },
"skip-auth-preflight": {
type: "boolean",
Expand Down
7 changes: 7 additions & 0 deletions test/unit/node/cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1036,6 +1036,13 @@ describe("toCodeArgs", () => {
})
})

it("should pass through --enable-remote-storage", async () => {
expect(await toCodeArgs(await setDefaults(parse(["--enable-remote-storage"])))).toStrictEqual({
...vscodeDefaults,
"enable-remote-storage": true,
})
})

it("should collect a repeated --vscode-option into an array", async () => {
const args = parse([
"--vscode-option",
Expand Down