/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { hostname, release } from 'os'; import { Emitter, Event } from 'vs/base/common/event'; import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; import * as path from 'vs/base/common/path'; import { IURITransformer } from 'vs/base/common/uriIpc'; import { getMachineId } from 'vs/base/node/id'; import { Promises } from 'vs/base/node/pfs'; import { ClientConnectionEvent, IMessagePassingProtocol, IPCServer, ProxyChannel, StaticRouter } from 'vs/base/parts/ipc/common/ipc'; import { ProtocolConstants } from 'vs/base/parts/ipc/common/ipc.net'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ConfigurationService } from 'vs/platform/configuration/common/configurationService'; import { ICredentialsMainService } from 'vs/platform/credentials/common/credentials'; import { CredentialsWebMainService } from 'vs/platform/credentials/node/credentialsMainService'; import { ExtensionHostDebugBroadcastChannel } from 'vs/platform/debug/common/extensionHostDebugIpc'; import { IDownloadService } from 'vs/platform/download/common/download'; import { DownloadServiceChannelClient } from 'vs/platform/download/common/downloadIpc'; import { IEncryptionMainService } from 'vs/platform/encryption/common/encryptionService'; import { EncryptionMainService } from 'vs/platform/encryption/node/encryptionMainService'; import { IEnvironmentService, INativeEnvironmentService } from 'vs/platform/environment/common/environment'; import { ExtensionGalleryServiceWithNoStorageService } from 'vs/platform/extensionManagement/common/extensionGalleryService'; import { IExtensionGalleryService, IExtensionManagementCLIService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { ExtensionManagementCLIService } from 'vs/platform/extensionManagement/common/extensionManagementCLIService'; import { ExtensionManagementChannel } from 'vs/platform/extensionManagement/common/extensionManagementIpc'; import { ExtensionManagementService, INativeServerExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService'; import { IFileService } from 'vs/platform/files/common/files'; import { FileService } from 'vs/platform/files/common/fileService'; import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { ILanguagePackService } from 'vs/platform/languagePacks/common/languagePacks'; import { NativeLanguagePackService } from 'vs/platform/languagePacks/node/languagePacks'; import { AbstractLogger, DEFAULT_LOG_LEVEL, getLogLevel, ILogService, LogLevel, LogService, MultiplexLogService } from 'vs/platform/log/common/log'; import { LogLevelChannel } from 'vs/platform/log/common/logIpc'; import { SpdLogLogger } from 'vs/platform/log/node/spdlogLog'; import product from 'vs/platform/product/common/product'; import { IProductService } from 'vs/platform/product/common/productService'; import { RemoteAgentConnectionContext } from 'vs/platform/remote/common/remoteAgentEnvironment'; import { IRequestService } from 'vs/platform/request/common/request'; import { RequestChannel } from 'vs/platform/request/common/requestIpc'; import { RequestService } from 'vs/platform/request/node/requestService'; import { resolveCommonProperties } from 'vs/platform/telemetry/common/commonProperties'; import { ITelemetryService, TelemetryLevel } from 'vs/platform/telemetry/common/telemetry'; import { ITelemetryServiceConfig } from 'vs/platform/telemetry/common/telemetryService'; import { getPiiPathsFromEnvironment, ITelemetryAppender, NullAppender, supportsTelemetry } from 'vs/platform/telemetry/common/telemetryUtils'; import ErrorTelemetry from 'vs/platform/telemetry/node/errorTelemetry'; import { IPtyService, TerminalSettingId } from 'vs/platform/terminal/common/terminal'; import { PtyHostService } from 'vs/platform/terminal/node/ptyHostService'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService'; import { RemoteAgentEnvironmentChannel } from 'vs/server/node/remoteAgentEnvironmentImpl'; import { RemoteAgentFileSystemProviderChannel } from 'vs/server/node/remoteFileSystemProviderServer'; import { ServerTelemetryChannel } from 'vs/platform/telemetry/common/remoteTelemetryChannel'; import { IServerTelemetryService, ServerNullTelemetryService, ServerTelemetryService } from 'vs/platform/telemetry/common/serverTelemetryService'; import { RemoteTerminalChannel } from 'vs/server/node/remoteTerminalChannel'; import { createURITransformer } from 'vs/workbench/api/node/uriTransformer'; import { ServerConnectionToken } from 'vs/server/node/serverConnectionToken'; import { ServerEnvironmentService, ServerParsedArgs } from 'vs/server/node/serverEnvironmentService'; import { REMOTE_TERMINAL_CHANNEL_NAME } from 'vs/workbench/contrib/terminal/common/remoteTerminalChannel'; import { RemoteExtensionLogFileName } from 'vs/workbench/services/remote/common/remoteAgentService'; import { REMOTE_FILE_SYSTEM_CHANNEL_NAME } from 'vs/workbench/services/remote/common/remoteFileSystemProviderClient'; import { ExtensionHostStatusService, IExtensionHostStatusService } from 'vs/server/node/extensionHostStatusService'; import { IExtensionsScannerService } from 'vs/platform/extensionManagement/common/extensionsScannerService'; import { ExtensionsScannerService } from 'vs/server/node/extensionsScannerService'; import { ExtensionsProfileScannerService, IExtensionsProfileScannerService } from 'vs/platform/extensionManagement/common/extensionsProfileScannerService'; import { IUserDataProfilesService, UserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; import { NullPolicyService } from 'vs/platform/policy/common/policy'; import { OneDataSystemAppender } from 'vs/platform/telemetry/node/1dsAppender'; const eventPrefix = 'monacoworkbench'; export async function setupServerServices(connectionToken: ServerConnectionToken, args: ServerParsedArgs, REMOTE_DATA_FOLDER: string, disposables: DisposableStore) { const services = new ServiceCollection(); const socketServer = new SocketServer(); const productService: IProductService = { _serviceBrand: undefined, ...product }; services.set(IProductService, productService); const environmentService = new ServerEnvironmentService(args, productService); services.set(IEnvironmentService, environmentService); services.set(INativeEnvironmentService, environmentService); const spdLogService = new LogService(new SpdLogLogger(RemoteExtensionLogFileName, path.join(environmentService.logsPath, `${RemoteExtensionLogFileName}.log`), true, false, getLogLevel(environmentService))); const logService = new MultiplexLogService([new ServerLogService(getLogLevel(environmentService)), spdLogService]); services.set(ILogService, logService); setTimeout(() => cleanupOlderLogs(environmentService.logsPath).then(null, err => logService.error(err)), 10000); logService.trace(`Remote configuration data at ${REMOTE_DATA_FOLDER}`); logService.trace('process arguments:', environmentService.args); if (Array.isArray(productService.serverGreeting)) { spdLogService.info(`\n\n${productService.serverGreeting.join('\n')}\n\n`); } // ExtensionHost Debug broadcast service socketServer.registerChannel(ExtensionHostDebugBroadcastChannel.ChannelName, new ExtensionHostDebugBroadcastChannel()); // TODO: @Sandy @Joao need dynamic context based router const router = new StaticRouter(ctx => ctx.clientId === 'renderer'); socketServer.registerChannel('logger', new LogLevelChannel(logService)); // Files const fileService = disposables.add(new FileService(logService)); services.set(IFileService, fileService); fileService.registerProvider(Schemas.file, disposables.add(new DiskFileSystemProvider(logService))); // URI Identity const uriIdentityService = new UriIdentityService(fileService); services.set(IUriIdentityService, uriIdentityService); // User Data Profiles const userDataProfilesService = new UserDataProfilesService(environmentService, fileService, uriIdentityService, logService); services.set(IUserDataProfilesService, userDataProfilesService); // Configuration const configurationService = new ConfigurationService(environmentService.machineSettingsResource, fileService, new NullPolicyService(), logService); services.set(IConfigurationService, configurationService); await configurationService.initialize(); const extensionHostStatusService = new ExtensionHostStatusService(); services.set(IExtensionHostStatusService, extensionHostStatusService); // Request services.set(IRequestService, new SyncDescriptor(RequestService)); let oneDsAppender: ITelemetryAppender = NullAppender; const machineId = await getMachineId(); if (supportsTelemetry(productService, environmentService)) { if (productService.aiConfig && productService.aiConfig.ariaKey) { oneDsAppender = new OneDataSystemAppender(configurationService, eventPrefix, null, productService.aiConfig.ariaKey); disposables.add(toDisposable(() => oneDsAppender?.flush())); // Ensure the AI appender is disposed so that it flushes remaining data } const config: ITelemetryServiceConfig = { appenders: [oneDsAppender], commonProperties: resolveCommonProperties(fileService, release(), hostname(), process.arch, productService.commit, productService.version + '-remote', machineId, productService.msftInternalDomains, environmentService.installSourcePath, 'remoteAgent'), piiPaths: getPiiPathsFromEnvironment(environmentService) }; const initialTelemetryLevelArg = environmentService.args['telemetry-level']; let injectedTelemetryLevel: TelemetryLevel = TelemetryLevel.USAGE; // Convert the passed in CLI argument into a telemetry level for the telemetry service if (initialTelemetryLevelArg === 'all') { injectedTelemetryLevel = TelemetryLevel.USAGE; } else if (initialTelemetryLevelArg === 'error') { injectedTelemetryLevel = TelemetryLevel.ERROR; } else if (initialTelemetryLevelArg === 'crash') { injectedTelemetryLevel = TelemetryLevel.CRASH; } else if (initialTelemetryLevelArg !== undefined) { injectedTelemetryLevel = TelemetryLevel.NONE; } services.set(IServerTelemetryService, new SyncDescriptor(ServerTelemetryService, [config, injectedTelemetryLevel])); } else { services.set(IServerTelemetryService, ServerNullTelemetryService); } services.set(IExtensionGalleryService, new SyncDescriptor(ExtensionGalleryServiceWithNoStorageService)); const downloadChannel = socketServer.getChannel('download', router); services.set(IDownloadService, new DownloadServiceChannelClient(downloadChannel, () => getUriTransformer('renderer') /* TODO: @Sandy @Joao need dynamic context based router */)); services.set(IExtensionsProfileScannerService, new SyncDescriptor(ExtensionsProfileScannerService)); services.set(IExtensionsScannerService, new SyncDescriptor(ExtensionsScannerService)); services.set(INativeServerExtensionManagementService, new SyncDescriptor(ExtensionManagementService)); const instantiationService: IInstantiationService = new InstantiationService(services); services.set(ILanguagePackService, instantiationService.createInstance(NativeLanguagePackService)); const extensionManagementCLIService = instantiationService.createInstance(ExtensionManagementCLIService); services.set(IExtensionManagementCLIService, extensionManagementCLIService); const ptyService = instantiationService.createInstance( PtyHostService, { graceTime: ProtocolConstants.ReconnectionGraceTime, shortGraceTime: ProtocolConstants.ReconnectionShortGraceTime, scrollback: configurationService.getValue(TerminalSettingId.PersistentSessionScrollback) ?? 100 } ); services.set(IPtyService, ptyService); services.set(IEncryptionMainService, new SyncDescriptor(EncryptionMainService, [machineId])); services.set(ICredentialsMainService, new SyncDescriptor(CredentialsWebMainService)); instantiationService.invokeFunction(accessor => { const extensionManagementService = accessor.get(INativeServerExtensionManagementService); const extensionsScannerService = accessor.get(IExtensionsScannerService); const remoteExtensionEnvironmentChannel = new RemoteAgentEnvironmentChannel(connectionToken, environmentService, userDataProfilesService, extensionManagementCLIService, logService, extensionHostStatusService, extensionsScannerService); socketServer.registerChannel('remoteextensionsenvironment', remoteExtensionEnvironmentChannel); const telemetryChannel = new ServerTelemetryChannel(accessor.get(IServerTelemetryService), oneDsAppender); socketServer.registerChannel('telemetry', telemetryChannel); socketServer.registerChannel(REMOTE_TERMINAL_CHANNEL_NAME, new RemoteTerminalChannel(environmentService, logService, ptyService, productService, extensionManagementService)); const remoteFileSystemChannel = new RemoteAgentFileSystemProviderChannel(logService, environmentService); socketServer.registerChannel(REMOTE_FILE_SYSTEM_CHANNEL_NAME, remoteFileSystemChannel); socketServer.registerChannel('request', new RequestChannel(accessor.get(IRequestService))); const channel = new ExtensionManagementChannel(extensionManagementService, (ctx: RemoteAgentConnectionContext) => getUriTransformer(ctx.remoteAuthority)); socketServer.registerChannel('extensions', channel); const encryptionChannel = ProxyChannel.fromService(accessor.get(IEncryptionMainService)); socketServer.registerChannel('encryption', encryptionChannel); const credentialsChannel = ProxyChannel.fromService(accessor.get(ICredentialsMainService)); socketServer.registerChannel('credentials', credentialsChannel); // clean up deprecated extensions extensionManagementService.removeUninstalledExtensions(true); disposables.add(new ErrorTelemetry(accessor.get(ITelemetryService))); return { telemetryService: accessor.get(ITelemetryService) }; }); return { socketServer, instantiationService }; } const _uriTransformerCache: { [remoteAuthority: string]: IURITransformer } = Object.create(null); function getUriTransformer(remoteAuthority: string): IURITransformer { if (!_uriTransformerCache[remoteAuthority]) { _uriTransformerCache[remoteAuthority] = createURITransformer(remoteAuthority); } return _uriTransformerCache[remoteAuthority]; } export class SocketServer extends IPCServer { private _onDidConnectEmitter: Emitter; constructor() { const emitter = new Emitter(); super(emitter.event); this._onDidConnectEmitter = emitter; } public acceptConnection(protocol: IMessagePassingProtocol, onDidClientDisconnect: Event): void { this._onDidConnectEmitter.fire({ protocol, onDidClientDisconnect }); } } class ServerLogService extends AbstractLogger implements ILogService { _serviceBrand: undefined; private useColors: boolean; constructor(logLevel: LogLevel = DEFAULT_LOG_LEVEL) { super(); this.setLevel(logLevel); this.useColors = Boolean(process.stdout.isTTY); } trace(message: string, ...args: any[]): void { if (this.getLevel() <= LogLevel.Trace) { if (this.useColors) { console.log(`\x1b[90m[${now()}]\x1b[0m`, message, ...args); } else { console.log(`[${now()}]`, message, ...args); } } } debug(message: string, ...args: any[]): void { if (this.getLevel() <= LogLevel.Debug) { if (this.useColors) { console.log(`\x1b[90m[${now()}]\x1b[0m`, message, ...args); } else { console.log(`[${now()}]`, message, ...args); } } } info(message: string, ...args: any[]): void { if (this.getLevel() <= LogLevel.Info) { if (this.useColors) { console.log(`\x1b[90m[${now()}]\x1b[0m`, message, ...args); } else { console.log(`[${now()}]`, message, ...args); } } } warn(message: string | Error, ...args: any[]): void { if (this.getLevel() <= LogLevel.Warning) { if (this.useColors) { console.warn(`\x1b[93m[${now()}]\x1b[0m`, message, ...args); } else { console.warn(`[${now()}]`, message, ...args); } } } error(message: string, ...args: any[]): void { if (this.getLevel() <= LogLevel.Error) { if (this.useColors) { console.error(`\x1b[91m[${now()}]\x1b[0m`, message, ...args); } else { console.error(`[${now()}]`, message, ...args); } } } critical(message: string, ...args: any[]): void { if (this.getLevel() <= LogLevel.Critical) { if (this.useColors) { console.error(`\x1b[90m[${now()}]\x1b[0m`, message, ...args); } else { console.error(`[${now()}]`, message, ...args); } } } override dispose(): void { // noop } flush(): void { // noop } } function now(): string { const date = new Date(); return `${twodigits(date.getHours())}:${twodigits(date.getMinutes())}:${twodigits(date.getSeconds())}`; } function twodigits(n: number): string { if (n < 10) { return `0${n}`; } return String(n); } /** * Cleans up older logs, while keeping the 10 most recent ones. */ async function cleanupOlderLogs(logsPath: string): Promise { const currentLog = path.basename(logsPath); const logsRoot = path.dirname(logsPath); const children = await Promises.readdir(logsRoot); const allSessions = children.filter(name => /^\d{8}T\d{6}$/.test(name)); const oldSessions = allSessions.sort().filter((d) => d !== currentLog); const toDelete = oldSessions.slice(0, Math.max(0, oldSessions.length - 9)); await Promise.all(toDelete.map(name => Promises.rm(path.join(logsRoot, name)))); }