mirror of
https://github.com/coder/code-server.git
synced 2026-11-02 22:09:11 +01:00
Add flag to store state on remote instead of browser (#7914)
This commit is contained in:
86
patches/remote-secret-storage.diff
Normal file
86
patches/remote-secret-storage.diff
Normal file
@@ -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
patches/remote-storage.diff
Normal file
122
patches/remote-storage.diff
Normal file
@@ -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);
|
||||||
@@ -25,3 +25,5 @@ signature-verification.diff
|
|||||||
copilot.diff
|
copilot.diff
|
||||||
app-name.diff
|
app-name.diff
|
||||||
csp-hashes.diff
|
csp-hashes.diff
|
||||||
|
remote-storage.diff
|
||||||
|
remote-secret-storage.diff
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ export interface UserProvidedCodeArgs {
|
|||||||
"show-versions"?: boolean
|
"show-versions"?: boolean
|
||||||
category?: string
|
category?: string
|
||||||
"github-auth"?: string
|
"github-auth"?: string
|
||||||
|
"enable-remote-storage"?: boolean
|
||||||
"disable-update-check"?: boolean
|
"disable-update-check"?: boolean
|
||||||
"disable-file-downloads"?: boolean
|
"disable-file-downloads"?: boolean
|
||||||
"disable-file-uploads"?: boolean
|
"disable-file-uploads"?: boolean
|
||||||
@@ -275,6 +276,10 @@ export const options: Options<Required<UserProvidedArgs>> = {
|
|||||||
type: "string",
|
type: "string",
|
||||||
description: "GitHub authentication token (can only be passed in via $GITHUB_TOKEN or the config file).",
|
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." },
|
"proxy-domain": { type: "string[]", description: "Domain used for proxying ports." },
|
||||||
"skip-auth-preflight": {
|
"skip-auth-preflight": {
|
||||||
type: "boolean",
|
type: "boolean",
|
||||||
|
|||||||
@@ -1067,6 +1067,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 () => {
|
it("should collect a repeated --vscode-option into an array", async () => {
|
||||||
const args = parse([
|
const args = parse([
|
||||||
"--vscode-option",
|
"--vscode-option",
|
||||||
|
|||||||
Reference in New Issue
Block a user