diff --git a/README.md b/README.md index 70a1c2ffc..f4956b077 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ # code-server - [!["Open Issues"](https://img.shields.io/github/issues-raw/cdr/code-server.svg)](https://github.com/cdr/code-server/issues) [!["Latest Release"](https://img.shields.io/github/release/cdr/code-server.svg)](https://github.com/cdr/code-server/releases/latest) [![MIT license](https://img.shields.io/badge/license-MIT-green.svg)](https://github.com/cdr/code-server/blob/master/LICENSE) @@ -23,18 +22,14 @@ docker run -it -p 127.0.0.1:8443:8443 -p 127.0.0.1:8444:8444 -v "$PWD:/home/code ![Screenshot](/doc/assets/ide.png) ## Getting Started - ### Run over SSH - Use [sshcode](https://github.com/codercom/sshcode) for a simple setup. ### Docker - See docker oneliner mentioned above. Dockerfile is at [/Dockerfile](/Dockerfile). ### Binaries - 1. [Download a binary](https://github.com/cdr/code-server/releases) (Linux and OS X supported. Windows coming soon) 2. Start the binary with the project directory as the first argument @@ -65,28 +60,15 @@ How to [secure your setup](/doc/security/ssl.md). compile the build directory as well. - For now `@coder/nbin` is a global dependency. - Run `yarn build ${codeServerVersion} ${vscodeVersion} ${target} ${arch}` in - this directory (for example: `yarn build development 1.35.0 linux x64`). + this directory (for example: `yarn build development 1.36.0 linux x64`). +- If you target the same VS Code version our Travis builds do everything will + work but if you target some other version it might not (we have to do some + patching to VS Code so different versions aren't always compatible). - You can run the built code with `node path/to/build/out/vs/server/main.js` or run `yarn binary` with the same arguments in the previous step to package the code into a single binary. -### Development - -```fish -git clone https://github.com/microsoft/vscode -cd vscode -git clone https://github.com/cdr/code-server src/vs/server -cd src/vs/server -yarn patch:apply -yarn -yarn watch -# Wait for the initial compilation to complete (it will say "Finished compilation"). -yarn start --allow-http --no-auth -# Visit http://localhost:8443 -``` - ## Known Issues - - Creating custom VS Code extensions and debugging them doesn't work. - To debug Golang using [ms-vscode-go extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode.Go), @@ -102,31 +84,48 @@ yarn start --allow-http --no-auth - Run VS Code unit tests against our builds to ensure features work as expected. ## Extensions - At the moment we can't use the official VSCode Marketplace. We've created a custom extension marketplace focused around open-sourced extensions. However, if you have access to the `.vsix` file, you can manually install the extension. ## Telemetry +Use the `--disable-telemetry` flag to completely disable telemetry. -Set the `telemetry.enableTelemetry` user setting to false to disable telemetry. - -We use data collected to improve code-server. +We use the data collected to improve code-server. ## Contributing -Development guides are coming soon. +### Development +```fish +git clone https://github.com/microsoft/vscode +cd vscode +git clone https://github.com/cdr/code-server src/vs/server +cd src/vs/server +yarn patch:apply +yarn +yarn watch +# Wait for the initial compilation to complete (it will say "Finished compilation"). +yarn start --allow-http --no-auth +# Visit http://localhost:8443 +``` + +### Upgrading VS Code +We have to patch VS Code to provide and fix some functionality. As the web +portion of VS Code matures, we'll be able to shrink and maybe even entirely +eliminate our patch. In the meantime, however, upgrading the VS Code version +requires ensuring that the patch still applies and has the intended effects. + +To generate a new patch, **stage all the changes** you want to be included in +the patch in the VS Code source, then run `yarn patch:generate` in this +directory. ## License - [MIT](LICENSE) ## Enterprise - Visit [our enterprise page](https://coder.com/enterprise) for more information about our enterprise offering. ## Commercialization - If you would like to commercialize code-server, please contact contact@coder.com. diff --git a/doc/self-hosted/index.md b/doc/self-hosted/index.md index f3a867f1c..f95f92f9c 100644 --- a/doc/self-hosted/index.md +++ b/doc/self-hosted/index.md @@ -1,14 +1,21 @@ # Getting Started -[code-server](https://coder.com) is used by developers at Azure, Google, Reddit, and more to give them access to VS Code in the browser. +[code-server](https://coder.com) is used by developers at Azure, Google, +Reddit, and more to give them access to VS Code in the browser. ## Quickstart Guide -> NOTE: If you get stuck or need help, [file an issue](https://github.com/cdr/code-server/issues/new?&title=Improve+self-hosted+quickstart+guide), [tweet (@coderhq)](https://twitter.com/coderhq) or [email](mailto:support@coder.com?subject=Self-hosted%20quickstart%20guide). +> NOTE: If you get stuck or need help, [file an issue](https://github.com/cdr/code-server/issues/new?&title=Improve+self-hosted+quickstart+guide), +> [tweet (@coderhq)](https://twitter.com/coderhq) or +> [email](mailto:support@coder.com?subject=Self-hosted%20quickstart%20guide). -This document pertains to Coder specific implementations of VS Code. For documentation on how to use VS Code itself, please refer to the official [documentation for VS Code](https://code.visualstudio.com/docs) +This document pertains to Coder-specific implementations of VS Code. For +documentation on how to use VS Code itself, please refer to the official +[documentation for VS Code](https://code.visualstudio.com/docs) -It takes just a few minutes to get your own self-hosted server running. If you've got a machine running macOS, Windows, or Linux, you're ready to start the binary which listens on port `8443` by default. +It takes just a few minutes to get your own self-hosted server running. If +you've got a machine running macOS, Windows, or Linux, you're ready to start +the binary which listens on ports `8443` and `8444` by default. +1. Visit [the releases](https://github.com/cdr/code-server/releases) page and + download the latest cli for your operating system. +2. Double click the executable to run in the current directory. +3. Copy the password that appears in the CLI. +4. In your browser navigate to `localhost:8443`. +5. Paste the password from the cli into the login window. -1. Visit [the releases](https://github.com/cdr/code-server/releases) page and download the latest cli for your operating system -2. Double click the executable to run in the current directory -3. Copy the password that appears in the cli -4. In your browser navigate to `localhost:8443` -5. Paste the password from the cli into the login window -> NOTE: Be careful with your password as sharing it will grant those users access to your server's file system +> NOTE: Be careful with your password as sharing it will grant those users +> access to your server's file system ### Things To Know -- When you visit the IP for your code-server instance, you will be greeted with a page similar to the following screenshot. Code-server is using a self-signed SSL certificate for easy setup. In Chrome/Chromium, click **"Advanced"** then click **"proceed anyway"**. In Firefox, click **Advanced**, then **Add Exception**, then finally **Confirm Security Exception**. +- When you visit the IP for your code-server instance, you will be greeted with + a page similar to the following screenshot. Code-server is using a + self-signed SSL certificate for easy setup. In Chrome/Chromium, click + **"Advanced"** then click **"proceed anyway"**. In Firefox, click + **Advanced**, then **Add Exception**, then finally **Confirm Security + Exception**. ## Usage -
code-server --help
- -code-server can be ran with a number of arguments to customize your working directory, host, port, and SSL certificate. - ``` -Usage: code-server [options] - -Run VS Code on a remote server. - -Options: - -V, --version output the version number - --cert - --cert-key - -e, --extensions-dir Override the main default path for user extensions. - --extra-extensions-dir [dir] Path to an extra user extension directory (repeatable). (default: []) - --extra-builtin-extensions-dir [dir] Path to an extra built-in extension directory (repeatable). (default: []) - -d, --user-data-dir Specifies the directory that user data is kept in, useful when running as root. - -h, --host Customize the hostname. (default: "0.0.0.0") - -o, --open Open in the browser on startup. - -p, --port Port to bind on. (default: 8443) - -N, --no-auth Start without requiring authentication. - -H, --allow-http Allow http connections. - --disable-telemetry Disables ALL telemetry. - --socket Listen on a UNIX socket. Host and port will be ignored when set. - --trust-proxy Trust the X-Forwarded-For header, useful when using a reverse proxy. - --install-extension Install an extension by its ID. - -h, --help output usage information +code-server --help ``` - ### Data Directory - Use `code-server -d (path/to/directory)` or `code-server --user-data-dir=(path/to/directory)`, excluding the parentheses to specify the root folder that VS Code will start in. +code-server can be ran with a number of arguments to customize your working +directory, host, port, and SSL certificate. - ### Host - By default, code-server will use `0.0.0.0` as its address. This can be changed by using `code-server -h` or `code-server --host=` followed by the address you want to use. - > Example: `code-server -h 127.0.0.1` +### Data Directory +Use `code-server --user-data-dir path/to/directory` to specify the root folder +that VS Code will start in. - ### Open - You can have the server automatically open the VS Code in your browser on startup by using the `code-server -o` or `code-server --open` flags +### Host +By default, code-server will use `127.0.0.1` for insecure connections and +`0.0.0.0` for secure connections. This can be changed by using +`code-server --host `. - ### Port - By default, code-server will use `8443` as its port. This can be changed by using `code-server -p` or `code-server --port=` followed by the port you want to use. - > Example: `code-server -p 9000` +> Example: `code-server --host 127.0.0.1` - ### Telemetry - Disable all telemetry with `code-server --disable-telemetry`. +### Open +You can have the server automatically open the VS Code in your browser on +startup by using the `code-server -o` or `code-server --open` flags - ### Cert and Cert Key - To encrypt the traffic between the browser and server use `code-server --cert=` followed by the path to your `.cer` file. Additionally, you can use certificate keys with `code-server --cert-key` followed by the path to your `.key` file. -> Example (certificate and key): `code-server --cert /etc/letsencrypt/live/example.com/fullchain.cer --cert-key /etc/letsencrypt/live/example.com/fullchain.key` -> Example (if you are using Letsencrypt or similar): `code-server --cert /etc/letsencrypt/live/example.com/fullchain.pem --cert-key /etc/letsencrypt/live/example.com/privkey.key` +### Port +By default, code-server will use `8443` as its port. This can be changed by +using `code-server -p` or `code-server --port=` followed by the port you want +to use. -> To ensure the connection between you and your server is encrypted view our guide on [securing your setup](../security/ssl.md) +> Example: `code-server -p 9000` - ### Nginx Reverse Proxy - Below is a virtual host example that works with code-server. Please also pass `--allow-http` and `--trust-proxy` to code-server to allow the proxy to connect. You can also use Let's Encrypt to get a SSL certificates for free. - ``` - server { - listen 80; - listen [::]:80; - server_name code.example.com code.example.org; - location / { - proxy_pass http://localhost:8443/; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection upgrade; - proxy_set_header Accept-Encoding gzip; - } +### Cert and Cert Key +To encrypt the traffic between the browser and server use `code-server --cert` +followed by the path to your `.cer` file. Additionally, you can use certificate +keys with `code-server --cert-key` followed by the path to your `.key` file. + +Example: +``` +code-server --cert /path/to/certificate/fullchain.cer --cert-key /path/to/certificate/fullchain.key +``` + +Example for Let's Encrypt: +``` +code-server --cert /etc/letsencrypt/live/example.com/fullchain.pem --cert-key /etc/letsencrypt/live/example.com/privkey.key +``` + +To ensure the connection between you and your server is encrypted view our +guide on [securing your setup](../security/ssl.md). + +### Nginx Reverse Proxy +Below is a virtual host example that works with code-server. Please also pass +`--allow-http` and `--trust-proxy` to code-server to allow the proxy to +connect. You can also use Let's Encrypt to get a SSL certificates for free. + +``` +server { + listen 80; + listen [::]:80; + server_name code.example.com code.example.org; + location / { + proxy_pass http://localhost:8443/; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection upgrade; + proxy_set_header Accept-Encoding gzip; } - ``` +} +``` - ### Apache Reverse Proxy - Example of a HTTPS virtualhost configuration for Apache as a reverse proxy. Please also pass `--allow-http` and `--trust-proxy` to code-server to allow the proxy to connect. You can also use Let's Encrypt to get a SSL certificates for free. - ``` - - ServerName code.example.com +### Apache Reverse Proxy +Example of an HTTPS virtualhost configuration for Apache as a reverse proxy. +Please also pass `--allow-http` and `--trust-proxy` to code-server to allow the +proxy to connect. You can also use Let's Encrypt to get a SSL certificates for +free. - RewriteEngine On - RewriteCond %{HTTP:Upgrade} =websocket [NC] - RewriteRule /(.*) ws://localhost:8443/$1 [P,L] - RewriteCond %{HTTP:Upgrade} !=websocket [NC] - RewriteRule /(.*) http://localhost:8443/$1 [P,L] +``` + + ServerName code.example.com - ProxyRequests off + RewriteEngine On + RewriteCond %{HTTP:Upgrade} =websocket [NC] + RewriteRule /(.*) ws://localhost:8443/$1 [P,L] + RewriteCond %{HTTP:Upgrade} !=websocket [NC] + RewriteRule /(.*) http://localhost:8443/$1 [P,L] - RequestHeader set X-Forwarded-Proto https - RequestHeader set X-Forwarded-Port 443 + ProxyRequests off - ProxyPass / http://localhost:8443/ nocanon - ProxyPassReverse / http://localhost:8443/ + RequestHeader set X-Forwarded-Proto https + RequestHeader set X-Forwarded-Port 443 - - ``` - *Important:* For more details about Apache reverse proxy configuration checkout the [documentation](https://httpd.apache.org/docs/current/mod/mod_proxy.html) - especially the [Securing your Server](https://httpd.apache.org/docs/current/mod/mod_proxy.html#access) section + ProxyPass / http://localhost:8443/ nocanon + ProxyPassReverse / http://localhost:8443/ - ### Help - Use `code-server --help` to view the usage for the CLI. This is also shown at the beginning of this section. + +``` +*Important:* For more details about Apache reverse proxy configuration checkout +the [documentation](https://httpd.apache.org/docs/current/mod/mod_proxy.html) - +especially the [Securing your Server](https://httpd.apache.org/docs/current/mod/mod_proxy.html#access) +section. + + +### Help +Use `code-server --help` to view the usage for the CLI. diff --git a/scripts/vscode.patch b/scripts/vscode.patch index c9dcce8c3..e97e530ca 100644 --- a/scripts/vscode.patch +++ b/scripts/vscode.patch @@ -214,6 +214,103 @@ index e09049c5b9..d93ffa527a 100644 -} \ No newline at end of file +} +diff --git a/src/vs/platform/log/common/logIpc.ts b/src/vs/platform/log/common/logIpc.ts +index 9f68b645b6..fe380bb6f8 100644 +--- a/src/vs/platform/log/common/logIpc.ts ++++ b/src/vs/platform/log/common/logIpc.ts +@@ -26,6 +26,7 @@ export class LogLevelSetterChannel implements IServerChannel { + call(_: unknown, command: string, arg?: any): Promise { + switch (command) { + case 'setLevel': this.service.setLevel(arg); return Promise.resolve(); ++ case 'getLevel': return Promise.resolve(this.service.getLevel()); + } + + throw new Error(`Call not found: ${command}`); +@@ -40,6 +41,10 @@ export class LogLevelSetterChannelClient { + return this.channel.listen('onDidChangeLogLevel'); + } + ++ getLevel(): Promise { ++ return this.channel.call('getLevel'); ++ } ++ + setLevel(level: LogLevel): void { + this.channel.call('setLevel', level); + } +@@ -56,4 +61,4 @@ export class FollowerLogService extends DelegatedLogService implements ILogServi + setLevel(level: LogLevel): void { + this.master.setLevel(level); + } +-} +\ No newline at end of file ++} +diff --git a/src/vs/platform/telemetry/node/telemetryIpc.ts b/src/vs/platform/telemetry/node/telemetryIpc.ts +index 8e1b68eb36..2b6a0d5b15 100644 +--- a/src/vs/platform/telemetry/node/telemetryIpc.ts ++++ b/src/vs/platform/telemetry/node/telemetryIpc.ts +@@ -6,6 +6,9 @@ + import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; + import { ITelemetryAppender } from 'vs/platform/telemetry/common/telemetryUtils'; + import { Event } from 'vs/base/common/event'; ++import { ITelemetryService, ITelemetryInfo } from 'vs/platform/telemetry/common/telemetry'; ++import { ITelemetryData } from 'vs/base/common/actions'; ++import { ClassifiedEvent, GDPRClassification, StrictPropertyCheck } from 'vs/platform/telemetry/common/gdprTypings'; + + export interface ITelemetryLog { + eventName: string; +@@ -41,3 +44,52 @@ export class TelemetryAppenderClient implements ITelemetryAppender { + // TODO + } + } ++ ++export class TelemetryChannel implements IServerChannel { ++ ++ constructor(private service: ITelemetryService) {} ++ ++ listen(_: unknown, event: string): Event { ++ throw new Error(`Invalid listen ${event}`); ++ } ++ ++ call(_: unknown, command: string, args?: any): Promise { ++ switch (command) { ++ case 'publicLog': return this.service.publicLog(args[0], args[1], args[2]); ++ case 'publicLog2': return this.service.publicLog2(args[0], args[1], args[2]); ++ case 'setEnabled': return Promise.resolve(this.service.setEnabled(args[0])); ++ case 'getTelemetryInfo': return this.service.getTelemetryInfo(); ++ } ++ ++ throw new Error(`Invalid call ${command}`); ++ } ++} ++ ++export class TelemetryChannelClient implements ITelemetryService { ++ ++ _serviceBrand: any; ++ ++ constructor( ++ private readonly channel: IChannel, ++ ) { } ++ ++ public publicLog(eventName: string, data?: ITelemetryData, anonymizeFilePaths?: boolean): Promise { ++ return this.channel.call('publicLog', [eventName, data, anonymizeFilePaths]); ++ } ++ ++ public publicLog2 = never, T extends GDPRClassification = never>(eventName: string, data?: StrictPropertyCheck, anonymizeFilePaths?: boolean): Promise { ++ return this.channel.call('publicLog2', [eventName, data, anonymizeFilePaths]); ++ } ++ ++ public setEnabled(value: boolean): void { ++ this.channel.call('setEnable', [value]); ++ } ++ ++ public getTelemetryInfo(): Promise { ++ return this.channel.call('getTelemetryInfo'); ++ } ++ ++ public get isOptedIn(): boolean { ++ return true; ++ } ++} diff --git a/src/vs/workbench/browser/web.main.ts b/src/vs/workbench/browser/web.main.ts index 1986fb6642..afbe385af6 100644 --- a/src/vs/workbench/browser/web.main.ts @@ -236,21 +333,22 @@ index 1986fb6642..afbe385af6 100644 \ No newline at end of file +} diff --git a/src/vs/workbench/browser/web.simpleservices.ts b/src/vs/workbench/browser/web.simpleservices.ts -index b253e573ae..2e4dfb393a 100644 +index b253e573ae..bde667d045 100644 --- a/src/vs/workbench/browser/web.simpleservices.ts +++ b/src/vs/workbench/browser/web.simpleservices.ts -@@ -53,6 +53,10 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur +@@ -53,6 +53,11 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { ParsedArgs } from 'vs/platform/environment/common/environment'; import { ClassifiedEvent, StrictPropertyCheck, GDPRClassification } from 'vs/platform/telemetry/common/gdprTypings'; import { IProcessEnvironment } from 'vs/base/common/platform'; +import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; +import { ExtensionManagementChannelClient } from 'vs/platform/extensionManagement/node/extensionManagementIpc'; -+import { ExtensionGalleryChannelClient } from "vs/platform/extensionManagement/node/extensionGalleryIpc"; ++import { ExtensionGalleryChannelClient } from 'vs/platform/extensionManagement/node/extensionGalleryIpc'; ++import { TelemetryChannelClient } from 'vs/platform/telemetry/node/telemetryIpc'; +import { IProductService } from 'vs/platform/product/common/product'; //#region Backup File -@@ -125,13 +129,11 @@ export class SimpleClipboardService implements IClipboardService { +@@ -125,13 +130,11 @@ export class SimpleClipboardService implements IClipboardService { writeText(text: string, type?: string): void { } readText(type?: string): string { @@ -266,7 +364,7 @@ index b253e573ae..2e4dfb393a 100644 } writeFindText(text: string): void { } -@@ -239,7 +241,17 @@ export class SimpleExtensionGalleryService implements IExtensionGalleryService { +@@ -239,7 +242,17 @@ export class SimpleExtensionGalleryService implements IExtensionGalleryService { } } @@ -285,7 +383,7 @@ index b253e573ae..2e4dfb393a 100644 //#endregion -@@ -262,7 +274,7 @@ export class SimpleExtensionsWorkbenchService implements IExtensionsWorkbenchSer +@@ -262,7 +275,7 @@ export class SimpleExtensionsWorkbenchService implements IExtensionsWorkbenchSer checkForUpdates: any; allowedBadgeProviders: string[]; } @@ -294,7 +392,7 @@ index b253e573ae..2e4dfb393a 100644 //#endregion //#region ICommentService -@@ -375,7 +387,10 @@ export class SimpleExtensionTipsService implements IExtensionTipsService { +@@ -375,7 +388,10 @@ export class SimpleExtensionTipsService implements IExtensionTipsService { } getAllIgnoredRecommendations(): { global: string[]; workspace: string[]; } { @@ -306,7 +404,7 @@ index b253e573ae..2e4dfb393a 100644 } } -@@ -436,7 +451,16 @@ export class SimpleExtensionManagementService implements IExtensionManagementSer +@@ -436,7 +452,16 @@ export class SimpleExtensionManagementService implements IExtensionManagementSer } } @@ -324,7 +422,24 @@ index b253e573ae..2e4dfb393a 100644 //#endregion -@@ -1288,4 +1312,4 @@ class SimpleTunnelService implements ITunnelService { +@@ -680,7 +705,15 @@ export class SimpleTelemetryService implements ITelemetryService { + } + } + +-registerSingleton(ITelemetryService, SimpleTelemetryService); ++// registerSingleton(ITelemetryService, SimpleTelemetryService); ++class TelemetryService extends TelemetryChannelClient { ++ public constructor( ++ @IRemoteAgentService remoteAgentService: IRemoteAgentService, ++ ) { ++ super(remoteAgentService.getConnection()!.getChannel('telemetry')); ++ } ++} ++registerSingleton(ITelemetryService, TelemetryService); + + //#endregion + +@@ -1288,4 +1321,4 @@ class SimpleTunnelService implements ITunnelService { registerSingleton(ITunnelService, SimpleTunnelService); @@ -911,6 +1026,20 @@ index c08a6e37c1..31640d7e66 100644 } return this._extensionAllowedBadgeProviders; } +diff --git a/src/vs/workbench/contrib/remote/common/remote.contribution.ts b/src/vs/workbench/contrib/remote/common/remote.contribution.ts +index 9235c739fb..32d203eb32 100644 +--- a/src/vs/workbench/contrib/remote/common/remote.contribution.ts ++++ b/src/vs/workbench/contrib/remote/common/remote.contribution.ts +@@ -55,7 +55,8 @@ class RemoteChannelsContribution extends Disposable implements IWorkbenchContrib + const connection = remoteAgentService.getConnection(); + if (connection) { + const logLevelClient = new LogLevelSetterChannelClient(connection.getChannel('loglevel')); +- logLevelClient.setLevel(logService.getLevel()); ++ logLevelClient.getLevel().then((level) => logService.setLevel(level)); ++ logLevelClient.onDidChangeLogLevel((level) => logService.setLevel(level)); + this._register(logService.onDidChangeLogLevel(level => logLevelClient.setLevel(level))); + } + } diff --git a/src/vs/workbench/services/environment/browser/environmentService.ts b/src/vs/workbench/services/environment/browser/environmentService.ts index 3525569601..a91a5fce7d 100644 --- a/src/vs/workbench/services/environment/browser/environmentService.ts diff --git a/src/channel.ts b/src/channel.ts index fa6e606f3..fbe61b3a3 100644 --- a/src/channel.ts +++ b/src/channel.ts @@ -15,6 +15,7 @@ import { ILogService } from "vs/platform/log/common/log"; import pkg from "vs/platform/product/node/package"; import product from "vs/platform/product/node/product"; import { IRemoteAgentEnvironment } from "vs/platform/remote/common/remoteAgentEnvironment"; +import { ITelemetryService } from "vs/platform/telemetry/common/telemetry"; import { ExtensionScanner, ExtensionScannerInput } from "vs/workbench/services/extensions/node/extensionPoints"; import { DiskFileSystemProvider } from "vs/workbench/services/files/node/diskFileSystemProvider"; @@ -181,6 +182,7 @@ export class ExtensionEnvironmentChannel implements IServerChannel { public constructor( private readonly environment: IEnvironmentService, private readonly log: ILogService, + private readonly telemetry: ITelemetryService, ) {} public listen(_: unknown, event: string): Event { @@ -271,7 +273,7 @@ export class ExtensionEnvironmentChannel implements IServerChannel { throw new Error("not implemented"); } - private disableTelemetry(): Promise { - throw new Error("not implemented"); + private async disableTelemetry(): Promise { + this.telemetry.setEnabled(false); } } diff --git a/src/cli.ts b/src/cli.ts index 5a7abe80b..05c3dd8b4 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -5,8 +5,8 @@ import { validatePaths } from "vs/code/node/paths"; import { parseMainProcessArgv } from "vs/platform/environment/node/argvHelper"; import { ParsedArgs } from "vs/platform/environment/common/environment"; import { buildHelpMessage, buildVersionMessage, options } from "vs/platform/environment/node/argv"; -import product from "vs/platform/product/node/product"; import pkg from "vs/platform/product/node/package"; +import product from "vs/platform/product/node/product"; import { MainServer, WebviewServer } from "vs/server/src/server"; import "vs/server/src/tar"; diff --git a/src/insights.ts b/src/insights.ts index bb94c7378..031bd3cb3 100644 --- a/src/insights.ts +++ b/src/insights.ts @@ -1,25 +1,16 @@ -/** - * Used by node - */ import * as https from "https"; import * as os from "os"; -export const defaultClient = "filler"; +import * as appInsights from "applicationinsights"; + +export class TelemetryClient implements appInsights.TelemetryClient { + public config: any = {}; -export class TelemetryClient { public channel = { setUseDiskRetryCaching: (): void => undefined, }; - public constructor() { - // - } - - public trackEvent(options: { - name: string; - properties: object; - measurements: object; - }): void { + public trackEvent(options: appInsights.EventTelemetry): void { if (!options.properties) { options.properties = {}; } @@ -29,41 +20,20 @@ export class TelemetryClient { try { const cpus = os.cpus(); - // tslint:disable-next-line:no-any - (options.measurements as any).cpu = { - model: cpus[0].model, - cores: cpus.length, - }; - } catch (ex) { - // Nothin - } + options.measurements.cores = cpus.length; + options.properties["common.cpuModel"] = cpus[0].model; + } catch (error) {} try { - // tslint:disable-next-line:no-any - (options.measurements as any).memory = { - virtual_free: os.freemem(), - virtual_used: os.totalmem(), - }; - } catch (ex) { - // - } + options.measurements.memoryFree = os.freemem(); + options.measurements.memoryTotal = os.totalmem(); + } catch (error) {} try { - // tslint:disable:no-any - (options.properties as any)["common.shell"] = os.userInfo().shell; - (options.properties as any)["common.release"] = os.release(); - (options.properties as any)["common.arch"] = os.arch(); - // tslint:enable:no-any - } catch (ex) { - // - } - - try { - // tslint:disable-next-line:no-any - (options.properties as any)["common.machineId"] = machineIdSync(); - } catch (ex) { - // - } + options.properties["common.shell"] = os.userInfo().shell; + options.properties["common.release"] = os.release(); + options.properties["common.arch"] = os.arch(); + } catch (error) {} try { const request = https.request({ @@ -75,96 +45,15 @@ export class TelemetryClient { "Content-Type": "application/json", }, }); - request.on("error", () => { - // Do nothing, we don"t really care - }); + request.on("error", () => { /* We don't care. */ }); request.write(JSON.stringify(options)); request.end(); - } catch (ex) { - // Suppress all errs + } catch (error) {} + } + + public flush(options: appInsights.FlushOptions): void { + if (options.callback) { + options.callback(""); } } - - public flush(options: { - readonly callback: () => void; - }): void { - options.callback(); - } } - -// Taken from https://github.com/automation-stack/node-machine-id -import { exec, execSync } from "child_process"; -import { createHash } from "crypto"; - -const isWindowsProcessMixedOrNativeArchitecture = (): "" | "mixed" | "native" => { - // detect if the node binary is the same arch as the Windows OS. - // or if this is 32 bit node on 64 bit windows. - if (process.platform !== "win32") { - return ""; - } - if (process.arch === "ia32" && process.env.hasOwnProperty("PROCESSOR_ARCHITEW6432")) { - return "mixed"; - } - - return "native"; -}; - -let { platform } = process, - win32RegBinPath = { - native: "%windir%\\System32", - mixed: "%windir%\\sysnative\\cmd.exe /c %windir%\\System32", - "": "", - }, - guid = { - darwin: "ioreg -rd1 -c IOPlatformExpertDevice", - win32: `${win32RegBinPath[isWindowsProcessMixedOrNativeArchitecture()]}\\REG ` + - "QUERY HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Cryptography " + - "/v MachineGuid", - linux: "( cat /var/lib/dbus/machine-id /etc/machine-id 2> /dev/null || hostname ) | head -n 1 || :", - freebsd: "kenv -q smbios.system.uuid || sysctl -n kern.hostuuid", - // tslint:disable-next-line:no-any - } as any; - -const hash = (guid: string): string => { - return createHash("sha256").update(guid).digest("hex"); -}; - -const expose = (result: string): string => { - switch (platform) { - case "darwin": - return result - .split("IOPlatformUUID")[1] - .split("\n")[0].replace(/\=|\s+|\"/ig, "") - .toLowerCase(); - case "win32": - return result - .toString() - .split("REG_SZ")[1] - .replace(/\r+|\n+|\s+/ig, "") - .toLowerCase(); - case "linux": - return result - .toString() - .replace(/\r+|\n+|\s+/ig, "") - .toLowerCase(); - case "freebsd": - return result - .toString() - .replace(/\r+|\n+|\s+/ig, "") - .toLowerCase(); - default: - throw new Error(`Unsupported platform: ${process.platform}`); - } -}; - -let cachedMachineId: string | undefined; - -const machineIdSync = (): string => { - if (cachedMachineId) { - return cachedMachineId; - } - let id: string = expose(execSync(guid[platform]).toString()); - cachedMachineId = hash(id); - - return cachedMachineId; -}; diff --git a/src/server.ts b/src/server.ts index cafd76fdf..7653fbb37 100644 --- a/src/server.ts +++ b/src/server.ts @@ -11,6 +11,7 @@ import * as querystring from "querystring"; import { Emitter } from "vs/base/common/event"; import { sanitizeFilePath } from "vs/base/common/extpath"; import { UriComponents, URI } from "vs/base/common/uri"; +import { getMachineId } from 'vs/base/node/id'; import { IPCServer, ClientConnectionEvent, StaticRouter } from "vs/base/parts/ipc/common/ipc"; import { mkdirp } from "vs/base/node/pfs"; import { LogsDataCleaner } from "vs/code/electron-browser/sharedProcess/contrib/logsDataCleaner"; @@ -34,19 +35,25 @@ import { getLogLevel, ILogService } from "vs/platform/log/common/log"; import { LogLevelSetterChannel } from "vs/platform/log/common/logIpc"; import { SpdLogService } from "vs/platform/log/node/spdlogService"; import { IProductConfiguration } from "vs/platform/product/common/product"; +import pkg from "vs/platform/product/node/package"; import product from "vs/platform/product/node/product"; import { ConnectionType, ConnectionTypeRequest } from "vs/platform/remote/common/remoteAgentConnection"; import { REMOTE_FILE_SYSTEM_CHANNEL_NAME } from "vs/platform/remote/common/remoteAgentFileSystemChannel"; import { IRequestService } from "vs/platform/request/node/request"; import { RequestService } from "vs/platform/request/node/requestService"; +import ErrorTelemetry from "vs/platform/telemetry/browser/errorTelemetry"; import { ITelemetryService } from "vs/platform/telemetry/common/telemetry"; -import { NullTelemetryService } from "vs/platform/telemetry/common/telemetryUtils"; +import { NullTelemetryService, LogAppender, combinedAppender } from "vs/platform/telemetry/common/telemetryUtils"; +import { TelemetryService, ITelemetryServiceConfig } from "vs/platform/telemetry/common/telemetryService"; +import { AppInsightsAppender } from "vs/platform/telemetry/node/appInsightsAppender"; +import { resolveCommonProperties } from "vs/platform/telemetry/node/commonProperties"; import { RemoteExtensionLogFileName } from "vs/workbench/services/remote/common/remoteAgentService"; -// import { TelemetryService } from "vs/workbench/services/telemetry/electron-browser/telemetryService"; +import { TelemetryChannel } from "vs/platform/telemetry/node/telemetryIpc"; import { IWorkbenchConstructionOptions } from "vs/workbench/workbench.web.api"; import { Connection, ManagementConnection, ExtensionHostConnection } from "vs/server/src/connection"; import { ExtensionEnvironmentChannel, FileProviderChannel , } from "vs/server/src/channel"; +import { TelemetryClient } from "vs/server/src/insights"; import { Protocol } from "vs/server/src/protocol"; import { getMediaMime, getUriTransformer, useHttpsTransformer } from "vs/server/src/util"; @@ -363,6 +370,7 @@ export class MainServer extends Server { private readonly connections = new Map>(); private readonly services = new ServiceCollection(); + private readonly servicesPromise: Promise; public constructor( options: ServerOptions, @@ -381,39 +389,7 @@ export class MainServer extends Server { protocol.getSocket().dispose(); } }); - - const environmentService = new EnvironmentService(args, process.execPath); - const logService = new SpdLogService(RemoteExtensionLogFileName, environmentService.logsPath, getLogLevel(environmentService)); - this.ipc.registerChannel("loglevel", new LogLevelSetterChannel(logService)); - - const router = new StaticRouter((context: any) => { - return context.clientId === "renderer"; - }); - - this.services.set(ILogService, logService); - this.services.set(IEnvironmentService, environmentService); - this.services.set(IConfigurationService, new SyncDescriptor(ConfigurationService, [environmentService.machineSettingsResource])); - this.services.set(IRequestService, new SyncDescriptor(RequestService)); - this.services.set(IExtensionGalleryService, new SyncDescriptor(ExtensionGalleryService)); - this.services.set(ITelemetryService, NullTelemetryService); // TODO: telemetry - this.services.set(IDialogService, new DialogChannelClient(this.ipc.getChannel("dialog", router))); - this.services.set(IExtensionManagementService, new SyncDescriptor(ExtensionManagementService)); - - const instantiationService = new InstantiationService(this.services); - - this.services.set(ILocalizationsService, instantiationService.createInstance(LocalizationsService)); - - instantiationService.invokeFunction(() => { - instantiationService.createInstance(LogsDataCleaner); - this.ipc.registerChannel(REMOTE_FILE_SYSTEM_CHANNEL_NAME, new FileProviderChannel(environmentService, logService)); - this.ipc.registerChannel("remoteextensionsenvironment", new ExtensionEnvironmentChannel(environmentService, logService)); - const extensionsService = this.services.get(IExtensionManagementService) as IExtensionManagementService; - const extensionsChannel = new ExtensionManagementChannel(extensionsService, (context) => getUriTransformer(context.remoteAuthority)); - this.ipc.registerChannel("extensions", extensionsChannel); - const galleryService = this.services.get(IExtensionGalleryService) as IExtensionGalleryService; - const galleryChannel = new ExtensionGalleryChannel(galleryService); - this.ipc.registerChannel("gallery", galleryChannel); - }); + this.servicesPromise = this.initializeServices(args); } public async listen(): Promise { @@ -456,7 +432,11 @@ export class MainServer extends Server { const remoteAuthority = request.headers.host as string; const transformer = getUriTransformer(remoteAuthority); - await this.webviewServer.listen(); + await Promise.all([ + this.webviewServer.listen(), + this.servicesPromise, + ]); + const webviewEndpoint = this.webviewServer.address(request); const cwd = process.env.VSCODE_CWD || process.cwd(); @@ -577,6 +557,69 @@ export class MainServer extends Server { } } + private async initializeServices(args: ParsedArgs): Promise { + const environmentService = new EnvironmentService(args, process.execPath); + const logService = new SpdLogService(RemoteExtensionLogFileName, environmentService.logsPath, getLogLevel(environmentService)); + this.ipc.registerChannel("loglevel", new LogLevelSetterChannel(logService)); + + const router = new StaticRouter((context: any) => { + return context.clientId === "renderer"; + }); + + this.services.set(ILogService, logService); + this.services.set(IEnvironmentService, environmentService); + this.services.set(IConfigurationService, new SyncDescriptor(ConfigurationService, [environmentService.machineSettingsResource])); + this.services.set(IRequestService, new SyncDescriptor(RequestService)); + this.services.set(IExtensionGalleryService, new SyncDescriptor(ExtensionGalleryService)); + if (!environmentService.args["disable-telemetry"]) { + const version = `${(pkg as any).codeServerVersion || "development"}-vsc${pkg.version}`; + this.services.set(ITelemetryService, new SyncDescriptor(TelemetryService, [{ + appender: combinedAppender( + new AppInsightsAppender("code-server", null, () => new TelemetryClient(), logService), + new LogAppender(logService), + ), + commonProperties: resolveCommonProperties( + product.commit, version, await getMachineId(), + environmentService.installSourcePath, "code-server", + ), + piiPaths: [ + environmentService.appRoot, + environmentService.extensionsPath, + ...environmentService.extraExtensionPaths, + ...environmentService.extraBuiltinExtensionPaths, + ], + } as ITelemetryServiceConfig])); + } else { + this.services.set(ITelemetryService, NullTelemetryService); + } + this.services.set(IDialogService, new DialogChannelClient(this.ipc.getChannel("dialog", router))); + this.services.set(IExtensionManagementService, new SyncDescriptor(ExtensionManagementService)); + + const instantiationService = new InstantiationService(this.services); + + this.services.set(ILocalizationsService, instantiationService.createInstance(LocalizationsService)); + + return new Promise((resolve) => { + instantiationService.invokeFunction(() => { + instantiationService.createInstance(LogsDataCleaner); + this.ipc.registerChannel(REMOTE_FILE_SYSTEM_CHANNEL_NAME, new FileProviderChannel(environmentService, logService)); + const telemetryService = this.services.get(ITelemetryService) as ITelemetryService; + this.ipc.registerChannel("remoteextensionsenvironment", new ExtensionEnvironmentChannel(environmentService, logService, telemetryService)); + const extensionsService = this.services.get(IExtensionManagementService) as IExtensionManagementService; + const extensionsChannel = new ExtensionManagementChannel(extensionsService, (context) => getUriTransformer(context.remoteAuthority)); + this.ipc.registerChannel("extensions", extensionsChannel); + const galleryService = this.services.get(IExtensionGalleryService) as IExtensionGalleryService; + const galleryChannel = new ExtensionGalleryChannel(galleryService); + this.ipc.registerChannel("gallery", galleryChannel); + const telemetryChannel = new TelemetryChannel(telemetryService); + this.ipc.registerChannel("telemetry", telemetryChannel); + // tslint:disable-next-line no-unused-expression + new ErrorTelemetry(telemetryService); + resolve(); + }); + }); + } + /** * TODO: implement. */