Compare commits

...

15 Commits

Author SHA1 Message Date
Asher
3d5db8313a Add secure domain to requirements 2019-10-30 10:33:07 -05:00
Asher
73cf8f34e3 Fix outgoing scheme transformation
Accidentally used local instead of remote.

Fixes #1127.
2019-10-30 10:32:57 -05:00
dependabot[bot]
766efd6079 Bump mixin-deep from 1.3.1 to 1.3.2 (#1126)
Bumps [mixin-deep](https://github.com/jonschlinkert/mixin-deep) from 1.3.1 to 1.3.2.
- [Release notes](https://github.com/jonschlinkert/mixin-deep/releases)
- [Commits](https://github.com/jonschlinkert/mixin-deep/compare/1.3.1...1.3.2)

Signed-off-by: dependabot[bot] <support@github.com>
2019-10-29 15:20:12 -05:00
Asher
87485948ad Kill inner process if parent process dies
Fixes #1076.
2019-10-29 14:43:27 -05:00
Asher
7e4a73ce2d Fix schema matching against vscode-remote
Fixes #1104.
2019-10-29 11:42:28 -05:00
Asher
2f0878d9b7 Revert remote scheme change
It doesn't show in the explorer anymore so there's no point. Also remove
the local scheme transform which is no longer required with the latest
client-side extension implementation.
2019-10-29 11:26:50 -05:00
Marc-André Daigneault
f65c9b23fc Add docker-compose file (#680) 2019-10-29 11:08:01 -05:00
Asher
cd859d117f Start pushing to latest Docker tag 2019-10-29 11:04:38 -05:00
Asher
e22964915a Support opening workspaces from command line
Partly addresses #1121.
2019-10-28 16:25:51 -05:00
Asher
197d0b6ca9 Strip internal env vars when spawning the shell
This should fix all those reports of code-server dropping straight to
Node and things like #1121.
2019-10-28 16:08:32 -05:00
Asher
422503ef98 Proxy child exit code when exiting parent process
This fixes code-server exiting with zero on errors.
2019-10-28 14:57:01 -05:00
Asher
ea36345d2c Allow fetching any resource
Fixes #1118.
2019-10-28 14:29:51 -05:00
Asher
a89d83cbba Fix other incorrect usages of split 2019-10-28 14:03:13 -05:00
Asher
83ff31b620 Fix passwords that contain =
Fixes #1119.

Apparently `split` does not work the way I'd expect.
2019-10-28 13:47:31 -05:00
Asher
3a9b032c72 Add heartbeat file (#1115)
Fixes #1050.
2019-10-28 09:59:34 -05:00
10 changed files with 167 additions and 44 deletions

View File

@@ -61,7 +61,7 @@ deploy:
- provider: script
skip_cleanup: true
script: docker build -f ./scripts/ci.dockerfile -t codercom/code-server:"$TAG" -t codercom/code-server:v2 . && docker push codercom/code-server:"$TAG" && docker push codercom/code-server:v2
script: docker build -f ./scripts/ci.dockerfile -t codercom/code-server:"$TAG" -t codercom/code-server:v2 -t codercom/code-server . && docker push codercom/code-server:"$TAG" && docker push codercom/code-server:v2 && docker push codercom/code-server
on:
repo: cdr/code-server
branch: master

View File

@@ -22,10 +22,11 @@ docker run -it -p 127.0.0.1:8080:8080 -v "${HOME}/.local/share/code-server:/home
### Requirements
- Minimum GLIBC version of 2.17 and a minimum version of GLIBCXX of 3.4.15.
- This is the main requirement for building Visual Studio Code. We cannot go lower than this.
- A 64-bit host with at least 1GB RAM and 2 cores.
- 1 core hosts would work but not optimally.
- 64-bit host.
- At least 1GB of RAM.
- 2 cores or more are recommended (1 core works but not optimally).
- Secure connection over HTTPS or localhost (required for service workers).
- For Linux: GLIBC 2.17 or later and GLIBCXX 3.4.15 or later.
- Docker (for Docker versions of `code-server`).
### Run over SSH

13
docker-compose.yml Normal file
View File

@@ -0,0 +1,13 @@
version: "3"
services:
code-server:
container_name: code-server
image: codercom/code-server
ports:
- "8080:8080"
volumes:
- "${PWD}:/home/coder/project"
- "${HOME}/.local/share/code-server:/home/coder/.local/share/code-server"
environment:
PASSWORD: ${PASSWORD}

View File

@@ -1,19 +1,8 @@
diff --git a/src/vs/base/common/network.ts b/src/vs/base/common/network.ts
index 6d41e85e42..f845d0bf9e 100644
index 6d41e85e42..64f39687a4 100644
--- a/src/vs/base/common/network.ts
+++ b/src/vs/base/common/network.ts
@@ -48,7 +48,9 @@ export namespace Schemas {
export const command: string = 'command';
- export const vscodeRemote: string = 'vscode-remote';
+ // NOTE@coder: Changed this so it'll be reflected in the explorer to prevent
+ // confusion with vscode-remote itself.
+ export const vscodeRemote: string = 'code-server';
export const vscodeRemoteResource: string = 'vscode-remote-resource';
@@ -96,12 +98,12 @@ class RemoteAuthoritiesImpl {
@@ -96,12 +96,12 @@ class RemoteAuthoritiesImpl {
if (host && host.indexOf(':') !== -1) {
host = `[${host}]`;
}
@@ -50,6 +39,21 @@ index a657f4a4d9..66bd13dffa 100644
} else if (typeof process === 'object') {
_isWindows = (process.platform === 'win32');
_isMacintosh = (process.platform === 'darwin');
diff --git a/src/vs/base/common/processes.ts b/src/vs/base/common/processes.ts
index c52f7b3774..5635cfac8a 100644
--- a/src/vs/base/common/processes.ts
+++ b/src/vs/base/common/processes.ts
@@ -110,7 +110,9 @@ export function sanitizeProcessEnvironment(env: IProcessEnvironment, ...preserve
/^ELECTRON_.+$/,
/^GOOGLE_API_KEY$/,
/^VSCODE_.+$/,
- /^SNAP(|_.*)$/
+ /^SNAP(|_.*)$/,
+ /^NBIN_BYPASS$/,
+ /^LAUNCH_VSCODE$/
];
const envKeys = Object.keys(env);
envKeys
diff --git a/src/vs/base/node/languagePacks.js b/src/vs/base/node/languagePacks.js
index 3ae24454cb..fac8679290 100644
--- a/src/vs/base/node/languagePacks.js
@@ -615,6 +619,34 @@ index 84c46faa36..957e8412e1 100644
if (!this.configuration.userDataProvider) {
const remoteUserDataUri = this.getRemoteUserDataUri();
diff --git a/src/vs/workbench/common/resources.ts b/src/vs/workbench/common/resources.ts
index 53de865d8f..df234821a9 100644
--- a/src/vs/workbench/common/resources.ts
+++ b/src/vs/workbench/common/resources.ts
@@ -15,6 +15,7 @@ import { ParsedExpression, IExpression, parse } from 'vs/base/common/glob';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration';
import { withNullAsUndefined } from 'vs/base/common/types';
+import { Schemas } from 'vs/base/common/network';
export class ResourceContextKey extends Disposable implements IContextKey<URI> {
@@ -63,7 +64,7 @@ export class ResourceContextKey extends Disposable implements IContextKey<URI> {
set(value: URI | null) {
if (!ResourceContextKey._uriEquals(this._resourceKey.get(), value)) {
this._resourceKey.set(value);
- this._schemeKey.set(value ? value.scheme : null);
+ this._schemeKey.set(value ? (value.scheme === Schemas.vscodeRemote ? Schemas.file : value.scheme) : null);
this._filenameKey.set(value ? basename(value) : null);
this._langIdKey.set(value ? this._modeService.getModeIdByFilepathOrFirstLine(value) : null);
this._extensionKey.set(value ? extname(value) : null);
@@ -200,4 +201,4 @@ export class ResourceGlobMatcher extends Disposable {
return !!expressionForRoot(resourcePathToMatch);
}
-}
\ No newline at end of file
+}
diff --git a/src/vs/workbench/contrib/files/browser/files.contribution.ts b/src/vs/workbench/contrib/files/browser/files.contribution.ts
index 1f4cd95f65..061931cbde 100644
--- a/src/vs/workbench/contrib/files/browser/files.contribution.ts

View File

@@ -3,15 +3,16 @@ import { URI } from "vs/base/common/uri";
import { registerSingleton } from "vs/platform/instantiation/common/extensions";
import { ServiceCollection } from "vs/platform/instantiation/common/serviceCollection";
import { ILocalizationsService } from "vs/platform/localizations/common/localizations";
import { LocalizationsService } from "vs/workbench/services/localizations/electron-browser/localizationsService";
import { PersistentConnectionEventType } from "vs/platform/remote/common/remoteAgentConnection";
import { ITelemetryService } from "vs/platform/telemetry/common/telemetry";
import { coderApi, vscodeApi } from "vs/server/src/browser/api";
import { IUploadService, UploadService } from "vs/server/src/browser/upload";
import { INodeProxyService, NodeProxyChannelClient } from "vs/server/src/common/nodeProxy";
import { TelemetryChannelClient } from "vs/server/src/common/telemetry";
import { split } from "vs/server/src/common/util";
import "vs/workbench/contrib/localizations/browser/localizations.contribution";
import { LocalizationsService } from "vs/workbench/services/localizations/electron-browser/localizationsService";
import { IRemoteAgentService } from "vs/workbench/services/remote/common/remoteAgentService";
import { PersistentConnectionEventType } from "vs/platform/remote/common/remoteAgentConnection";
class TelemetryService extends TelemetryChannelClient {
public constructor(
@@ -79,7 +80,7 @@ export const withQuery = (url: string, replace: Query): string => {
const uri = URI.parse(url);
const query = { ...replace };
uri.query.split("&").forEach((kv) => {
const [key, value] = kv.split("=", 2);
const [key, value] = split(kv, "=");
if (!(key in query)) {
query[key] = value;
}

10
src/common/util.ts Normal file
View File

@@ -0,0 +1,10 @@
/**
* Split a string up to the delimiter. If the delimiter doesn't exist the first
* item will have all the text and the second item will be an empty string.
*/
export const split = (str: string, delimiter: string): [string, string] => {
const index = str.indexOf(delimiter);
return index !== -1
? [str.substring(0, index).trim(), str.substring(index + 1)]
: [str, ""];
};

View File

@@ -90,7 +90,7 @@ const startVscode = async (): Promise<void | void[]> => {
basePath: args["base-path"],
cert: args.cert,
certKey: args["cert-key"],
folderUri: extra.length > 1 ? extra[extra.length - 1] : undefined,
openUri: extra.length > 1 ? extra[extra.length - 1] : undefined,
host: args.host,
password: process.env.PASSWORD,
};
@@ -204,6 +204,7 @@ export class WrapperProcess {
logger.info("Relaunching...");
this.started = undefined;
if (this.process) {
this.process.removeAllListeners();
this.process.kill();
}
try {
@@ -223,7 +224,9 @@ export class WrapperProcess {
public start(): Promise<void> {
if (!this.started) {
const child = this.spawn();
this.started = ipcMain.handshake(child);
this.started = ipcMain.handshake(child).then(() => {
child.once("exit", (code) => exit(code!));
});
this.process = child;
}
return this.started;
@@ -234,6 +237,7 @@ export class WrapperProcess {
env: {
...process.env,
LAUNCH_VSCODE: "true",
VSCODE_PARENT_PID: process.pid.toString(),
},
stdio: ["inherit", "inherit", "inherit", "ipc"],
});
@@ -254,6 +258,20 @@ process.exit = function (code?: number) {
console.warn(err.stack);
} as (code?: number) => never;
// Copy the extension host behavior of killing oneself if the parent dies. This
// also exists in bootstrap-fork.js but spawning with that won't work because we
// override process.exit.
if (typeof process.env.VSCODE_PARENT_PID !== "undefined") {
const parentPid = parseInt(process.env.VSCODE_PARENT_PID, 10);
setInterval(() => {
try {
process.kill(parentPid, 0); // Throws an exception if the process doesn't exist anymore.
} catch (e) {
exit();
}
}, 5000);
}
// It's possible that the pipe has closed (for example if you run code-server
// --version | head -1). Assume that means we're done.
if (!process.stdout.isTTY) {

View File

@@ -56,6 +56,7 @@ import { resolveCommonProperties } from "vs/platform/telemetry/node/commonProper
import { UpdateChannel } from "vs/platform/update/electron-main/updateIpc";
import { INodeProxyService, NodeProxyChannel } from "vs/server/src/common/nodeProxy";
import { TelemetryChannel } from "vs/server/src/common/telemetry";
import { split } from "vs/server/src/common/util";
import { ExtensionEnvironmentChannel, FileProviderChannel, NodeProxyService } from "vs/server/src/node/channel";
import { Connection, ExtensionHostConnection, ManagementConnection } from "vs/server/src/node/connection";
import { TelemetryClient } from "vs/server/src/node/insights";
@@ -115,7 +116,7 @@ export interface ServerOptions {
readonly connectionToken?: string;
readonly cert?: string;
readonly certKey?: string;
readonly folderUri?: string;
readonly openUri?: string;
readonly host?: string;
readonly password?: string;
readonly port?: number;
@@ -212,8 +213,8 @@ export abstract class Server {
}
protected withBase(request: http.IncomingMessage, path: string): string {
const split = request.url ? request.url.split("?", 2) : [];
return `${this.protocol}://${request.headers.host}${this.options.basePath}${path}${split.length === 2 ? `?${split[1]}` : ""}`;
const [, query] = request.url ? split(request.url, "?") : [];
return `${this.protocol}://${request.headers.host}${this.options.basePath}${path}${query ? `?${query}` : ""}`;
}
private isAllowedRequestPath(path: string): boolean {
@@ -440,8 +441,8 @@ export abstract class Server {
const cookies: { [key: string]: string } = {};
if (request.headers.cookie) {
request.headers.cookie.split(";").forEach((keyValue) => {
const [key, value] = keyValue.split("=", 2);
cookies[key.trim()] = decodeURI(value);
const [key, value] = split(keyValue, "=");
cookies[key] = decodeURI(value);
});
}
return cookies as T;
@@ -474,6 +475,9 @@ export class MainServer extends Server {
private readonly proxyTimeout = 5000;
private settings: Settings = {};
private heartbeatTimer?: NodeJS.Timeout;
private heartbeatInterval = 60000;
private lastHeartbeat = 0;
public constructor(options: ServerOptions, args: ParsedArgs) {
super(options);
@@ -491,6 +495,7 @@ export class MainServer extends Server {
}
protected async handleWebSocket(socket: net.Socket, parsedUrl: url.UrlWithParsedQuery): Promise<void> {
this.heartbeat();
if (!parsedUrl.query.reconnectionToken) {
throw new Error("Reconnection token is missing from query parameters");
}
@@ -514,12 +519,13 @@ export class MainServer extends Server {
parsedUrl: url.UrlWithParsedQuery,
request: http.IncomingMessage,
): Promise<Response> {
this.heartbeat();
switch (base) {
case "/": return this.getRoot(request, parsedUrl);
case "/resource":
case "/vscode-remote-resource":
if (typeof parsedUrl.query.path === "string") {
return this.getResource(parsedUrl.query.path);
return this.getAnyResource(parsedUrl.query.path);
}
break;
case "/tar":
@@ -546,9 +552,9 @@ export class MainServer extends Server {
util.promisify(fs.readFile)(filePath, "utf8"),
this.getFirstValidPath([
{ path: parsedUrl.query.workspace, workspace: true },
{ path: parsedUrl.query.folder },
{ path: parsedUrl.query.folder, workspace: false },
(await this.readSettings()).lastVisited,
{ path: this.options.folderUri }
{ path: this.options.openUri }
]),
this.servicesPromise,
]);
@@ -592,7 +598,9 @@ export class MainServer extends Server {
}
/**
* Choose the first valid path.
* Choose the first valid path. If `workspace` is undefined then either a
* workspace or a directory are acceptable. Otherwise it must be a file if a
* workspace or a directory otherwise.
*/
private async getFirstValidPath(startPaths: Array<StartPath | undefined>): Promise<{ uri: URI, workspace?: boolean} | undefined> {
const logger = this.services.get(ILogService) as ILogService;
@@ -607,9 +615,8 @@ export class MainServer extends Server {
const uri = URI.file(sanitizeFilePath(paths[j], cwd));
try {
const stat = await util.promisify(fs.stat)(uri.fsPath);
// Workspace must be a file.
if (!!startPath.workspace !== stat.isDirectory()) {
return { uri, workspace: startPath.workspace };
if (typeof startPath.workspace === "undefined" || startPath.workspace !== stat.isDirectory()) {
return { uri, workspace: !stat.isDirectory() };
}
} catch (error) {
logger.warn(error.message);
@@ -876,4 +883,48 @@ export class MainServer extends Server {
(this.services.get(ILogService) as ILogService).warn(error.message);
}
}
/**
* Return the file path for the heartbeat file.
*/
private get heartbeatPath(): string {
const environment = this.services.get(IEnvironmentService) as IEnvironmentService;
return path.join(environment.userDataPath, "heartbeat");
}
/**
* Return all online connections regardless of type.
*/
private get onlineConnections(): Connection[] {
const online = <Connection[]>[];
this.connections.forEach((connections) => {
connections.forEach((connection) => {
if (typeof connection.offline === "undefined") {
online.push(connection);
}
});
});
return online;
}
/**
* Write to the heartbeat file if we haven't already done so within the
* timeout and start or reset a timer that keeps running as long as there are
* active connections. Failures are logged as warnings.
*/
private heartbeat(): void {
const now = Date.now();
if (now - this.lastHeartbeat >= this.heartbeatInterval) {
util.promisify(fs.writeFile)(this.heartbeatPath, "").catch((error) => {
(this.services.get(ILogService) as ILogService).warn(error.message);
});
this.lastHeartbeat = now;
clearTimeout(this.heartbeatTimer!); // We can clear undefined so ! is fine.
this.heartbeatTimer = setTimeout(() => {
if (this.onlineConnections.length > 0) {
this.heartbeat();
}
}, this.heartbeatInterval);
}
}
}

View File

@@ -4,22 +4,19 @@ module.exports = (remoteAuthority) => {
return {
transformIncoming: (uri) => {
switch (uri.scheme) {
case "code-server": return { scheme: "file", path: uri.path };
case "file": return { scheme: "code-server", path: uri.path };
case "vscode-remote": return { scheme: "file", path: uri.path };
default: return uri;
}
},
transformOutgoing: (uri) => {
switch (uri.scheme) {
case "code-server": return { scheme: "file", path: uri.path };
case "file": return { scheme: "code-server", authority: remoteAuthority, path: uri.path };
case "file": return { scheme: "vscode-remote", authority: remoteAuthority, path: uri.path };
default: return uri;
}
},
transformOutgoingScheme: (scheme) => {
switch (scheme) {
case "code-server": return "file";
case "file": return "code-server";
case "file": return "vscode-remote";
default: return scheme;
}
},

View File

@@ -1256,9 +1256,9 @@ minizlib@^1.1.1:
minipass "^2.2.1"
mixin-deep@^1.2.0:
version "1.3.1"
resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.1.tgz#a49e7268dce1a0d9698e45326c5626df3543d0fe"
integrity sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==
version "1.3.2"
resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566"
integrity sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==
dependencies:
for-in "^1.0.2"
is-extendable "^1.0.1"