From c3a38e3fea8a28afa4017cf6c45ba12e82ff1feb Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Wed, 17 Apr 2019 17:18:35 -0500 Subject: [PATCH] Add telemetry and option to disable (#519) * Add telemetry and option to disable * Update readme and getting-started guide * Update lockfile * Update getting started guide --- README.md | 6 + doc/self-hosted/index.md | 40 +++-- packages/server/package.json | 2 +- packages/server/src/cli.ts | 6 + packages/server/src/vscode/sharedProcess.ts | 1 + packages/server/yarn.lock | 8 +- .../vscode/src/fill/applicationInsights.ts | 170 ++++++++++++++++++ packages/vscode/src/fill/product.ts | 9 +- packages/vscode/webpack.bootstrap.config.js | 1 + 9 files changed, 219 insertions(+), 24 deletions(-) create mode 100644 packages/vscode/src/fill/applicationInsights.ts diff --git a/README.md b/README.md index 547c336c1..8793a606e 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,12 @@ How to [secure your setup](/doc/security/ssl.md). 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 or set `DISABLE_TELEMETRY=true` to disable tracking ENTIRELY. + +We use data collected to improve code-server. + ## Contributing Development guides are coming soon. diff --git a/doc/self-hosted/index.md b/doc/self-hosted/index.md index c3ee6a998..4a8934cbb 100644 --- a/doc/self-hosted/index.md +++ b/doc/self-hosted/index.md @@ -34,28 +34,29 @@ It takes just a few minutes to get your own self-hosted server running. If you'v code-server can be ran with a number of arguments to customize your working directory, host, port, and SSL certificate. ``` -USAGE - $ code-server [WORKDIR] +Usage: code-server [options] -ARGUMENTS - WORKDIR [default: (directory to binary)] Specify working dir +Run VS Code on a remote server. -OPTIONS - -d, --data-dir=data-dir - -h, --host=host [default: 0.0.0.0] - -o, --open Open in browser on startup - -p, --port=port [default: 8443] Port to bind on - -v, --version show CLI version - --allow-http - --cert=cert - --cert-key=cert-key - --help show CLI help - --no-auth - --password=password +Options: + -V, --version output the version number + --cert + --cert-key + -e, --extensions-dir Set the root path for extensions. + -d --user-data-dir Specifies the directory that user data is kept in, useful when running as root. + --data-dir DEPRECATED: Use '--user-data-dir' instead. Customize where user-data is stored. + -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. + -P, --password Specify a password for authentication. + --disable-telemetry Disables ALL telemetry. + -h, --help output usage information ``` ### Data Directory - Use `code-server -d (path/to/directory)` or `code-server --data-dir=(path/to/directory)`, excluding the parentheses to specify the root folder that VS Code will start in + Use `code-server -d (path/to/directory)` or `code-server --data-dir=(path/to/directory)`, excluding the parentheses to specify the root folder that VS Code will start in. ### 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. @@ -68,6 +69,9 @@ OPTIONS 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` + ### Telemetry + Disable all telemetry with `code-server --disable-telemetry`. + ### 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` @@ -116,4 +120,4 @@ OPTIONS *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 -h` or `code-server --help` to view the usage for the cli. This is also shown at the beginning of this section. + Use `code-server --help` to view the usage for the CLI. This is also shown at the beginning of this section. diff --git a/packages/server/package.json b/packages/server/package.json index 3f6062c03..88e71d9d0 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -9,7 +9,7 @@ "build:binary": "ts-node scripts/nbin.ts" }, "dependencies": { - "@coder/nbin": "^1.1.1", + "@coder/nbin": "^1.1.2", "commander": "^2.19.0", "express": "^4.16.4", "express-static-gzip": "^1.1.3", diff --git a/packages/server/src/cli.ts b/packages/server/src/cli.ts index 45d2d5530..e57a6af0d 100644 --- a/packages/server/src/cli.ts +++ b/packages/server/src/cli.ts @@ -29,6 +29,7 @@ commander.version(process.env.VERSION || "development") .option("-N, --no-auth", "Start without requiring authentication.", undefined) .option("-H, --allow-http", "Allow http connections.", false) .option("-P, --password ", "Specify a password for authentication.") + .option("--disable-telemetry", "Disables ALL telemetry.", false) .option("--install-extension ", "Install an extension by its ID.") .option("--bootstrap-fork ", "Used for development. Never set.") .option("--extra-args ", "Used for development. Never set.") @@ -52,6 +53,7 @@ const bold = (text: string | number): string | number => { readonly allowHttp: boolean; readonly host: string; readonly port: number; + readonly disableTelemetry: boolean; readonly userDataDir?: string; readonly extensionsDir?: string; @@ -68,6 +70,10 @@ const bold = (text: string | number): string | number => { readonly extraArgs?: string; }; + if (options.disableTelemetry) { + process.env.DISABLE_TELEMETRY = "true"; + } + // Commander has an exception for `--no` prefixes. Here we'll adjust that. // tslint:disable-next-line:no-any const noAuthValue = (commander as any).auth; diff --git a/packages/server/src/vscode/sharedProcess.ts b/packages/server/src/vscode/sharedProcess.ts index cba3519e3..675b8b53f 100644 --- a/packages/server/src/vscode/sharedProcess.ts +++ b/packages/server/src/vscode/sharedProcess.ts @@ -92,6 +92,7 @@ export class SharedProcess { env: { VSCODE_ALLOW_IO: "true", VSCODE_LOGS: process.env.VSCODE_LOGS, + DISABLE_TELEMETRY: process.env.DISABLE_TELEMETRY, }, }, this.userDataDir); this.activeProcess = activeProcess; diff --git a/packages/server/yarn.lock b/packages/server/yarn.lock index 025be5927..b97e1daae 100644 --- a/packages/server/yarn.lock +++ b/packages/server/yarn.lock @@ -7,10 +7,10 @@ resolved "https://registry.yarnpkg.com/@coder/logger/-/logger-1.0.3.tgz#e0e1ae5496fde5a3c6ef3d748fdfb26a55add8b8" integrity sha512-1o5qDZX2VZUNnzgz5KfAdMnaqaX6FNeTs0dUdg73MRHfQW94tFTIryFC1xTTCuzxGDjVHOHkaUAI4uHA2bheOA== -"@coder/nbin@^1.1.1": - version "1.1.1" - resolved "https://registry.yarnpkg.com/@coder/nbin/-/nbin-1.1.1.tgz#0690928fb1306ee2a84120c8ae8ba221c338b190" - integrity sha512-SDlW0dNw6N5Ge3XlI6nbQV7G7dvTYqxzhN0douJlD56upaU4C130g0FCrhLPU/H4gT3SdZVfWoWc4AGv2fhZZw== +"@coder/nbin@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@coder/nbin/-/nbin-1.1.2.tgz#3af9e4368f37532da446c7c291d476bb52de995d" + integrity sha512-MkwKpmu1SU9wkBwQ+bZVU2nPzENWUa3Isut9osVq3LG+udovsk+k5c5rjfJ1q8cf4km5snjOSYiulug3n9sdgw== dependencies: "@coder/logger" "^1.0.3" fs-extra "^7.0.1" diff --git a/packages/vscode/src/fill/applicationInsights.ts b/packages/vscode/src/fill/applicationInsights.ts new file mode 100644 index 000000000..bb94c7378 --- /dev/null +++ b/packages/vscode/src/fill/applicationInsights.ts @@ -0,0 +1,170 @@ +/** + * Used by node + */ +import * as https from "https"; +import * as os from "os"; + +export const defaultClient = "filler"; + +export class TelemetryClient { + public channel = { + setUseDiskRetryCaching: (): void => undefined, + }; + + public constructor() { + // + } + + public trackEvent(options: { + name: string; + properties: object; + measurements: object; + }): void { + if (!options.properties) { + options.properties = {}; + } + if (!options.measurements) { + options.measurements = {}; + } + + 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 + } + + try { + // tslint:disable-next-line:no-any + (options.measurements as any).memory = { + virtual_free: os.freemem(), + virtual_used: os.totalmem(), + }; + } catch (ex) { + // + } + + 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) { + // + } + + try { + const request = https.request({ + host: "v1.telemetry.coder.com", + port: 443, + path: "/track", + method: "POST", + headers: { + "Content-Type": "application/json", + }, + }); + request.on("error", () => { + // Do nothing, we don"t really care + }); + request.write(JSON.stringify(options)); + request.end(); + } catch (ex) { + // Suppress all errs + } + } + + 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/packages/vscode/src/fill/product.ts b/packages/vscode/src/fill/product.ts index 1fa3c66d5..0350b917f 100644 --- a/packages/vscode/src/fill/product.ts +++ b/packages/vscode/src/fill/product.ts @@ -12,6 +12,12 @@ class Product implements IProductConfiguration { public tipsAndTricksUrl = "https://code.visualstudio.com/docs/getstarted/tips-and-tricks"; public twitterUrl = "https://twitter.com/code"; public licenseUrl = "https://github.com/codercom/code-server/blob/master/LICENSE"; + public aiConfig = process.env.DISABLE_TELEMETRY ? undefined! : { + // Only needed so vscode can see that content exists for this value. + // We override the application insights module. + asimovKey: "content", + }; + public enableTelemetry = process.env.DISABLE_TELEMETRY ? false : true; private _dataFolderName: string | undefined; public get dataFolderName(): string { @@ -26,7 +32,8 @@ class Product implements IProductConfiguration { serviceUrl: global && global.process && global.process.env.SERVICE_URL || process.env.SERVICE_URL || "https://v1.extapi.coder.com", - }; + // tslint:disable-next-line:no-any + } as any; public extensionExecutionEnvironments = { "wayou.vscode-todo-highlight": "worker", diff --git a/packages/vscode/webpack.bootstrap.config.js b/packages/vscode/webpack.bootstrap.config.js index 38156a6b1..c3d667fae 100644 --- a/packages/vscode/webpack.bootstrap.config.js +++ b/packages/vscode/webpack.bootstrap.config.js @@ -55,6 +55,7 @@ module.exports = merge( "vscode-sqlite3": path.resolve(fills, "empty.ts"), "vs/base/browser/browser": path.resolve(fills, "empty.ts"), + "applicationinsights": path.join(vsFills, "applicationInsights.ts"), "electron": path.join(vsFills, "stdioElectron.ts"), "vscode-ripgrep": path.join(vsFills, "ripgrep.ts"), "native-keymap": path.join(vsFills, "native-keymap.ts"),