Compare commits

..

92 Commits

Author SHA1 Message Date
Asher
2eba7af117 Use code-server commit in product.json
Also remove github.com from the trusted domains.  This causes the
browser to block the popup instead (probably because the space between
interaction and popup is too great), which is difficult to notice in
Chromium.  Even in Firefox with the extra bar they add at the top it can
be easy to miss.
2023-09-29 13:58:01 -08:00
Asher
0dc59c0da2 Add trusted extension list
Also trust github.com to make the auth flow smoother.
2023-09-29 10:39:00 -08:00
Asher
6275520348 Fix incorrect argon2 target in arm builds (#6453)
* Fix building from source on arm

Not building from source causes argon2 to pull the wrong arch, so we
have to build from source.

But building from source is causing the new Kerberos module to fail on
arm64 and keytar to fail on both.

The latter has been very difficult to debug because the GitHub image
provides a different result to containers based on Ubuntu 20.04.
Because of this, use a container instead.

Use debian:buster as the container because it is easier to set up the
architecture sources (no need to modify the sources) and because it
seems to come with glibc 2.28 rather than 2.31.

Also use the exact version of Node (18.15.0) for reproducibility.

* Set owner and group during tar to zero

Otherwise you get IDs that can cause (benign) errors while extracting,
which might be confusing.  At the very least, I did not see these errors
from previous tars (although they seem to use 1001).

There is no guarantee what IDs might exist so 0 seems the most
reasonable.
2023-09-27 19:17:47 -08:00
Asher
2bb51a25a9 Refresh proxy URI patch 2023-09-26 12:19:13 -08:00
Asher
7a213161f2 Build from source on cross-compile step
The comment said the issue with argon2 was related to CentOS 7 but the
cross-compile steps never used CentOS 7 so maybe the real issue is with
the architecture.
2023-09-26 09:58:53 -08:00
Asher
a1131fadf2 Enable secret storage (#6450)
* Remove unused dependency patch

* Enable secret storage based on local storage

* Remove unnecessary GitHub auth patch

It works now without the patch.
2023-09-26 08:35:41 -08:00
Asher
468cf5c6ce Update npm publish workflow to use v18 2023-09-22 17:53:56 -08:00
Asher
800bf364d6 Release v4.17.0 2023-09-22 17:49:40 -08:00
DevMirza
5428442628 feat(lang): Add Urdu Locale (#6444) 2023-09-22 17:16:51 -08:00
Dov Benyomin Sohacheski
020f280458 Add .local mount to example Docker command (#6419) 2023-09-22 17:07:07 -08:00
Asher
58f6e24a07 Fix installation of kerberos module on armv7l (#6442)
Also build with the same version of Node we will release with.
2023-09-22 00:54:14 -08:00
Asher
7868f4db23 Fix cross-compilation (#6441)
* Avoid packaging yarn.lock

Since the shrinkwrap is what we want everything to use.

* Build with npm

It seems we stuck with yarn because npm was giving us errors but I will try
sorting it out now so we can build with npm as originally intended.

* Remove build from source

Not using CentOS 7 anymore so based on the comment we no longer need
this.  Keytar seems to install fine now.

* Update missed Node version

These numbers are all over the place.

* npm_config_arch must be lowercase

* Patch out Kerberos

I am not sure exactly how it is used but I think it is not a path code-server
worries about, at least not right now.  Just going to patch it out rather than
figure out how to build it on armv7l but we can revisit later.
2023-09-21 23:21:01 -08:00
Asher
acc50a5d36 Update dependencies and force-update qs (#6440)
* Update dependencies and force-update qs

This is mainly an attempt to get rid of as many resolutions as possible
since it seems they are unnecessary except for qs (according to yarn/npm
audit).

For qs use 6.9.7 since Express is using 6.9.6 and that matches the most
closely.

Also add overrides since this is npm's version of yarn's resolutions and
we need it for the shrinkwrap to generate with the right dependencies.

Decided to keep pinning @types/node as well although I am not sure it is
necessary.  Express is pulling in v20 types.  Since this is
development-only we only need it in resolutions.

* Run formatter

Some rules seem to have changed with the dependency updates.

* Replace deprecated bodyParser.json() usage

* Audit npm shrinkwrap as well

* Skip installing dependencies in audit

It seems the tools only require the lock files.

* Fix tests when using ipv6

* Add missing openssl dependency to flake
2023-09-21 16:13:34 -08:00
Asher
47ee7ae670 Bump minimum glibc to 2.28 (#6439)
At least, for the standalone and for anyone running on default Node 18.

If support for 2.17 is needed then one would need to build Node 18 with 2.17 and then build code-server with that version (specifically, the native npm modules).
2023-09-21 13:42:17 -08:00
Asher
70d0c603cc Update to VS Code 1.82.2 (#6436)
* Update VS Code to 1.82.2

* Add new libkrb5 dependency

* Update patches

The only changes were to context except:

- The URL callback provider uses a new _callbackRoute argument and moved
  locations.
- The telemetry provider gets passed the request service as the first
  argument now.
- CSP hash changed, as usual.

* Update Node to v18

* Revert back to es2020

es2022 is breaking Safari.
2023-09-20 15:33:28 -08:00
dependabot[bot]
2e29c233ea chore: bump tj-actions/changed-files from 37 to 38 (#6417)
Bumps [tj-actions/changed-files](https://github.com/tj-actions/changed-files) from 37 to 38.
- [Release notes](https://github.com/tj-actions/changed-files/releases)
- [Changelog](https://github.com/tj-actions/changed-files/blob/main/HISTORY.md)
- [Commits](https://github.com/tj-actions/changed-files/compare/v37...v38)

---
updated-dependencies:
- dependency-name: tj-actions/changed-files
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-18 11:54:56 -08:00
dependabot[bot]
331a017309 chore: bump argon2 from 0.30.3 to 0.31.0 (#6415)
Bumps [argon2](https://github.com/ranisalt/node-argon2) from 0.30.3 to 0.31.0.
- [Release notes](https://github.com/ranisalt/node-argon2/releases)
- [Commits](https://github.com/ranisalt/node-argon2/compare/v0.30.3...v0.31.0)

---
updated-dependencies:
- dependency-name: argon2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-18 11:53:31 -08:00
dependabot[bot]
f915d1e2c8 chore: bump eslint from 8.44.0 to 8.48.0 (#6414)
Bumps [eslint](https://github.com/eslint/eslint) from 8.44.0 to 8.48.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v8.44.0...v8.48.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-18 11:52:54 -08:00
Kaoru Yamamoto
95bcf101d7 Add Japanese locale (#6433) 2023-09-18 17:23:03 +00:00
James MacDonald
d80568df20 Add pod annotations (#6432) 2023-09-16 00:31:05 +00:00
Asher
03dc8cd808 Propagate post-installation failures
pipefail might be ideal here but not sure how wide the support is yet
considering this may run on plain sh.
2023-09-11 09:27:29 -08:00
Asher
3e1c00e017 Run formatter
Not sure why CI missed these, will have to look into it.
2023-09-11 09:27:28 -08:00
Cyanoure
a76e5241b6 Add "CODE_SERVER_HOST" environment variable (#6423) 2023-09-08 22:50:11 +00:00
Asher
913fc30866 Upgrade @coder/logger (#6409)
This gets rid of the unused peer dependency that was pulling in
protobufjs.
2023-08-30 12:10:34 -08:00
Asher
eb498b0d6d Remove humanPath (#6404)
The tilde is ambiguous and it can be helpful to know exactly what paths
code-server is trying to use, especially if it is running as a different
user than you expected.
2023-08-29 14:25:24 -08:00
Antoine
eb8099ff89 feat(helm): Don't create Secret if existingSecret is provided (#6397) 2023-08-28 11:01:11 -08:00
Efren
56d3548fa2 Add braces to Caddyfile example (#6382) 2023-08-10 00:44:23 +00:00
jothi prasath
2d335bd1ea Update screenshots (#6372) 2023-08-07 19:02:54 +00:00
dependabot[bot]
291682e1c0 chore: bump i18next from 23.2.11 to 23.4.1 (#6362)
Bumps [i18next](https://github.com/i18next/i18next) from 23.2.11 to 23.4.1.
- [Release notes](https://github.com/i18next/i18next/releases)
- [Changelog](https://github.com/i18next/i18next/blob/master/CHANGELOG.md)
- [Commits](https://github.com/i18next/i18next/compare/v23.2.11...v23.4.1)

---
updated-dependencies:
- dependency-name: i18next
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-02 10:17:13 -08:00
dependabot[bot]
97aeb77abe chore: bump eslint-plugin-import from 2.27.5 to 2.28.0 (#6363)
Bumps [eslint-plugin-import](https://github.com/import-js/eslint-plugin-import) from 2.27.5 to 2.28.0.
- [Release notes](https://github.com/import-js/eslint-plugin-import/releases)
- [Changelog](https://github.com/import-js/eslint-plugin-import/blob/main/CHANGELOG.md)
- [Commits](https://github.com/import-js/eslint-plugin-import/compare/v2.27.5...v2.28.0)

---
updated-dependencies:
- dependency-name: eslint-plugin-import
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-02 10:16:13 -08:00
dependabot[bot]
76fd2389e4 chore: bump proxy-agent from 6.2.1 to 6.3.0 (#6361)
Bumps [proxy-agent](https://github.com/TooTallNate/proxy-agents/tree/HEAD/packages/proxy-agent) from 6.2.1 to 6.3.0.
- [Release notes](https://github.com/TooTallNate/proxy-agents/releases)
- [Changelog](https://github.com/TooTallNate/proxy-agents/blob/main/packages/proxy-agent/CHANGELOG.md)
- [Commits](https://github.com/TooTallNate/proxy-agents/commits/proxy-agent@6.3.0/packages/proxy-agent)

---
updated-dependencies:
- dependency-name: proxy-agent
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-02 09:51:46 -08:00
Asher
3cb92edc76 Release v4.16.1 2023-07-31 11:21:45 -08:00
Olivier Benz
94ef3776ad Update Code to 1.80.2 (#6357) 2023-07-31 09:14:29 -08:00
Asher
a82f1ceaa5 Add missing FAQ table of contents entry 2023-07-28 17:46:36 -08:00
Asher
25254214fb Release v4.16.0 2023-07-28 17:32:57 -08:00
Asher
feb5e6770c Add entry for location of configuration
Closes #6324.
2023-07-28 17:32:48 -08:00
Asher
e37b35278d Account for disabled proxy in startup logs 2023-07-27 09:26:11 -08:00
Asher
2ffbcfdfcb Release v4.15.0 2023-07-21 15:30:03 -08:00
Ryan Brainard
74da5167a2 Add --disable-proxy option (#6349) 2023-07-21 15:23:21 -08:00
dependabot[bot]
daac46b3cf chore: bump i18next from 22.5.1 to 23.2.11 (#6326)
Bumps [i18next](https://github.com/i18next/i18next) from 22.5.1 to 23.2.11.
- [Release notes](https://github.com/i18next/i18next/releases)
- [Changelog](https://github.com/i18next/i18next/blob/master/CHANGELOG.md)
- [Commits](https://github.com/i18next/i18next/compare/v22.5.1...v23.2.11)

---
updated-dependencies:
- dependency-name: i18next
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-20 14:03:21 -05:00
dependabot[bot]
ea3c79ffd1 chore: bump @typescript-eslint/eslint-plugin from 5.59.1 to 5.62.0 (#6327)
Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 5.59.1 to 5.62.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v5.62.0/packages/eslint-plugin)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/eslint-plugin"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-20 13:31:37 -05:00
Alex Thillen
93e60f7b0e add trusted-origins cli argument (#6319) 2023-07-19 14:04:03 -08:00
dependabot[bot]
7926647058 chore: bump tough-cookie from 4.0.0 to 4.1.3 in /test (#6318)
Bumps [tough-cookie](https://github.com/salesforce/tough-cookie) from 4.0.0 to 4.1.3.
- [Release notes](https://github.com/salesforce/tough-cookie/releases)
- [Changelog](https://github.com/salesforce/tough-cookie/blob/master/CHANGELOG.md)
- [Commits](https://github.com/salesforce/tough-cookie/compare/v4.0.0...v4.1.3)

---
updated-dependencies:
- dependency-name: tough-cookie
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-19 14:02:19 -08:00
dependabot[bot]
d27cd43dce chore: bump @typescript-eslint/parser from 5.61.0 to 5.62.0 (#6328)
Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 5.61.0 to 5.62.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v5.62.0/packages/parser)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/parser"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-19 14:01:32 -08:00
dependabot[bot]
1befd37310 chore: bump word-wrap from 1.2.3 to 1.2.4 (#6342)
Bumps [word-wrap](https://github.com/jonschlinkert/word-wrap) from 1.2.3 to 1.2.4.
- [Release notes](https://github.com/jonschlinkert/word-wrap/releases)
- [Commits](https://github.com/jonschlinkert/word-wrap/compare/1.2.3...1.2.4)

---
updated-dependencies:
- dependency-name: word-wrap
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-19 14:01:14 -08:00
dependabot[bot]
2b3b5e7051 chore: bump word-wrap from 1.2.3 to 1.2.4 in /test (#6343)
Bumps [word-wrap](https://github.com/jonschlinkert/word-wrap) from 1.2.3 to 1.2.4.
- [Release notes](https://github.com/jonschlinkert/word-wrap/releases)
- [Commits](https://github.com/jonschlinkert/word-wrap/compare/1.2.3...1.2.4)

---
updated-dependencies:
- dependency-name: word-wrap
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-19 14:00:56 -08:00
Olivier Benz
788da5d495 Update Code to 1.80.1 (#6340)
* Update Code to 1.80.1 (#6340)
* Give build workflow more time to run
2023-07-19 14:00:31 -08:00
dependabot[bot]
070a5a3179 chore: bump semver from 6.3.0 to 6.3.1 in /test (#6320)
Bumps [semver](https://github.com/npm/node-semver) from 6.3.0 to 6.3.1.
- [Release notes](https://github.com/npm/node-semver/releases)
- [Changelog](https://github.com/npm/node-semver/blob/v6.3.1/CHANGELOG.md)
- [Commits](https://github.com/npm/node-semver/compare/v6.3.0...v6.3.1)

---
updated-dependencies:
- dependency-name: semver
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-14 13:52:42 -08:00
Michael Thomas
7cbb6a8b09 Add Svelte to guide (#6269)
Added a guide on proxying to a Svelte app since there wasn't one already. Used the vue and angular guides as a template and included a link to an issue post on sveltekits website which adds some context.
2023-07-13 16:17:25 -05:00
dependabot[bot]
7fb54073db chore: bump @typescript-eslint/parser from 5.59.11 to 5.61.0 (#6312)
Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 5.59.11 to 5.61.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v5.61.0/packages/parser)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/parser"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-13 16:04:32 -05:00
dependabot[bot]
e72e8bb6b5 chore: bump aquasecurity/trivy-action from 0.10.0 to 0.11.2 (#6303)
Bumps [aquasecurity/trivy-action](https://github.com/aquasecurity/trivy-action) from 0.10.0 to 0.11.2.
- [Release notes](https://github.com/aquasecurity/trivy-action/releases)
- [Commits](e5f43133f6...41f05d9ecf)

---
updated-dependencies:
- dependency-name: aquasecurity/trivy-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-13 16:04:18 -05:00
dependabot[bot]
411846528b chore: bump eslint from 8.39.0 to 8.44.0 (#6299)
Bumps [eslint](https://github.com/eslint/eslint) from 8.39.0 to 8.44.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v8.39.0...v8.44.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-13 16:03:55 -05:00
dependabot[bot]
7f024eda6e chore: bump typescript from 5.0.4 to 5.1.6 (#6297)
Bumps [typescript](https://github.com/Microsoft/TypeScript) from 5.0.4 to 5.1.6.
- [Release notes](https://github.com/Microsoft/TypeScript/releases)
- [Commits](https://github.com/Microsoft/TypeScript/commits)

---
updated-dependencies:
- dependency-name: typescript
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-06 13:59:24 -05:00
dependabot[bot]
2617623ed6 chore: bump tj-actions/changed-files from 36 to 37 (#6302)
Bumps [tj-actions/changed-files](https://github.com/tj-actions/changed-files) from 36 to 37.
- [Release notes](https://github.com/tj-actions/changed-files/releases)
- [Changelog](https://github.com/tj-actions/changed-files/blob/main/HISTORY.md)
- [Commits](https://github.com/tj-actions/changed-files/compare/v36...v37)

---
updated-dependencies:
- dependency-name: tj-actions/changed-files
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-06 13:59:01 -05:00
Asher
70aa1b7722 Release v4.14.1 2023-06-26 10:21:35 -08:00
dependabot[bot]
90a4147381 chore: bump semver from 7.3.8 to 7.5.2 (#6287)
Bumps [semver](https://github.com/npm/node-semver) from 7.3.8 to 7.5.2.
- [Release notes](https://github.com/npm/node-semver/releases)
- [Changelog](https://github.com/npm/node-semver/blob/main/CHANGELOG.md)
- [Commits](https://github.com/npm/node-semver/compare/v7.3.8...v7.5.2)

---
updated-dependencies:
- dependency-name: semver
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-26 12:12:45 -05:00
Asher
5c19962930 Set session socket into environment variable (#6282)
* Avoid spawning code-server with --reuse-window and --new-window

These flags mean the user explicitly wants to open in an existing
instance so if the socket is down it should error rather than try to
spawn code-server normally.

* Set session socket into environment variable

While I was at it I added a CLI flag to override the default.  I also
swapped the default to --user-data-dir.

The value is set on an environment variable so it can be used by the
extension host similar to VSCODE_IPC_HOOK_CLI.

* Add e2e test for opening files externally
2023-06-21 22:47:01 -08:00
Asher
56d10d82bf Enforce Node binary permissions
In some cases it may have extra write permissions.
2023-06-21 11:23:40 -08:00
Asher
cef2aa22dc Move session server log after HTTP server logs
The indented logs technically apply to the HTTP server so move the
session server log afterward to avoid making them look like they apply
to the session server.
2023-06-21 10:59:02 -08:00
Sean Lee
b5a9ef80e7 Use unique socket per user for managing editor sessions (#6278)
Also warn if editor session manager socket cannot be created rather than failing.
2023-06-21 13:39:25 -05:00
Asher
5d3c9edce4 Release v4.14.0 2023-06-16 13:38:21 -08:00
Asher
9955cd91a4 Update Code to 1.79.1 (#6264)
Mostly just the usual shifting or changing of the surrounding context
but I did refactor the getting started block we insert because it keeps
getting mangled on each update.  Instead of shifting things around the
columns I just prepend it to the right column.

Getting 404s on some vsda module but everything seems to work 
without it and I do not see it referenced in the package.json nor a
nywhere on npmjs.com so it seems to be optional.
2023-06-15 08:00:03 -08:00
dependabot[bot]
fdeaba9581 chore: bump proxy-agent from 5.0.0 to 6.2.1 (#6247)
* chore: bump proxy-agent from 5.0.0 to 6.2.1

Bumps [proxy-agent](https://github.com/TooTallNate/proxy-agents/tree/HEAD/packages/proxy-agent) from 5.0.0 to 6.2.1.
- [Release notes](https://github.com/TooTallNate/proxy-agents/releases)
- [Changelog](https://github.com/TooTallNate/proxy-agents/blob/main/packages/proxy-agent/CHANGELOG.md)
- [Commits](https://github.com/TooTallNate/proxy-agents/commits/proxy-agent@6.2.1/packages/proxy-agent)

---
updated-dependencies:
- dependency-name: proxy-agent
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

* Update proxy-agent usage

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Asher <ash@coder.com>
2023-06-14 14:27:15 -08:00
dependabot[bot]
40ff2e6049 chore: bump @typescript-eslint/parser from 5.57.1 to 5.59.11 (#6262)
Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 5.57.1 to 5.59.11.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v5.59.11/packages/parser)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/parser"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-14 14:08:39 -08:00
dependabot[bot]
73d42f7ea0 chore: bump i18next from 22.4.6 to 22.5.1 (#6261)
Bumps [i18next](https://github.com/i18next/i18next) from 22.4.6 to 22.5.1.
- [Release notes](https://github.com/i18next/i18next/releases)
- [Changelog](https://github.com/i18next/i18next/blob/master/CHANGELOG.md)
- [Commits](https://github.com/i18next/i18next/compare/v22.4.6...v22.5.1)

---
updated-dependencies:
- dependency-name: i18next
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-14 13:33:51 -08:00
Sean Lee
fb73742b2b Prefer matching editor sessions when opening files. (#6191)
Signed-off-by: Sean Lee <freshdried@gmail.com>
2023-06-14 13:32:07 -08:00
Asher
ccb0d3a34f Remove unused dependency split2
This was used with now-removed `--link`.
2023-06-13 10:38:13 -08:00
dependabot[bot]
4a121edd16 chore: bump tj-actions/changed-files from 35 to 36 (#6246)
Bumps [tj-actions/changed-files](https://github.com/tj-actions/changed-files) from 35 to 36.
- [Release notes](https://github.com/tj-actions/changed-files/releases)
- [Changelog](https://github.com/tj-actions/changed-files/blob/main/HISTORY.md)
- [Commits](https://github.com/tj-actions/changed-files/compare/v35...v36)

---
updated-dependencies:
- dependency-name: tj-actions/changed-files
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-13 18:28:24 +00:00
dependabot[bot]
f6db985712 chore: bump robinraju/release-downloader from 1.7 to 1.8 (#6245)
Bumps [robinraju/release-downloader](https://github.com/robinraju/release-downloader) from 1.7 to 1.8.
- [Release notes](https://github.com/robinraju/release-downloader/releases)
- [Commits](https://github.com/robinraju/release-downloader/compare/v1.7...v1.8)

---
updated-dependencies:
- dependency-name: robinraju/release-downloader
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-13 12:47:40 -05:00
dependabot[bot]
6d00fc7f46 chore: bump typescript from 4.6.4 to 5.0.4 (#6182)
Bumps [typescript](https://github.com/Microsoft/TypeScript) from 4.6.4 to 5.0.4.
- [Release notes](https://github.com/Microsoft/TypeScript/releases)
- [Commits](https://github.com/Microsoft/TypeScript/compare/v4.6.4...v5.0.4)

---
updated-dependencies:
- dependency-name: typescript
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-13 12:45:49 -05:00
Jonas
ee024f3f2d docs: mention Termux extension workarounds (#6227) 2023-05-31 15:00:58 -08:00
Simon Merschjohann
0703ef008c Allow {{host}} and {{port}} in domain proxy (#6225) 2023-05-31 16:31:30 -05:00
Asher
2109d1cf6a Add new unreleased section to changelog 2023-05-22 12:21:30 -08:00
Asher
74af05dfbe Release v4.13.0 2023-05-19 13:14:29 -08:00
dependabot[bot]
2798322b03 chore: bump vm2 from 3.9.17 to 3.9.19 (#6216)
Bumps [vm2](https://github.com/patriksimek/vm2) from 3.9.17 to 3.9.19.
- [Release notes](https://github.com/patriksimek/vm2/releases)
- [Changelog](https://github.com/patriksimek/vm2/blob/master/CHANGELOG.md)
- [Commits](https://github.com/patriksimek/vm2/compare/3.9.17...3.9.19)

---
updated-dependencies:
- dependency-name: vm2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-17 12:38:38 -08:00
Asher
b3b971480f Support X-Forwarded-Host with multiple hosts
Closes #6215.
2023-05-17 11:51:05 -08:00
Asher
6745a46034 Fix extension install test
This is not a valid ID (to install a specific version you use @) and,
quite strangely, Code is now returning an "extension not found" error if
the ID is invalid, breaking the test since we expect it to error about
the marketplace not existing.
2023-05-15 20:54:37 -08:00
Asher
43ef50b404 Update to 1.78.2 (#6201)
* Update to 1.78.1

No changes needed in the patches other than moving some lines around and
updating the CSP hash as usual.

The flake had to be updated as it was using Node 16.16 and 16.17 is
required at minimum now.  Also python seems to install python2 which is
marked as deprecated so explicitly install python3.

* Update to 1.78.2

Patches applied without any conflicts.

* Update commit environment variable

This was causing the commit not to be set.  It broke display languages
since that has a hard dependency on the commit for directory names.
Possibly broke other things.
2023-05-15 15:44:03 -08:00
dependabot[bot]
45c89856fd chore: bump eslint-config-prettier from 8.6.0 to 8.8.0 (#6179)
Bumps [eslint-config-prettier](https://github.com/prettier/eslint-config-prettier) from 8.6.0 to 8.8.0.
- [Release notes](https://github.com/prettier/eslint-config-prettier/releases)
- [Changelog](https://github.com/prettier/eslint-config-prettier/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prettier/eslint-config-prettier/compare/v8.6.0...v8.8.0)

---
updated-dependencies:
- dependency-name: eslint-config-prettier
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-08 12:43:37 -08:00
Jim van Abkoude
5f4ae75431 Add nix-on-droid as an Android installation method (#6196) 2023-05-08 12:42:43 -08:00
易良
521ff44aed fix: relative path in markdown (#6188)
Co-authored-by: jinjing.zzj <jinjing.zzj@alibaba-inc.com>
2023-05-08 12:38:52 -08:00
Asher
5708e6ce32 Format redacted arg lines
Pushing straight to main again.
2023-05-05 14:44:40 -08:00
Asher
3f7db15fde Redact sensitive args from handshake debug log 2023-05-04 10:54:41 -08:00
Asher
8c99f41b90 Fix parent process log level 2023-05-04 10:48:32 -08:00
Asher
e02d56dbfd Do not trace child process messages
All this does is add a stack trace which is not helpful here.
2023-05-04 10:48:31 -08:00
Asher
aac5efa046 Do not log < 500 on web sockets as errors
For example if someone spams a web socket without authentication we
should not log "forbidden".  Forbidden is normal/expected operation, not
an error.
2023-05-04 10:48:30 -08:00
dependabot[bot]
ff2764f7b1 chore: bump rotating-file-stream from 3.0.0 to 3.1.0 (#6183)
Bumps [rotating-file-stream](https://github.com/iccicci/rotating-file-stream) from 3.0.0 to 3.1.0.
- [Release notes](https://github.com/iccicci/rotating-file-stream/releases)
- [Changelog](https://github.com/iccicci/rotating-file-stream/blob/master/CHANGELOG.md)
- [Commits](https://github.com/iccicci/rotating-file-stream/compare/v3.0.0...v3.1.0)

---
updated-dependencies:
- dependency-name: rotating-file-stream
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-03 08:33:32 -08:00
dependabot[bot]
bda6b631ab chore: bump @typescript-eslint/eslint-plugin from 5.57.1 to 5.59.1 (#6180)
Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 5.57.1 to 5.59.1.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v5.59.1/packages/eslint-plugin)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/eslint-plugin"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-01 16:49:48 -08:00
dependabot[bot]
9bac70ea0d chore: bump eslint from 8.26.0 to 8.39.0 (#6181)
Bumps [eslint](https://github.com/eslint/eslint) from 8.26.0 to 8.39.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v8.26.0...v8.39.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-01 16:49:18 -08:00
dependabot[bot]
7722ef1437 chore: bump aquasecurity/trivy-action from 0.9.2 to 0.10.0 (#6178)
Bumps [aquasecurity/trivy-action](https://github.com/aquasecurity/trivy-action) from 0.9.2 to 0.10.0.
- [Release notes](https://github.com/aquasecurity/trivy-action/releases)
- [Commits](1f0aa582c8...e5f43133f6)

---
updated-dependencies:
- dependency-name: aquasecurity/trivy-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-01 11:17:06 -05:00
smalllady
951d8ac45e Fix proxying non-ASCII (#6154)
This only affects the path proxy since `req.originalUrl` is in escaped format.
2023-04-25 11:41:33 -08:00
Asher
2e17735795 Release v4.12.0 (#6157) 2023-04-21 14:04:07 -05:00
94 changed files with 3396 additions and 2324 deletions

View File

@@ -31,7 +31,7 @@ jobs:
uses: actions/checkout@v3
- name: Run prettier with actionsx/prettier
uses: actionsx/prettier@v2
uses: actionsx/prettier@v3
with:
args: --check --loglevel=warn .
@@ -45,16 +45,16 @@ jobs:
- name: Get changed files
id: changed-files
uses: tj-actions/changed-files@v35
uses: tj-actions/changed-files@v38
with:
files: |
docs/**
- name: Install Node.js v16
- name: Install Node.js v18
if: steps.changed-files.outputs.any_changed == 'true'
uses: actions/setup-node@v3
with:
node-version: "16"
node-version: "18"
cache: "yarn"
- name: Install doctoc
@@ -76,7 +76,7 @@ jobs:
- name: Get changed files
id: changed-files
uses: tj-actions/changed-files@v35
uses: tj-actions/changed-files@v38
with:
files: |
ci/helm-chart/**
@@ -107,7 +107,7 @@ jobs:
- name: Get changed files
id: changed-files
uses: tj-actions/changed-files@v35
uses: tj-actions/changed-files@v38
with:
files: |
**/*.ts
@@ -115,11 +115,11 @@ jobs:
files_ignore: |
lib/vscode/**
- name: Install Node.js v16
- name: Install Node.js v18
if: steps.changed-files.outputs.any_changed == 'true'
uses: actions/setup-node@v3
with:
node-version: "16"
node-version: "18"
- name: Fetch dependencies from cache
if: steps.changed-files.outputs.any_changed == 'true'
@@ -163,18 +163,18 @@ jobs:
- name: Get changed files
id: changed-files
uses: tj-actions/changed-files@v35
uses: tj-actions/changed-files@v38
with:
files: |
**/*.ts
files_ignore: |
lib/vscode/**
- name: Install Node.js v16
- name: Install Node.js v18
if: steps.changed-files.outputs.any_changed == 'true'
uses: actions/setup-node@v3
with:
node-version: "16"
node-version: "18"
- name: Fetch dependencies from cache
if: steps.changed-files.outputs.any_changed == 'true'
@@ -203,7 +203,7 @@ jobs:
build:
name: Build code-server
runs-on: ubuntu-20.04
timeout-minutes: 30
timeout-minutes: 60
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
steps:
@@ -212,6 +212,9 @@ jobs:
with:
submodules: true
- name: Install system dependencies
run: sudo apt update && sudo apt install -y libkrb5-dev
- name: Install quilt
uses: awalsh128/cache-apt-pkgs-action@latest
with:
@@ -221,10 +224,10 @@ jobs:
- name: Patch Code
run: quilt push -a
- name: Install Node.js v16
- name: Install Node.js v18
uses: actions/setup-node@v3
with:
node-version: "16"
node-version: "18"
- name: Fetch dependencies from cache
id: cache-node-modules
@@ -292,10 +295,13 @@ jobs:
- name: Checkout repo
uses: actions/checkout@v3
- name: Install Node.js v16
- name: Install system dependencies
run: sudo apt update && sudo apt install -y libkrb5-dev
- name: Install Node.js v18
uses: actions/setup-node@v3
with:
node-version: "16"
node-version: "18"
- name: Fetch dependencies from cache
id: cache-node-modules
@@ -348,10 +354,13 @@ jobs:
- name: Checkout repo
uses: actions/checkout@v3
- name: Install Node.js v16
- name: Install system dependencies
run: sudo apt update && sudo apt install -y libkrb5-dev
- name: Install Node.js v18
uses: actions/setup-node@v3
with:
node-version: "16"
node-version: "18"
- name: Fetch dependencies from cache
id: cache-node-modules

View File

@@ -29,14 +29,14 @@ jobs:
- name: Checkout code-server
uses: actions/checkout@v3
- name: Install Node.js v16
- name: Install Node.js v18
uses: actions/setup-node@v3
with:
node-version: "16"
node-version: "18"
cache: "yarn"
- name: Download npm package from release artifacts
uses: robinraju/release-downloader@v1.7
uses: robinraju/release-downloader@v1.8
with:
repository: "coder/code-server"
tag: ${{ github.event.inputs.version || github.ref_name }}
@@ -184,7 +184,7 @@ jobs:
echo "VERSION=${TAG#v}" >> $GITHUB_ENV
- name: Download release artifacts
uses: robinraju/release-downloader@v1.7
uses: robinraju/release-downloader@v1.8
with:
repository: "coder/code-server"
tag: v${{ env.VERSION }}

View File

@@ -27,7 +27,7 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 15
needs: npm-version
container: "centos:7"
container: "centos:8"
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
@@ -35,17 +35,17 @@ jobs:
- name: Checkout repo
uses: actions/checkout@v3
- name: Install Node.js v16
- name: Install Node.js v18
uses: actions/setup-node@v3
with:
node-version: "16"
node-version: "18.15.0"
- name: Install development tools
run: |
yum install -y epel-release centos-release-scl make
yum install -y devtoolset-9-{make,gcc,gcc-c++} jq rsync python3
# for keytar
yum install -y libsecret-devel
cd /etc/yum.repos.d/
sed -i 's/mirrorlist/#mirrorlist/g' /etc/yum.repos.d/CentOS-*
sed -i 's|#baseurl=http://mirror.centos.org|baseurl=http://vault.centos.org|g' /etc/yum.repos.d/CentOS-*
yum install -y gcc-c++ make jq rsync python3 libsecret-devel krb5-devel
- name: Install nfpm and envsubst
run: |
@@ -67,10 +67,8 @@ jobs:
- name: Decompress npm package
run: tar -xzf package.tar.gz
# NOTE: && here is deliberate - GitHub puts each line in its own `.sh`
# file when running inside a docker container.
- name: Build standalone release
run: source scl_source enable devtoolset-9 && npm run release:standalone
run: npm run release:standalone
- name: Install test dependencies
run: SKIP_SUBMODULE_DEPS=1 yarn --frozen-lockfile
@@ -102,54 +100,60 @@ jobs:
discussion_category_name: "📣 Announcements"
files: ./release-packages/*
# NOTE@oxy:
# We use Ubuntu 16.04 here, so that our build is more compatible
# with older libc versions. We used to (Q1'20) use CentOS 7 here,
# but it has a full update EOL of Q4'20 and a 'critical security'
# update EOL of 2024. We're dropping full support a few years before
# the final EOL, but I don't believe CentOS 7 has a large arm64 userbase.
# It is not feasible to cross-compile with CentOS.
# Cross-compile notes: To compile native dependencies for arm64,
# we install the aarch64/armv7l cross toolchain and then set it as the default
# compiler/linker/etc. with the AR/CC/CXX/LINK environment variables.
# qemu-user-static on ubuntu-16.04 currently doesn't run Node correctly,
# so we just build with "native"/x86_64 node, then download arm64/armv7l node
# and then put it in our release. We can't smoke test the cross build this way,
# but this means we don't need to maintain a self-hosted runner!
# NOTE@jsjoeio:
# We used to use 18.04 until GitHub browned it out on December 15, 2022
# See here: https://github.com/actions/runner-images/issues/6002
package-linux-cross:
name: Linux cross-compile builds
runs-on: ubuntu-20.04
runs-on: ubuntu-latest
timeout-minutes: 15
needs: npm-version
container: "debian:buster"
strategy:
matrix:
include:
- prefix: aarch64-linux-gnu
arch: arm64
npm_arch: arm64
apt_arch: arm64
- prefix: arm-linux-gnueabihf
arch: armv7l
npm_arch: armv7l
apt_arch: armhf
env:
AR: ${{ format('{0}-ar', matrix.prefix) }}
AS: ${{ format('{0}-as', matrix.prefix) }}
CC: ${{ format('{0}-gcc', matrix.prefix) }}
CPP: ${{ format('{0}-cpp', matrix.prefix) }}
CXX: ${{ format('{0}-g++', matrix.prefix) }}
LINK: ${{ format('{0}-g++', matrix.prefix) }}
NPM_CONFIG_ARCH: ${{ matrix.arch }}
NODE_VERSION: v16.13.0
FC: ${{ format('{0}-gfortran', matrix.prefix) }}
LD: ${{ format('{0}-ld', matrix.prefix) }}
STRIP: ${{ format('{0}-strip', matrix.prefix) }}
PKG_CONFIG_PATH: ${{ format('/usr/lib/{0}/pkgconfig', matrix.prefix) }}
TARGET_ARCH: ${{ matrix.apt_arch }}
npm_config_arch: ${{ matrix.npm_arch }}
NODE_VERSION: v18.15.0
# Not building from source results in an x86_64 argon2, as if
# npm_config_arch is being ignored.
npm_config_build_from_source: true
steps:
- name: Checkout repo
uses: actions/checkout@v3
- name: Install Node.js v16
- name: Install Node.js v18
uses: actions/setup-node@v3
with:
node-version: "16"
node-version: "18.15.0"
- name: Install cross-compiler and system dependencies
run: |
dpkg --add-architecture $TARGET_ARCH
apt-get update && apt-get install -y --no-install-recommends \
crossbuild-essential-$TARGET_ARCH \
libx11-dev:$TARGET_ARCH \
libx11-xcb-dev:$TARGET_ARCH \
libxkbfile-dev:$TARGET_ARCH \
libsecret-1-dev:$TARGET_ARCH \
libkrb5-dev:$TARGET_ARCH \
ca-certificates \
curl wget rsync gettext-base
- name: Install nfpm
run: |
@@ -157,11 +161,6 @@ jobs:
curl -sSfL https://github.com/goreleaser/nfpm/releases/download/v2.3.1/nfpm_2.3.1_`uname -s`_`uname -m`.tar.gz | tar -C ~/.local/bin -zxv nfpm
echo "$HOME/.local/bin" >> $GITHUB_PATH
- name: Install cross-compiler
run: sudo apt update && sudo apt install $PACKAGE
env:
PACKAGE: ${{ format('g++-{0}', matrix.prefix) }}
- name: Download npm package
uses: actions/download-artifact@v3
with:
@@ -170,15 +169,13 @@ jobs:
- name: Decompress npm package
run: tar -xzf package.tar.gz
# NOTE@jsjoeio - npm fails here
# so use yarn
- name: Build standalone release
run: yarn release:standalone
run: npm run release:standalone
- name: Replace node with cross-compile equivalent
run: |
wget https://nodejs.org/dist/${NODE_VERSION}/node-${NODE_VERSION}-linux-${NPM_CONFIG_ARCH}.tar.xz
tar -xf node-${NODE_VERSION}-linux-${NPM_CONFIG_ARCH}.tar.xz node-${NODE_VERSION}-linux-${NPM_CONFIG_ARCH}/bin/node --strip-components=2
wget https://nodejs.org/dist/${NODE_VERSION}/node-${NODE_VERSION}-linux-${npm_config_arch}.tar.xz
tar -xf node-${NODE_VERSION}-linux-${npm_config_arch}.tar.xz node-${NODE_VERSION}-linux-${npm_config_arch}/bin/node --strip-components=2
mv ./node ./release-standalone/lib/node
# NOTE@jsjoeio - we do this so we can strip out the v
@@ -191,7 +188,7 @@ jobs:
- name: Build packages with nfpm
env:
VERSION: ${{ env.VERSION }}
run: yarn package ${NPM_CONFIG_ARCH}
run: npm run package ${npm_config_arch}
- uses: softprops/action-gh-release@v1
with:
@@ -208,10 +205,10 @@ jobs:
- name: Checkout repo
uses: actions/checkout@v3
- name: Install Node.js v16
- name: Install Node.js v18
uses: actions/setup-node@v3
with:
node-version: "16"
node-version: "18.15.0"
- name: Install nfpm
run: |
@@ -305,7 +302,7 @@ jobs:
npm version --prefix release "$VERSION"
echo "Updating version in lib/vscode/product.json"
tmp=$(mktemp)
tmp=$(mktemp)
jq ".codeServerVersion = \"$VERSION\"" release/lib/vscode/product.json > "$tmp" && mv "$tmp" release/lib/vscode/product.json
# Ensure it has the same permissions as before
chmod 644 release/lib/vscode/product.json

View File

@@ -29,26 +29,17 @@ jobs:
with:
fetch-depth: 0
- name: Install Node.js v16
- name: Install Node.js v18
uses: actions/setup-node@v3
with:
node-version: "16"
node-version: "18"
- name: Fetch dependencies from cache
id: cache-yarn
uses: actions/cache@v3
with:
path: "**/node_modules"
key: yarn-build-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
yarn-build-
- name: Audit yarn for vulnerabilities
run: yarn audit
if: success()
- name: Install dependencies
if: steps.cache-yarn.outputs.cache-hit != 'true'
run: SKIP_SUBMODULE_DEPS=1 yarn --frozen-lockfile
- name: Audit for vulnerabilities
run: yarn _audit
- name: Audit npm for vulnerabilities
run: npm shrinkwrap && npm audit
if: success()
trivy-scan-repo:
@@ -64,7 +55,7 @@ jobs:
fetch-depth: 0
- name: Run Trivy vulnerability scanner in repo mode
uses: aquasecurity/trivy-action@1f0aa582c8c8f5f7639610d6d38baddfea4fdcee
uses: aquasecurity/trivy-action@41f05d9ecffa2ed3f1580af306000f734b733e54
with:
scan-type: "fs"
scan-ref: "."

View File

@@ -51,7 +51,7 @@ jobs:
uses: actions/checkout@v3
- name: Run Trivy vulnerability scanner in image mode
uses: aquasecurity/trivy-action@1f0aa582c8c8f5f7639610d6d38baddfea4fdcee
uses: aquasecurity/trivy-action@41f05d9ecffa2ed3f1580af306000f734b733e54
with:
image-ref: "docker.io/codercom/code-server:latest"
ignore-unfixed: true

View File

@@ -1 +1 @@
16
18

View File

@@ -20,6 +20,129 @@ Code v99.99.999
-->
## Unreleased
## [4.17.0](https://github.com/coder/code-server/releases/tag/v4.17.0) - 2023-09-22
Code v1.82.2
### Added
- Japanese locale.
- `CODE_SERVER_HOST` environment variable.
### Changed
- Update to Code 1.82.2. This includes an update to Node 18, which also means
that the minimum glibc is now 2.28. If you need to maintain a lower glibc then
you can take a version of Node 18 that is compiled with a lower glibc and use
that to build code-server (or at a minimum rebuild the native modules).
- Display paths to config files in full rather than abbreviated. If you have
trouble with the password not working please update and make sure the
displayed config paths are what you expect.
### Fixed
- Fix some dependency issues for the standalone arm64 and armv7l releases. If
you had issues with missing or failing modules please try these new builds.
## [4.16.1](https://github.com/coder/code-server/releases/tag/v4.16.1) - 2023-07-31
Code v1.80.2
### Changed
- Updated to Code 1.80.2.
## [4.16.0](https://github.com/coder/code-server/releases/tag/v4.16.0) - 2023-07-28
Code v1.80.1
### Added
- `--disable-proxy` flag. This disables the domain and path proxies but it does
not disable the ports panel in Code. That can be disabled by using
`remote.autoForwardPorts=false` in your settings.
## [4.15.0](https://github.com/coder/code-server/releases/tag/v4.15.0) - 2023-07-21
Code v1.80.1
### Changed
- Updated to Code 1.80.1.
### Added
- `--trusted-origin` flag for specifying origins that you trust but do not
control (for example a reverse proxy).
Code v1.79.2
## [4.14.1](https://github.com/coder/code-server/releases/tag/v4.14.1) - 2023-06-26
Code v1.79.2
### Security
- Remove extra write permissions on the Node binary bundled with the linux-amd64
tarball. If you extract the tar without a umask this could mean the Node
binary would be unexpectedly writable.
### Fixed
- Inability to launch multiple instances of code-server for different users.
### Added
- `--session-socket` CLI flag to configure the location of the session socket.
By default it will be placed in `<user data dir>/code-server-ipc.sock`.
## [4.14.0](https://github.com/coder/code-server/releases/tag/v4.14.0) - 2023-06-16
Code v1.79.2
### Added
- `--domain-proxy` now supports `{{port}}` and `{{host}}` template variables.
### Changed
- Updated to Code 1.79.2
- Files opened from an external terminal will now open in the most closely
related window rather than in the last opened window.
## [4.13.0](https://github.com/coder/code-server/releases/tag/v4.13.0) - 2023-05-19
Code v1.78.2
### Changed
- Updated to Code 1.78.2.
### Fixed
- Proxying files that contain non-ASCII characters.
- Origin check when X-Forwarded-Host contains comma-separated hosts.
## [4.12.0](https://github.com/coder/code-server/releases/tag/v4.12.0) - 2023-04-21
Code v1.77.3
### Changed
- Updated to Code 1.77.3
- Ports panel will use domain-based proxy (instead of the default path-based
proxy) when set via --proxy-domain.
- Apply --app-name to the PWA title.
### Added
- Thai translation for login page.
- Debug logs around the origin security check. If you are getting forbidden
errors on web sockets please run code-server with `--log debug` to see why the
requests are being blocked.
## [4.11.0](https://github.com/coder/code-server/releases/tag/v4.11.0) - 2023-03-16
Code v1.76.1

View File

@@ -29,7 +29,7 @@ This directory contains scripts used for the development of code-server.
- [./ci/dev/watch.ts](./dev/watch.ts) (`yarn watch`)
- Starts a process to build and launch code-server and restart on any code changes.
- Example usage in [./docs/CONTRIBUTING.md](../docs/CONTRIBUTING.md).
- [./ci/dev/gen_icons.sh](./ci/dev/gen_icons.sh) (`yarn icons`)
- [./ci/dev/gen_icons.sh](./dev/gen_icons.sh) (`yarn icons`)
- Generates the various icons from a single `.svg` favicon in
`src/browser/media/favicon.svg`.
- Requires [imagemagick](https://imagemagick.org/index.php)
@@ -75,7 +75,7 @@ You can disable minification by setting `MINIFY=`.
This directory contains the release docker container image.
- [./ci/steps/build-docker-buildx-push.sh](./ci/steps/docker-buildx-push.sh)
- [./ci/steps/build-docker-buildx-push.sh](./steps/docker-buildx-push.sh)
- Builds the release containers with tags `codercom/code-server-$ARCH:$VERSION` for amd64 and arm64 with `docker buildx` and pushes them.
- Assumes debian releases are ready in `./release-packages`.

View File

@@ -27,7 +27,7 @@ main() {
release_archive() {
local release_name="code-server-$VERSION-$OS-$ARCH"
if [[ $OS == "linux" ]]; then
tar -czf "release-packages/$release_name.tar.gz" --transform "s/^\.\/release-standalone/$release_name/" ./release-standalone
tar -czf "release-packages/$release_name.tar.gz" --owner=0 --group=0 --transform "s/^\.\/release-standalone/$release_name/" ./release-standalone
else
tar -czf "release-packages/$release_name.tar.gz" -s "/^release-standalone/$release_name/" release-standalone
fi

View File

@@ -56,7 +56,6 @@ bundle_code_server() {
}
EOF
) > "$RELEASE_PATH/package.json"
rsync yarn.lock "$RELEASE_PATH"
mv npm-shrinkwrap.json "$RELEASE_PATH"
rsync ci/build/npm-postinstall.sh "$RELEASE_PATH/postinstall.sh"
@@ -95,12 +94,10 @@ bundle_vscode() {
"$VSCODE_SRC_PATH/remote/package.json" \
"$VSCODE_SRC_PATH/package.json" > "$VSCODE_OUT_PATH/package.json"
rsync "$VSCODE_SRC_PATH/remote/yarn.lock" "$VSCODE_OUT_PATH/yarn.lock"
mv "$VSCODE_SRC_PATH/remote/npm-shrinkwrap.json" "$VSCODE_OUT_PATH/npm-shrinkwrap.json"
# Include global extension dependencies as well.
rsync "$VSCODE_SRC_PATH/extensions/package.json" "$VSCODE_OUT_PATH/extensions/package.json"
rsync "$VSCODE_SRC_PATH/extensions/yarn.lock" "$VSCODE_OUT_PATH/extensions/yarn.lock"
mv "$VSCODE_SRC_PATH/extensions/npm-shrinkwrap.json" "$VSCODE_OUT_PATH/extensions/npm-shrinkwrap.json"
rsync "$VSCODE_SRC_PATH/extensions/postinstall.mjs" "$VSCODE_OUT_PATH/extensions/postinstall.mjs"
}

View File

@@ -1,10 +1,6 @@
#!/usr/bin/env bash
set -euo pipefail
# This is due to an upstream issue with RHEL7/CentOS 7 comptability with node-argon2
# See: https://github.com/cdr/code-server/pull/3422#pullrequestreview-677765057
export npm_config_build_from_source=true
main() {
cd "$(dirname "${0}")/../.."
@@ -13,17 +9,19 @@ main() {
rsync "$RELEASE_PATH/" "$RELEASE_PATH-standalone"
RELEASE_PATH+=-standalone
# We cannot find the path to node from $PATH because yarn shims a script to ensure
# we use the same version it's using so we instead run a script with yarn that
# will print the path to node.
# We cannot get the path to Node from $PATH (for example via `which node`)
# because Yarn shims a script called `node` and we would end up just copying
# that script. Instead we run Node and have it print its actual path.
local node_path
node_path="$(yarn -s node <<< 'console.info(process.execPath)')"
node_path="$(node <<< 'console.info(process.execPath)')"
mkdir -p "$RELEASE_PATH/bin"
mkdir -p "$RELEASE_PATH/lib"
rsync ./ci/build/code-server.sh "$RELEASE_PATH/bin/code-server"
rsync "$node_path" "$RELEASE_PATH/lib/node"
chmod 755 "$RELEASE_PATH/lib/node"
pushd "$RELEASE_PATH"
npm install --unsafe-perm --omit=dev
popd

View File

@@ -15,7 +15,7 @@ copy-bin-script() {
local dest="lib/vscode-reh-web-linux-x64/bin/$script"
cp "lib/vscode/resources/server/bin/$script" "$dest"
sed -i.bak "s/@@VERSION@@/$(vscode_version)/g" "$dest"
sed -i.bak "s/@@COMMIT@@/$VSCODE_DISTRO_COMMIT/g" "$dest"
sed -i.bak "s/@@COMMIT@@/$BUILD_SOURCEVERSION/g" "$dest"
sed -i.bak "s/@@APPNAME@@/code-server/g" "$dest"
# Fix Node path on Darwin and Linux.
@@ -40,6 +40,16 @@ main() {
source ./ci/lib.sh
# Set the commit Code will embed into the product.json. We need to do this
# since Code tries to get the commit from the `.git` directory which will fail
# as it is a submodule.
#
# Also, we use code-server's commit rather than VS Code's otherwise it would
# not update when only our patch files change, and that will cause caching
# issues where the browser keeps using outdated code.
export BUILD_SOURCEVERSION
BUILD_SOURCEVERSION=$(git rev-parse HEAD)
pushd lib/vscode
if [[ ! ${VERSION-} ]]; then
@@ -48,14 +58,11 @@ main() {
exit 1
fi
# Set the commit Code will embed into the product.json. We need to do this
# since Code tries to get the commit from the `.git` directory which will fail
# as it is a submodule.
export VSCODE_DISTRO_COMMIT
VSCODE_DISTRO_COMMIT=$(git rev-parse HEAD)
# Add the date, our name, links, and enable telemetry (this just makes
# telemetry available; telemetry can still be disabled by flag or setting).
# Add the date, our name, links, enable telemetry (this just makes telemetry
# available; telemetry can still be disabled by flag or setting), and
# configure trusted extensions (since some, like github.copilot-chat, never
# ask to be trusted and this is the only way to get auth working).
#
# This needs to be done before building as Code will read this file and embed
# it into the client-side code.
git checkout product.json # Reset in case the script exited early.
@@ -89,6 +96,11 @@ main() {
"linkProtectionTrustedDomains": [
"https://open-vsx.org"
],
"trustedExtensionAuthAccess": [
"vscode.git", "vscode.github",
"github.vscode-pull-request-github",
"github.copilot", "github.copilot-chat"
],
"aiConfig": {
"ariaKey": "code-server"
}
@@ -109,6 +121,15 @@ EOF
popd
pushd lib/vscode-reh-web-linux-x64
# Make sure Code took the version we set in the environment variable. Not
# having a version will break display languages.
if ! jq -e .commit product.json; then
echo "'commit' is missing from product.json"
exit 1
fi
popd
# These provide a `code-server` command in the integrated terminal to open
# files in the current instance.
delete-bin-script remote-cli/code-server

View File

@@ -53,10 +53,6 @@ symlink_bin_script() {
OS="$(os)"
# This is due to an upstream issue with RHEL7/CentOS 7 comptability with node-argon2
# See: https://github.com/cdr/code-server/pull/3422#pullrequestreview-677765057
export npm_config_build_from_source=true
main() {
# Grabs the major version of node from $npm_config_user_agent which looks like
# yarn/1.21.1 npm/? node/v14.2.0 darwin x64
@@ -68,8 +64,8 @@ main() {
echo "USE AT YOUR OWN RISK!"
fi
if [ "$major_node_version" -ne "${FORCE_NODE_VERSION:-16}" ]; then
echo "ERROR: code-server currently requires node v16."
if [ "$major_node_version" -ne "${FORCE_NODE_VERSION:-18}" ]; then
echo "ERROR: code-server currently requires node v18."
if [ -n "$FORCE_NODE_VERSION" ]; then
echo "However, you have overrided the version check to use v$FORCE_NODE_VERSION."
fi
@@ -113,29 +109,40 @@ install_with_yarn_or_npm() {
# HACK: NPM's use of semver doesn't like resolving some peerDependencies that vscode (upstream) brings in the form of pre-releases.
# The legacy behavior doesn't complain about pre-releases being used, falling back to that for now.
# See https://github.com//pull/5071
npm install --unsafe-perm --legacy-peer-deps --omit=dev
if ! npm install --unsafe-perm --legacy-peer-deps --omit=dev; then
return 1
fi
;;
yarn*)
yarn --production --frozen-lockfile --no-default-rc
if ! yarn --production --frozen-lockfile --no-default-rc; then
return 1
fi
;;
*)
echo "Could not determine which package manager is being used to install code-server"
exit 1
;;
esac
return 0
}
vscode_install() {
echo 'Installing Code dependencies...'
cd lib/vscode
install_with_yarn_or_npm
if ! install_with_yarn_or_npm; then
return 1
fi
symlink_asar
symlink_bin_script remote-cli code code-server
symlink_bin_script helpers browser browser .sh
cd extensions
install_with_yarn_or_npm
if ! install_with_yarn_or_npm; then
return 1
fi
return 0
}
main "$@"

View File

@@ -15,9 +15,9 @@ type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 3.7.0
version: 3.13.0
# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using.
appVersion: 4.11.0
appVersion: 4.17.0

View File

@@ -20,6 +20,9 @@ spec:
labels:
app.kubernetes.io/name: {{ include "code-server.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- if .Values.podAnnotations }}
annotations: {{- toYaml .Values.podAnnotations | nindent 8 }}
{{- end }}
spec:
imagePullSecrets: {{- toYaml .Values.imagePullSecrets | nindent 8 }}
{{- if .Values.hostnameOverride }}

View File

@@ -1,3 +1,4 @@
{{- if not .Values.existingSecret }}
apiVersion: v1
kind: Secret
metadata:
@@ -11,8 +12,9 @@ metadata:
app.kubernetes.io/managed-by: {{ .Release.Service }}
type: Opaque
data:
{{ if .Values.password }}
{{- if .Values.password }}
password: "{{ .Values.password | b64enc }}"
{{ else }}
{{- else }}
password: "{{ randAlphaNum 24 | b64enc }}"
{{ end }}
{{- end }}
{{- end }}

View File

@@ -6,7 +6,7 @@ replicaCount: 1
image:
repository: codercom/code-server
tag: '4.11.0'
tag: '4.17.0'
pullPolicy: Always
# Specifies one or more secrets to be used when pulling images from a

View File

@@ -37,7 +37,7 @@ for [VS
Code](https://github.com/Microsoft/vscode/wiki/How-to-Contribute#prerequisites).
Here is what is needed:
- `node` v16.x
- `node` v18.x
- `git` v2.x or greater
- [`git-lfs`](https://git-lfs.github.com)
- [`yarn`](https://classic.yarnpkg.com/en/)
@@ -63,7 +63,7 @@ Here is what is needed:
If you're developing code-server on Linux, make sure you have installed or install the following dependencies:
```shell
sudo apt-get install build-essential g++ libx11-dev libxkbfile-dev libsecret-1-dev python-is-python3
sudo apt-get install build-essential g++ libx11-dev libxkbfile-dev libsecret-1-dev libkrb5-dev python-is-python3
```
These are required by Code. See [their Wiki](https://github.com/microsoft/vscode/wiki/How-to-Contribute#prerequisites) for more information.

View File

@@ -14,6 +14,7 @@
- [How do I install an extension manually?](#how-do-i-install-an-extension-manually)
- [How do I use my own extensions marketplace?](#how-do-i-use-my-own-extensions-marketplace)
- [Where are extensions stored?](#where-are-extensions-stored)
- [Where is VS Code configuration stored?](#where-is-vs-code-configuration-stored)
- [How can I reuse my VS Code configuration?](#how-can-i-reuse-my-vs-code-configuration)
- [How does code-server decide what workspace or folder to open?](#how-does-code-server-decide-what-workspace-or-folder-to-open)
- [How do I access my Documents/Downloads/Desktop folders in code-server on macOS?](#how-do-i-access-my-documentsdownloadsdesktop-folders-in-code-server-on-macos)
@@ -34,6 +35,7 @@
- [Are there community projects involving code-server?](#are-there-community-projects-involving-code-server)
- [How do I change the port?](#how-do-i-change-the-port)
- [How do I hide the coder/coder promotion in Help: Getting Started?](#how-do-i-hide-the-codercoder-promotion-in-help-getting-started)
- [How do I disable the proxy?](#how-do-i-disable-the-proxy)
- [How do I disable file download?](#how-do-i-disable-file-download)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
@@ -191,10 +193,20 @@ docs](https://github.com/VSCodium/vscodium/blob/master/DOCS.md#extensions--marke
## Where are extensions stored?
Extensions are store, by default, to `~/.local/share/code-server/extensions`.
Extensions are stored in `~/.local/share/code-server/extensions` by default.
If you set the `XDG_DATA_HOME` environment variable, the data directory will be
`$XDG_DATA_HOME/code-server/extensions`. In general, we try to follow the XDG directory spec.
On Linux and macOS if you set the `XDG_DATA_HOME` environment variable, the
extensions directory will be `$XDG_DATA_HOME/code-server/extensions`. In
general, we try to follow the XDG directory spec.
## Where is VS Code configuration stored?
VS Code configuration such as settings and keybindings are stored in
`~/.local/share/code-server` by default.
On Linux and macOS if you set the `XDG_DATA_HOME` environment variable, the data
directory will be `$XDG_DATA_HOME/code-server`. In general, we try to follow the
XDG directory spec.
## How can I reuse my VS Code configuration?
@@ -453,6 +465,19 @@ You can pass the flag `--disable-getting-started-override` to `code-server` or
you can set the environment variable `CS_DISABLE_GETTING_STARTED_OVERRIDE=1` or
`CS_DISABLE_GETTING_STARTED_OVERRIDE=true`.
## How do I disable the proxy?
You can pass the flag `--disable-proxy` to `code-server` or
you can set the environment variable `CS_DISABLE_PROXY=1` or
`CS_DISABLE_PROXY=true`.
Note, this option currently only disables the proxy routes to forwarded ports, including
the domain and path proxy routes over HTTP and WebSocket; however, it does not
disable the automatic port forwarding in the VS Code workbench itself. In other words,
user will still see the Ports tab and notifications, but will not be able to actually
use access the ports. It is recommended to set `remote.autoForwardPorts` to `false`
when using the option.
## How do I disable file download?
You can pass the flag `--disable-file-downloads` to `code-server`

View File

@@ -5,7 +5,8 @@
Run [VS Code](https://github.com/Microsoft/vscode) on any machine anywhere and
access it in the browser.
![Screenshot](./assets/screenshot.png)
![Screenshot](./assets/screenshot-1.png)
![Screenshot](./assets/screenshot-2.png)
## Highlights

View File

@@ -11,13 +11,21 @@ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash
```
6. Exit the terminal using `exit` and then reopen the terminal
7. Install and use Node.js 16:
7. Install and use Node.js 18:
```shell
nvm install 16
nvm use 16
nvm install 18
nvm use 18
```
8. Install code-server globally on device with: `npm install --global code-server --unsafe-perm`
9. Run code-server with `code-server`
10. Access on localhost:8080 in your browser
# Running code-server using Nix-on-Droid
1. Install Nix-on-Droid from [F-Droid](https://f-droid.org/packages/com.termux.nix/)
2. Start app
3. Spawn a shell with code-server by running `nix-shell -p code-server`
4. Run code-server with `code-server`
5. Access on localhost:8080 in your browser

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 267 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 357 KiB

View File

@@ -19,6 +19,7 @@
- [Proxying to create a React app](#proxying-to-create-a-react-app)
- [Proxying to a Vue app](#proxying-to-a-vue-app)
- [Proxying to an Angular app](#proxying-to-an-angular-app)
- [Proxying to a Svelte app](#proxying-to-a-svelte-app)
- [SSH into code-server on VS Code](#ssh-into-code-server-on-vs-code)
- [Option 1: cloudflared tunnel](#option-1-cloudflared-tunnel)
- [Option 2: ngrok tunnel](#option-2-ngrok-tunnel)
@@ -138,9 +139,9 @@ sudo apt install caddy
1. Replace `/etc/caddy/Caddyfile` using `sudo` so that the file looks like this:
```text
mydomain.com
reverse_proxy 127.0.0.1:8080
mydomain.com {
reverse_proxy 127.0.0.1:8080
}
```
If you want to serve code-server from a sub-path, you can do so as follows:
@@ -414,6 +415,27 @@ In order to use code-server's built-in proxy with Angular, you need to make the
For additional context, see [this GitHub Discussion](https://github.com/coder/code-server/discussions/5439#discussioncomment-3371983).
### Proxying to a Svelte app
In order to use code-server's built-in proxy with Svelte, you need to make the following changes in your app:
1. Add `svelte.config.js` if you don't already have one
2. Update the values to match this (you can use any free port):
```js
const config = {
kit: {
paths: {
base: "/absproxy/5173",
},
},
}
```
3. Access app at `<code-server-root>/absproxy/5173/` e.g. `http://localhost:8080/absproxy/5173/
For additional context, see [this Github Issue](https://github.com/sveltejs/kit/issues/2958)
## SSH into code-server on VS Code
[![SSH](https://img.shields.io/badge/SSH-363636?style=for-the-badge&logo=GNU+Bash&logoColor=ffffff)](https://ohmyz.sh/) [![Terminal](https://img.shields.io/badge/Terminal-2E2E2E?style=for-the-badge&logo=Windows+Terminal&logoColor=ffffff)](https://img.shields.io/badge/Terminal-2E2E2E?style=for-the-badge&logo=Windows+Terminal&logoColor=ffffff) [![Visual Studio Code](https://img.shields.io/badge/Visual_Studio_Code-007ACC?style=for-the-badge&logo=Visual+Studio+Code&logoColor=ffffff)](vscode:extension/ms-vscode-remote.remote-ssh)

View File

@@ -103,10 +103,9 @@ _exact_ same commands presented in the rest of this document.
We recommend installing with `npm` when:
1. You aren't using a machine with `amd64` or `arm64`.
1. You are installing code-server on Windows
1. You're on Linux with `glibc` < v2.17, `glibcxx` < v3.4.18 on `amd64`, `glibc`
< v2.23, or `glibcxx` < v3.4.21 on `arm64`.
1. You're running Alpine Linux or are using a non-glibc libc. See
2. You are installing code-server on Windows.
3. You're on Linux with `glibc` < v2.28 or `glibcxx` < v3.4.21.
4. You're running Alpine Linux or are using a non-glibc libc. See
[#1430](https://github.com/coder/code-server/issues/1430#issuecomment-629883198)
for more information.
@@ -123,8 +122,8 @@ node binary and node modules.
We create the standalone releases using the [npm package](#npm), and we
then create the remaining releases using the standalone version.
The only requirement to use the standalone release is `glibc` >= 2.17 and
`glibcxx` >= v3.4.18 on Linux (for macOS, there is no minimum system
The only requirement to use the standalone release is `glibc` >= 2.28 and
`glibcxx` >= v3.4.21 on Linux (for macOS, there is no minimum system
requirement).
To use a standalone release:
@@ -280,6 +279,7 @@ brew services start code-server
# outside the container.
mkdir -p ~/.config
docker run -it --name code-server -p 127.0.0.1:8080:8080 \
-v "$HOME/.local:/home/coder/.local" \
-v "$HOME/.config:/home/coder/.config" \
-v "$PWD:/home/coder/project" \
-u "$(id -u):$(id -g)" \

View File

@@ -30,7 +30,7 @@ includes installing instructions based on your operating system.
## Node.js version
We use the same major version of Node.js shipped with Code's remote, which is
currently `16.x`. VS Code also [lists Node.js
currently `18.x`. VS Code also [lists Node.js
requirements](https://github.com/microsoft/vscode/wiki/How-to-Contribute#prerequisites).
Using other versions of Node.js [may lead to unexpected
@@ -79,7 +79,7 @@ Proceed to [installing](#installing)
## FreeBSD
```sh
pkg install -y git python npm-node16 pkgconf
pkg install -y git python npm-node18 pkgconf
pkg install -y libinotify
```

View File

@@ -8,6 +8,7 @@
- [Upgrade](#upgrade)
- [Known Issues](#known-issues)
- [Git won't work in `/sdcard`](#git-wont-work-in-sdcard)
- [Many extensions including language packs fail to install](#many-extensions-including-language-packs-fail-to-install)
- [Extra](#extra)
- [Create a new user](#create-a-new-user)
- [Install Go](#install-go)
@@ -55,7 +56,7 @@ npm config set python python3
node -v
```
you will get node version `v16.15.0`
you will get Node version `v18`
5. Now install code-server following our guide on [installing with npm](./npm.md)
@@ -87,6 +88,37 @@ Potential Workaround :
1. Create a soft-link from the debian-fs to your folder in `/sdcard`
2. Use git from termux (preferred)
### Many extensions including language packs fail to install
Issue: Android is not seen as a Linux environment but as a separate, unsupported platform, so code-server only allows [Web Extensions](https://code.visualstudio.com/api/extension-guides/web-extensions), refusing to download extensions that run on the server.\
Fix: None\
Potential workarounds :
Either
- Manually download extensions as `.vsix` file and install them via `Extensions: Install from VSIX...` in the Command Palette.
- Use an override to pretend the platform is Linux:
Create a JS script that patches `process.platform`:
```js
// android-as-linux.js
Object.defineProperty(process, "platform", {
get() {
return "linux"
},
})
```
Then use Node's `--require` option to make sure it is loaded before `code-server` starts:
```sh
NODE_OPTIONS="--require /path/to/android-as-linux.js" code-server
```
⚠️ Note that Android and Linux are not 100% compatible, so use these workarounds at your own risk. Extensions that have native dependencies other than Node or that directly interact with the OS might cause issues.
## Extra
### Create a new user

30
flake.lock generated
View File

@@ -1,12 +1,15 @@
{
"nodes": {
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1659877975,
"narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=",
"lastModified": 1681202837,
"narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0",
"rev": "cfacdce06f30d2b68473a46042957675eebb3401",
"type": "github"
},
"original": {
@@ -17,11 +20,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1660639432,
"narHash": "sha256-2WDiboOCfB0LhvnDVMXOAr8ZLDfm3WdO54CkoDPwN1A=",
"lastModified": 1683594133,
"narHash": "sha256-iUhLhEAgOCnexSGDsYT2ouydis09uDoNzM7UC685XGE=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "6c6409e965a6c883677be7b9d87a95fab6c3472e",
"rev": "8d447c5626cfefb9b129d5b30103344377fe09bc",
"type": "github"
},
"original": {
@@ -34,6 +37,21 @@
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",

View File

@@ -7,16 +7,16 @@
flake-utils.lib.eachDefaultSystem
(system:
let pkgs = nixpkgs.legacyPackages.${system};
nodejs = pkgs.nodejs-16_x;
nodejs = pkgs.nodejs-18_x;
yarn' = pkgs.yarn.override { inherit nodejs; };
in {
devShells.default = pkgs.mkShell {
nativeBuildInputs = with pkgs; [
nodejs yarn' python pkg-config git rsync jq moreutils quilt bats
nodejs yarn' python3 pkg-config git rsync jq moreutils quilt bats openssl
];
buildInputs = with pkgs; (lib.optionals (!stdenv.isDarwin) [ libsecret ]
buildInputs = with pkgs; (lib.optionals (!stdenv.isDarwin) [ libsecret libkrb5 ]
++ (with xorg; [ libX11 libxkbfile ])
++ lib.optionals stdenv.isDarwin (with pkgs.darwin.apple_sdk.frameworks; [
++ lib.optionals stdenv.isDarwin (with pkgs.darwin.apple_sdk.frameworks; [
AppKit Cocoa CoreServices Security xcbuild
]));
};

View File

@@ -441,7 +441,7 @@ install_npm() {
return
fi
echoerr "Please install npm to install code-server!"
echoerr "You will need at least node v12 and a few C dependencies."
echoerr "You will need at least node v18 and a few C dependencies."
echoerr "See the docs https://coder.com/docs/code-server/latest/install#npm"
exit 1

View File

@@ -38,77 +38,62 @@
},
"main": "out/node/entry.js",
"devDependencies": {
"@schemastore/package": "^0.0.6",
"@types/compression": "^1.7.0",
"@types/cookie-parser": "^1.4.2",
"@types/express": "^4.17.8",
"@types/http-proxy": "^1.17.4",
"@types/js-yaml": "^4.0.0",
"@types/node": "^16.0.0",
"@types/pem": "^1.9.5",
"@schemastore/package": "^0.0.10",
"@types/compression": "^1.7.3",
"@types/cookie-parser": "^1.4.4",
"@types/express": "^4.17.17",
"@types/http-proxy": "1.17.7",
"@types/js-yaml": "^4.0.6",
"@types/node": "^18.0.0",
"@types/pem": "^1.14.1",
"@types/proxy-from-env": "^1.0.1",
"@types/safe-compare": "^1.1.0",
"@types/semver": "^7.1.0",
"@types/split2": "^3.2.0",
"@types/trusted-types": "^2.0.2",
"@types/ws": "^8.5.3",
"@typescript-eslint/eslint-plugin": "^5.41.0",
"@typescript-eslint/parser": "^5.41.0",
"audit-ci": "^6.0.0",
"doctoc": "2.2.1",
"eslint": "^8.26.0",
"eslint-config-prettier": "^8.5.0",
"eslint-import-resolver-typescript": "^3.5.2",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-prettier": "^4.2.1",
"prettier": "2.8.0",
"prettier-plugin-sh": "^0.12.8",
"ts-node": "^10.0.0",
"typescript": "^4.6.2"
},
"resolutions": {
"ansi-regex": "^5.0.1",
"normalize-package-data": "^5.0.0",
"doctoc/underscore": "^1.13.1",
"doctoc/**/trim": "^1.0.0",
"postcss": "^8.2.1",
"browserslist": "^4.16.5",
"safe-buffer": "^5.1.1",
"vfile-message": "^2.0.2",
"tar": "^6.1.9",
"path-parse": "^1.0.7",
"vm2": "^3.9.11",
"follow-redirects": "^1.14.8",
"node-fetch": "^2.6.7",
"nanoid": "^3.1.31",
"minimist": "npm:minimist-lite@2.2.1",
"glob-parent": "^6.0.1",
"@types/node": "^16.0.0",
"qs": "^6.7.3"
"@types/semver": "^7.5.2",
"@types/trusted-types": "^2.0.4",
"@types/ws": "^8.5.5",
"@typescript-eslint/eslint-plugin": "^6.7.2",
"@typescript-eslint/parser": "^6.7.2",
"audit-ci": "^6.6.1",
"doctoc": "^2.2.1",
"eslint": "^8.49.0",
"eslint-config-prettier": "^9.0.0",
"eslint-import-resolver-typescript": "^3.6.0",
"eslint-plugin-import": "^2.28.1",
"eslint-plugin-prettier": "^5.0.0",
"prettier": "^3.0.3",
"prettier-plugin-sh": "^0.13.1",
"ts-node": "^10.9.1",
"typescript": "^5.2.2"
},
"dependencies": {
"@coder/logger": "^3.0.0",
"argon2": "0.30.3",
"@coder/logger": "^3.0.1",
"argon2": "^0.31.1",
"compression": "^1.7.4",
"cookie-parser": "^1.4.5",
"env-paths": "^2.2.0",
"cookie-parser": "^1.4.6",
"env-paths": "^2.2.1",
"express": "5.0.0-alpha.8",
"http-proxy": "^1.18.0",
"http-proxy": "^1.18.1",
"httpolyglot": "^0.1.2",
"i18next": "^22.4.6",
"js-yaml": "^4.0.0",
"i18next": "^23.5.1",
"js-yaml": "^4.1.0",
"limiter": "^2.1.0",
"pem": "^1.14.2",
"proxy-agent": "^5.0.0",
"qs": "6.11.0",
"rotating-file-stream": "^3.0.0",
"safe-buffer": "^5.1.1",
"pem": "^1.14.8",
"proxy-agent": "^6.3.1",
"qs": "6.9.7",
"rotating-file-stream": "^3.1.1",
"safe-buffer": "^5.2.1",
"safe-compare": "^1.1.4",
"semver": "^7.1.3",
"split2": "^4.0.0",
"ws": "^8.0.0",
"semver": "^7.5.4",
"ws": "^8.14.2",
"xdg-basedir": "^4.0.0"
},
"resolutions": {
"@types/node": "^18.0.0",
"qs": "6.9.7"
},
"overrides": {
"qs": "6.9.7"
},
"bin": {
"code-server": "out/node/entry.js"
},
@@ -122,7 +107,7 @@
"remote-development"
],
"engines": {
"node": "16"
"node": "18"
},
"jest": {
"transform": {

View File

@@ -10,7 +10,7 @@ Index: code-server/lib/vscode/src/vs/base/common/network.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/base/common/network.ts
+++ code-server/lib/vscode/src/vs/base/common/network.ts
@@ -166,7 +166,9 @@ class RemoteAuthoritiesImpl {
@@ -168,7 +168,9 @@ class RemoteAuthoritiesImpl {
return URI.from({
scheme: platform.isWeb ? this._preferredWebSchema : Schemas.vscodeRemoteResource,
authority: `${host}:${port}`,
@@ -99,26 +99,19 @@ Index: code-server/lib/vscode/src/vs/platform/remote/browser/browserSocketFactor
===================================================================
--- code-server.orig/lib/vscode/src/vs/platform/remote/browser/browserSocketFactory.ts
+++ code-server/lib/vscode/src/vs/platform/remote/browser/browserSocketFactory.ts
@@ -274,6 +274,7 @@ export class BrowserSocketFactory implem
connect(host: string, port: number, path: string, query: string, debugLabel: string, callback: IConnectCallback): void {
const webSocketSchema = (/^https:/.test(window.location.href) ? 'wss' : 'ws');
+ path = (window.location.pathname + "/" + path).replace(/\/\/+/g, "/")
const socket = this._webSocketFactory.create(`${webSocketSchema}://${(/:/.test(host) && !/\[/.test(host)) ? `[${host}]` : host}:${port}${path}?${query}&skipWebSocketFrames=false`, debugLabel);
const errorListener = socket.onError((err) => callback(err, undefined));
socket.onOpen(() => {
@@ -282,6 +283,3 @@ export class BrowserSocketFactory implem
});
}
}
-
-
-
@@ -280,6 +280,7 @@ export class BrowserSocketFactory implem
connect({ host, port }: WebSocketRemoteConnection, path: string, query: string, debugLabel: string): Promise<ISocket> {
return new Promise<ISocket>((resolve, reject) => {
const webSocketSchema = (/^https:/.test(window.location.href) ? 'wss' : 'ws');
+ path = (window.location.pathname + "/" + path).replace(/\/\/+/g, "/")
const socket = this._webSocketFactory.create(`${webSocketSchema}://${(/:/.test(host) && !/\[/.test(host)) ? `[${host}]` : host}:${port}${path}?${query}&skipWebSocketFrames=false`, debugLabel);
const errorListener = socket.onError(reject);
socket.onOpen(() => {
Index: code-server/lib/vscode/src/vs/server/node/webClientServer.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/server/node/webClientServer.ts
+++ code-server/lib/vscode/src/vs/server/node/webClientServer.ts
@@ -267,12 +267,11 @@ export class WebClientServer {
@@ -269,16 +269,15 @@ export class WebClientServer {
return void res.end();
}
@@ -127,33 +120,33 @@ Index: code-server/lib/vscode/src/vs/server/node/webClientServer.ts
- return Array.isArray(val) ? val[0] : val;
- };
-
- const remoteAuthority = getFirstHeader('x-original-host') || getFirstHeader('x-forwarded-host') || req.headers.host;
const useTestResolver = (!this._environmentService.isBuilt && this._environmentService.args['use-test-resolver']);
+ // For now we are getting the remote authority from the client to avoid
+ // needing specific configuration for reverse proxies to work. Set this to
+ // something invalid to make sure we catch code that is using this value
+ // from the backend when it should not.
+ const remoteAuthority = 'remote';
const remoteAuthority = (
useTestResolver
? 'test+test'
- : (getFirstHeader('x-original-host') || getFirstHeader('x-forwarded-host') || req.headers.host)
+ : 'remote'
);
if (!remoteAuthority) {
return serveError(req, res, 400, `Bad request.`);
}
@@ -298,6 +297,8 @@ export class WebClientServer {
@@ -305,8 +304,12 @@ export class WebClientServer {
scopes: [['user:email'], ['repo']]
} : undefined;
+ const base = relativeRoot(getOriginalUrl(req))
+ const vscodeBase = relativePath(getOriginalUrl(req))
const workbenchWebConfiguration = {
remoteAuthority,
@@ -309,6 +310,7 @@ export class WebClientServer {
workspaceUri: resolveWorkspaceURI(this._environmentService.args['default-workspace']),
productConfiguration: <Partial<IProductConfiguration>>{
codeServerVersion: this._productService.codeServerVersion,
+ rootEndpoint: base,
embedderIdentifier: 'server-distro',
extensionsGallery: this._webExtensionResourceUrlTemplate ? {
...this._productService.extensionsGallery,
@@ -326,8 +328,10 @@ export class WebClientServer {
+
const productConfiguration = <Partial<IProductConfiguration>>{
codeServerVersion: this._productService.codeServerVersion,
+ rootEndpoint: base,
embedderIdentifier: 'server-distro',
extensionsGallery: this._webExtensionResourceUrlTemplate ? {
...this._productService.extensionsGallery,
@@ -341,8 +344,10 @@ export class WebClientServer {
const values: { [key: string]: string } = {
WORKBENCH_WEB_CONFIGURATION: asJSON(workbenchWebConfiguration),
WORKBENCH_AUTH_SESSION: authSessionInfo ? asJSON(authSessionInfo) : '',
@@ -165,17 +158,17 @@ Index: code-server/lib/vscode/src/vs/server/node/webClientServer.ts
+ VS_BASE: vscodeBase,
};
@@ -344,7 +348,7 @@ export class WebClientServer {
if (useTestResolver) {
@@ -367,7 +372,7 @@ export class WebClientServer {
'default-src \'self\';',
'img-src \'self\' https: data: blob:;',
'media-src \'self\';',
- `script-src 'self' 'unsafe-eval' ${this._getScriptCspHashes(data).join(' ')} 'sha256-fh3TwPMflhsEIpR8g1OYTIMVWhXTLcjQ9kh2tIpmv54=' http://${remoteAuthority};`, // the sha is the same as in src/vs/workbench/services/extensions/worker/webWorkerExtensionHostIframe.html
+ `script-src 'self' 'unsafe-eval' ${this._getScriptCspHashes(data).join(' ')} 'sha256-fh3TwPMflhsEIpR8g1OYTIMVWhXTLcjQ9kh2tIpmv54=';`, // the sha is the same as in src/vs/workbench/services/extensions/worker/webWorkerExtensionHostIframe.html
- `script-src 'self' 'unsafe-eval' ${this._getScriptCspHashes(data).join(' ')} 'sha256-fh3TwPMflhsEIpR8g1OYTIMVWhXTLcjQ9kh2tIpmv54=' ${useTestResolver ? '' : `http://${remoteAuthority}`};`, // the sha is the same as in src/vs/workbench/services/extensions/worker/webWorkerExtensionHostIframe.html
+ `script-src 'self' 'unsafe-eval' ${this._getScriptCspHashes(data).join(' ')} 'sha256-fh3TwPMflhsEIpR8g1OYTIMVWhXTLcjQ9kh2tIpmv54=' ${useTestResolver ? '' : ''};`, // the sha is the same as in src/vs/workbench/services/extensions/worker/webWorkerExtensionHostIframe.html
'child-src \'self\';',
`frame-src 'self' https://*.vscode-cdn.net data:;`,
'worker-src \'self\' data: blob:;',
@@ -417,3 +421,70 @@ export class WebClientServer {
@@ -440,3 +445,70 @@ export class WebClientServer {
return void res.end(data);
}
}
@@ -262,23 +255,45 @@ 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
@@ -489,6 +489,7 @@ function doCreateUri(path: string, query
});
@@ -304,7 +304,8 @@ class LocalStorageURLCallbackProvider ex
this.startListening();
}
- return URI.parse(window.location.href).with({ path: this._callbackRoute, query: queryParams.join('&') });
+ const path = (window.location.pathname + "/" + this._callbackRoute).replace(/\/\/+/g, "/");
+ return URI.parse(window.location.href).with({ path: path, query: queryParams.join('&') });
}
+ path = (window.location.pathname + "/" + path).replace(/\/\/+/g, "/")
return URI.parse(window.location.href).with({ path, query });
private startListening(): void {
@@ -550,17 +551,6 @@ class WorkspaceProvider implements IWork
}
}
@@ -500,7 +501,7 @@ function doCreateUri(path: string, query
-function readCookie(name: string): string | undefined {
- const cookies = document.cookie.split('; ');
- for (const cookie of cookies) {
- if (cookie.startsWith(name + '=')) {
- return cookie.substring(name.length + 1);
- }
- }
-
- return undefined;
-}
-
(function () {
// Find config by checking for DOM
@@ -569,8 +559,8 @@ function readCookie(name: string): strin
if (!configElement || !configElementAttribute) {
throw new Error('Missing web configuration element');
}
- const config: IWorkbenchConstructionOptions & { folderUri?: UriComponents; workspaceUri?: UriComponents; callbackRoute: string } = JSON.parse(configElementAttribute);
- const secretStorageKeyPath = readCookie('vscode-secret-key-path');
+ const config: IWorkbenchConstructionOptions & { folderUri?: UriComponents; workspaceUri?: UriComponents; callbackRoute: string } = { ...JSON.parse(configElementAttribute), remoteAuthority: location.host }
+ const secretStorageKeyPath = (window.location.pathname + "/mint-key").replace(/\/\/+/g, "/");
const secretStorageCrypto = secretStorageKeyPath && ServerKeyedAESCrypto.supported()
? new ServerKeyedAESCrypto(secretStorageKeyPath) : new TransparentCrypto();
// Create workbench
create(document.body, {
Index: code-server/lib/vscode/src/vs/platform/extensionResourceLoader/common/extensionResourceLoader.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/platform/extensionResourceLoader/common/extensionResourceLoader.ts
@@ -289,12 +304,12 @@ Index: code-server/lib/vscode/src/vs/platform/extensionResourceLoader/common/ext
import { getTelemetryLevel, supportsTelemetry } from 'vs/platform/telemetry/common/telemetryUtils';
-import { RemoteAuthorities } from 'vs/base/common/network';
import { getRemoteServerRootPath } from 'vs/platform/remote/common/remoteHosts';
import { TargetPlatform } from 'vs/platform/extensions/common/extensions';
const WEB_EXTENSION_RESOURCE_END_POINT = 'web-extension-resource';
@@ -75,7 +74,7 @@ export abstract class AbstractExtensionR
public getExtensionGalleryResourceURL(galleryExtension: { publisher: string; name: string; version: string }, path?: string): URI | undefined {
if (this._extensionGalleryResourceUrlTemplate) {
const uri = URI.parse(format2(this._extensionGalleryResourceUrlTemplate, { publisher: galleryExtension.publisher, name: galleryExtension.name, version: galleryExtension.version, path: 'extension' }));
@@ -102,7 +101,7 @@ export abstract class AbstractExtensionR
: version,
path: 'extension'
}));
- return this._isWebExtensionResourceEndPoint(uri) ? uri.with({ scheme: RemoteAuthorities.getPreferredWebSchema() }) : uri;
+ return this._isWebExtensionResourceEndPoint(uri) ? URI.joinPath(URI.parse(window.location.href), uri.path) : uri;
}

View File

@@ -17,7 +17,7 @@ Index: code-server/lib/vscode/src/vs/workbench/contrib/terminal/browser/remoteTe
===================================================================
--- code-server.orig/lib/vscode/src/vs/workbench/contrib/terminal/browser/remoteTerminalBackend.ts
+++ code-server/lib/vscode/src/vs/workbench/contrib/terminal/browser/remoteTerminalBackend.ts
@@ -100,10 +100,14 @@ class RemoteTerminalBackend extends Base
@@ -104,10 +104,14 @@ class RemoteTerminalBackend extends Base
}
const reqId = e.reqId;
const commandId = e.commandId;

View File

@@ -7,7 +7,7 @@ Index: code-server/lib/vscode/src/vs/workbench/contrib/extensions/browser/extens
===================================================================
--- code-server.orig/lib/vscode/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts
+++ code-server/lib/vscode/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts
@@ -243,6 +243,10 @@ export class Extension implements IExten
@@ -244,6 +244,10 @@ export class Extension implements IExten
if (this.type === ExtensionType.System && this.productService.quality === 'stable') {
return false;
}

View File

@@ -12,7 +12,7 @@ Index: code-server/lib/vscode/src/vs/workbench/browser/web.api.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/workbench/browser/web.api.ts
+++ code-server/lib/vscode/src/vs/workbench/browser/web.api.ts
@@ -260,6 +260,11 @@ export interface IWorkbenchConstructionO
@@ -288,6 +288,11 @@ export interface IWorkbenchConstructionO
*/
readonly userDataPath?: string
@@ -28,19 +28,19 @@ Index: code-server/lib/vscode/src/vs/workbench/services/environment/browser/envi
===================================================================
--- code-server.orig/lib/vscode/src/vs/workbench/services/environment/browser/environmentService.ts
+++ code-server/lib/vscode/src/vs/workbench/services/environment/browser/environmentService.ts
@@ -32,6 +32,11 @@ export interface IBrowserWorkbenchEnviro
* Options used to configure the workbench.
*/
@@ -34,6 +34,11 @@ export interface IBrowserWorkbenchEnviro
readonly options?: IWorkbenchConstructionOptions;
+
+ /**
/**
+ * Enable downloading files via menu actions.
+ */
+ readonly isEnabledFileDownloads?: boolean;
}
export class BrowserWorkbenchEnvironmentService implements IBrowserWorkbenchEnvironmentService {
@@ -101,6 +106,13 @@ export class BrowserWorkbenchEnvironment
+
+ /**
* Gets whether a resolver extension is expected for the environment.
*/
readonly expectsResolverExtension: boolean;
@@ -111,6 +116,13 @@ export class BrowserWorkbenchEnvironment
return this.options.userDataPath;
}
@@ -58,7 +58,7 @@ Index: code-server/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
+++ code-server/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
@@ -14,6 +14,7 @@ export const serverOptions: OptionDescri
@@ -16,6 +16,7 @@ export const serverOptions: OptionDescri
/* ----- code-server ----- */
'disable-update-check': { type: 'boolean' },
'auth': { type: 'string' },
@@ -66,7 +66,7 @@ Index: code-server/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
/* ----- server setup ----- */
@@ -94,6 +95,7 @@ export interface ServerParsedArgs {
@@ -97,6 +98,7 @@ export interface ServerParsedArgs {
/* ----- code-server ----- */
'disable-update-check'?: boolean;
'auth'?: string
@@ -78,7 +78,7 @@ Index: code-server/lib/vscode/src/vs/server/node/webClientServer.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/server/node/webClientServer.ts
+++ code-server/lib/vscode/src/vs/server/node/webClientServer.ts
@@ -304,6 +304,7 @@ export class WebClientServer {
@@ -332,6 +332,7 @@ export class WebClientServer {
remoteAuthority,
webviewEndpoint: vscodeBase + this._staticRoute + '/out/vs/workbench/contrib/webview/browser/pre',
userDataPath: this._environmentService.userDataPath,
@@ -90,29 +90,22 @@ Index: code-server/lib/vscode/src/vs/workbench/browser/contextkeys.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/workbench/browser/contextkeys.ts
+++ code-server/lib/vscode/src/vs/workbench/browser/contextkeys.ts
@@ -7,12 +7,11 @@ import { Event } from 'vs/base/common/ev
@@ -7,12 +7,12 @@ import { Event } from 'vs/base/common/ev
import { Disposable } from 'vs/base/common/lifecycle';
import { IContextKeyService, IContextKey, setConstant as setConstantContextKey } from 'vs/platform/contextkey/common/contextkey';
import { InputFocusedContext, IsMacContext, IsLinuxContext, IsWindowsContext, IsWebContext, IsMacNativeContext, IsDevelopmentContext, IsIOSContext, ProductQualityContext, IsMobileContext } from 'vs/platform/contextkey/common/contextkeys';
-import { SplitEditorsVertically, InEditorZenModeContext, ActiveEditorCanRevertContext, ActiveEditorGroupLockedContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext, AuxiliaryBarVisibleContext, SideBarVisibleContext, PanelAlignmentContext, PanelMaximizedContext, PanelVisibleContext, ActiveEditorContext, EditorsVisibleContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, EmbedderIdentifierContext, EditorTabsVisibleContext, IsCenteredLayoutContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext, ActiveEditorReadonlyContext, EditorAreaVisibleContext, ActiveEditorAvailableEditorIdsContext, DirtyWorkingCopiesContext, EmptyWorkspaceSupportContext, EnterMultiRootWorkspaceSupportContext, HasWebFileSystemAccess, IsFullscreenContext, OpenFolderWorkspaceSupportContext, RemoteNameContext, VirtualWorkspaceContext, WorkbenchStateContext, WorkspaceFolderCountContext, PanelPositionContext, TemporaryWorkspaceContext } from 'vs/workbench/common/contextkeys';
+import { SplitEditorsVertically, InEditorZenModeContext, ActiveEditorCanRevertContext, ActiveEditorGroupLockedContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext, AuxiliaryBarVisibleContext, SideBarVisibleContext, PanelAlignmentContext, PanelMaximizedContext, PanelVisibleContext, ActiveEditorContext, EditorsVisibleContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, EmbedderIdentifierContext, EditorTabsVisibleContext, IsCenteredLayoutContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext, ActiveEditorReadonlyContext, EditorAreaVisibleContext, ActiveEditorAvailableEditorIdsContext, DirtyWorkingCopiesContext, EmptyWorkspaceSupportContext, EnterMultiRootWorkspaceSupportContext, HasWebFileSystemAccess, IsFullscreenContext, OpenFolderWorkspaceSupportContext, RemoteNameContext, VirtualWorkspaceContext, WorkbenchStateContext, WorkspaceFolderCountContext, PanelPositionContext, TemporaryWorkspaceContext, IsEnabledFileDownloads } from 'vs/workbench/common/contextkeys';
import { TEXT_DIFF_EDITOR_ID, EditorInputCapabilities, SIDE_BY_SIDE_EDITOR_ID, DEFAULT_EDITOR_ASSOCIATION } from 'vs/workbench/common/editor';
-import { SplitEditorsVertically, InEditorZenModeContext, ActiveEditorCanRevertContext, ActiveEditorGroupLockedContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext, AuxiliaryBarVisibleContext, SideBarVisibleContext, PanelAlignmentContext, PanelMaximizedContext, PanelVisibleContext, ActiveEditorContext, EditorsVisibleContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, EmbedderIdentifierContext, EditorTabsVisibleContext, IsCenteredLayoutContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext, ActiveEditorReadonlyContext, EditorAreaVisibleContext, ActiveEditorAvailableEditorIdsContext, DirtyWorkingCopiesContext, EmptyWorkspaceSupportContext, EnterMultiRootWorkspaceSupportContext, HasWebFileSystemAccess, IsFullscreenContext, OpenFolderWorkspaceSupportContext, RemoteNameContext, VirtualWorkspaceContext, WorkbenchStateContext, WorkspaceFolderCountContext, PanelPositionContext, TemporaryWorkspaceContext, ActiveEditorCanToggleReadonlyContext, applyAvailableEditorIds } from 'vs/workbench/common/contextkeys';
+import { SplitEditorsVertically, InEditorZenModeContext, ActiveEditorCanRevertContext, ActiveEditorGroupLockedContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext, AuxiliaryBarVisibleContext, SideBarVisibleContext, PanelAlignmentContext, PanelMaximizedContext, PanelVisibleContext, ActiveEditorContext, EditorsVisibleContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, EmbedderIdentifierContext, EditorTabsVisibleContext, IsCenteredLayoutContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext, ActiveEditorReadonlyContext, EditorAreaVisibleContext, ActiveEditorAvailableEditorIdsContext, DirtyWorkingCopiesContext, EmptyWorkspaceSupportContext, EnterMultiRootWorkspaceSupportContext, HasWebFileSystemAccess, IsFullscreenContext, OpenFolderWorkspaceSupportContext, RemoteNameContext, VirtualWorkspaceContext, WorkbenchStateContext, WorkspaceFolderCountContext, PanelPositionContext, TemporaryWorkspaceContext, IsEnabledFileDownloads, ActiveEditorCanToggleReadonlyContext, applyAvailableEditorIds } from 'vs/workbench/common/contextkeys';
import { TEXT_DIFF_EDITOR_ID, EditorInputCapabilities, SIDE_BY_SIDE_EDITOR_ID, EditorResourceAccessor, SideBySideEditor } from 'vs/workbench/common/editor';
import { trackFocus, addDisposableListener, EventType } from 'vs/base/browser/dom';
import { preferredSideBySideGroupDirection, GroupDirection, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
-import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
+import { IBrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { WorkbenchState, IWorkspaceContextService, isTemporaryWorkspace } from 'vs/platform/workspace/common/workspace';
import { IWorkbenchLayoutService, Parts, positionToString } from 'vs/workbench/services/layout/browser/layoutService';
@@ -25,6 +24,7 @@ import { IPaneCompositePartService } fro
import { Schemas } from 'vs/base/common/network';
import { WebFileSystemAccess } from 'vs/platform/files/browser/webFileSystemAccess';
import { IProductService } from 'vs/platform/product/common/productService';
+import { IBrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService';
export class WorkbenchContextKeysHandler extends Disposable {
private inputFocusedContext: IContextKey<boolean>;
@@ -77,7 +77,7 @@ export class WorkbenchContextKeysHandler
@@ -79,7 +79,7 @@ export class WorkbenchContextKeysHandler
@IContextKeyService private readonly contextKeyService: IContextKeyService,
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@@ -121,7 +114,7 @@ Index: code-server/lib/vscode/src/vs/workbench/browser/contextkeys.ts
@IProductService private readonly productService: IProductService,
@IEditorService private readonly editorService: IEditorService,
@IEditorResolverService private readonly editorResolverService: IEditorResolverService,
@@ -205,6 +205,9 @@ export class WorkbenchContextKeysHandler
@@ -209,6 +209,9 @@ export class WorkbenchContextKeysHandler
this.auxiliaryBarVisibleContext = AuxiliaryBarVisibleContext.bindTo(this.contextKeyService);
this.auxiliaryBarVisibleContext.set(this.layoutService.isVisible(Parts.AUXILIARYBAR_PART));
@@ -144,7 +137,7 @@ Index: code-server/lib/vscode/src/vs/workbench/contrib/files/browser/fileActions
import { IsWebContext } from 'vs/platform/contextkey/common/contextkeys';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { ThemeIcon } from 'vs/base/common/themables';
@@ -485,13 +485,16 @@ MenuRegistry.appendMenuItem(MenuId.Explo
@@ -489,13 +489,16 @@ MenuRegistry.appendMenuItem(MenuId.Explo
id: DOWNLOAD_COMMAND_ID,
title: DOWNLOAD_LABEL
},
@@ -172,7 +165,7 @@ Index: code-server/lib/vscode/src/vs/workbench/common/contextkeys.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/workbench/common/contextkeys.ts
+++ code-server/lib/vscode/src/vs/workbench/common/contextkeys.ts
@@ -35,6 +35,8 @@ export const HasWebFileSystemAccess = ne
@@ -38,6 +38,8 @@ export const HasWebFileSystemAccess = ne
export const EmbedderIdentifierContext = new RawContextKey<string | undefined>('embedderIdentifier', undefined, localize('embedderIdentifier', 'The identifier of the embedder according to the product service, if one is defined'));

View File

@@ -21,15 +21,15 @@ Index: code-server/lib/vscode/src/vs/server/node/serverServices.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/server/node/serverServices.ts
+++ code-server/lib/vscode/src/vs/server/node/serverServices.ts
@@ -233,6 +233,9 @@ export async function setupServerService
@@ -231,6 +231,9 @@ export async function setupServerService
const channel = new ExtensionManagementChannel(extensionManagementService, (ctx: RemoteAgentConnectionContext) => getUriTransformer(ctx.remoteAuthority));
socketServer.registerChannel('extensions', channel);
+ const languagePackChannel = ProxyChannel.fromService<RemoteAgentConnectionContext>(accessor.get(ILanguagePackService));
+ socketServer.registerChannel('languagePacks', languagePackChannel);
+
const encryptionChannel = ProxyChannel.fromService<RemoteAgentConnectionContext>(accessor.get(IEncryptionMainService));
socketServer.registerChannel('encryption', encryptionChannel);
const credentialsChannel = ProxyChannel.fromService<RemoteAgentConnectionContext>(accessor.get(ICredentialsMainService));
socketServer.registerChannel('credentials', credentialsChannel);
Index: code-server/lib/vscode/src/vs/base/common/platform.ts
===================================================================
@@ -89,10 +89,10 @@ Index: code-server/lib/vscode/src/vs/code/browser/workbench/workbench.html
<!-- Workbench Icon/Manifest/CSS -->
<link rel="icon" href="{{BASE}}/_static/src/browser/media/favicon-dark-support.svg" />
<link rel="alternate icon" href="{{BASE}}/_static/src/browser/media/favicon.ico" type="image/x-icon" />
@@ -46,15 +49,26 @@
// Set up nls if the user is not using the default language (English)
const nlsConfig = {};
const locale = window.localStorage.getItem('vscode.nls.locale') || navigator.language;
@@ -48,15 +51,26 @@
// Normalize locale to lowercase because translationServiceUrl is case-sensitive.
// ref: https://github.com/microsoft/vscode/issues/187795
const locale = window.localStorage.getItem('vscode.nls.locale') || navigator.language.toLowerCase();
- if (!locale.startsWith('en')) {
- nlsConfig['vs/nls'] = {
- availableLanguages: {
@@ -203,36 +203,36 @@ Index: code-server/lib/vscode/src/vs/server/node/webClientServer.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/server/node/webClientServer.ts
+++ code-server/lib/vscode/src/vs/server/node/webClientServer.ts
@@ -26,6 +26,7 @@ import { URI } from 'vs/base/common/uri'
@@ -27,6 +27,7 @@ import { URI } from 'vs/base/common/uri'
import { streamToBuffer } from 'vs/base/common/buffer';
import { IProductConfiguration } from 'vs/base/common/product';
import { isString } from 'vs/base/common/types';
+import { getLocaleFromConfig, getNLSConfiguration } from 'vs/server/node/remoteLanguagePacks';
import { CharCode } from 'vs/base/common/charCode';
import { getRemoteServerRootPath } from 'vs/platform/remote/common/remoteHosts';
import { IExtensionManifest } from 'vs/platform/extensions/common/extensions';
@@ -344,6 +345,8 @@ export class WebClientServer {
callbackRoute: this._callbackRoute
};
@@ -299,6 +300,8 @@ export class WebClientServer {
const base = relativeRoot(getOriginalUrl(req))
const vscodeBase = relativePath(getOriginalUrl(req))
+ const locale = this._environmentService.args.locale || await getLocaleFromConfig(this._environmentService.argvResource.fsPath);
+ const nlsConfiguration = await getNLSConfiguration(locale, this._environmentService.userDataPath)
const workbenchWebConfiguration = {
remoteAuthority,
@@ -336,6 +339,7 @@ export class WebClientServer {
const nlsBaseUrl = this._productService.extensionsGallery?.nlsBaseUrl;
const values: { [key: string]: string } = {
WORKBENCH_WEB_CONFIGURATION: asJSON(workbenchWebConfiguration),
@@ -352,6 +355,7 @@ export class WebClientServer {
WORKBENCH_NLS_BASE_URL: vscodeBase + (nlsBaseUrl ? `${nlsBaseUrl}${!nlsBaseUrl.endsWith('/') ? '/' : ''}${this._productService.commit}/${this._productService.version}/` : ''),
BASE: base,
VS_BASE: vscodeBase,
+ NLS_CONFIGURATION: asJSON(nlsConfiguration),
};
if (useTestResolver) {
Index: code-server/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
+++ code-server/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
@@ -15,6 +15,7 @@ export const serverOptions: OptionDescri
@@ -17,6 +17,7 @@ export const serverOptions: OptionDescri
'disable-update-check': { type: 'boolean' },
'auth': { type: 'string' },
'disable-file-downloads': { type: 'boolean' },
@@ -240,7 +240,7 @@ Index: code-server/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
/* ----- server setup ----- */
@@ -96,6 +97,7 @@ export interface ServerParsedArgs {
@@ -99,6 +100,7 @@ export interface ServerParsedArgs {
'disable-update-check'?: boolean;
'auth'?: string
'disable-file-downloads'?: boolean;
@@ -261,9 +261,9 @@ Index: code-server/lib/vscode/src/vs/workbench/workbench.web.main.ts
import 'vs/workbench/services/path/browser/pathService';
import 'vs/workbench/services/themes/browser/browserHostColorSchemeService';
import 'vs/workbench/services/encryption/browser/encryptionService';
@@ -119,8 +119,9 @@ import 'vs/workbench/contrib/logs/browse
// Explorer
import 'vs/workbench/contrib/files/browser/files.web.contribution';
@@ -117,8 +117,9 @@ registerSingleton(ILanguagePackService,
// Logs
import 'vs/workbench/contrib/logs/browser/logs.contribution';
-// Localization
-import 'vs/workbench/contrib/localization/browser/localization.contribution';
@@ -339,7 +339,7 @@ Index: code-server/lib/vscode/src/vs/workbench/contrib/extensions/browser/extens
===================================================================
--- code-server.orig/lib/vscode/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts
+++ code-server/lib/vscode/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts
@@ -335,9 +335,6 @@ export abstract class AbstractInstallAct
@@ -321,9 +321,6 @@ export class InstallAction extends Exten
if (this.extension.isBuiltin) {
return;
}
@@ -349,7 +349,7 @@ Index: code-server/lib/vscode/src/vs/workbench/contrib/extensions/browser/extens
if (this.extension.state === ExtensionState.Uninstalled && await this.extensionsWorkbenchService.canInstall(this.extension)) {
this.enabled = this.options.installPreReleaseVersion ? this.extension.hasPreReleaseVersion : this.extension.hasReleaseVersion;
this.updateLabel();
@@ -715,7 +712,7 @@ export abstract class InstallInOtherServ
@@ -591,7 +588,7 @@ export abstract class InstallInOtherServ
}
if (isLanguagePackExtension(this.extension.local.manifest)) {
@@ -358,7 +358,7 @@ Index: code-server/lib/vscode/src/vs/workbench/contrib/extensions/browser/extens
}
// Prefers to run on UI
@@ -1803,17 +1800,6 @@ export class SetLanguageAction extends E
@@ -1683,17 +1680,6 @@ export class SetLanguageAction extends E
update(): void {
this.enabled = false;
this.class = SetLanguageAction.DisabledClass;
@@ -376,7 +376,7 @@ Index: code-server/lib/vscode/src/vs/workbench/contrib/extensions/browser/extens
}
override async run(): Promise<any> {
@@ -1830,7 +1816,6 @@ export class ClearLanguageAction extends
@@ -1710,7 +1696,6 @@ export class ClearLanguageAction extends
private static readonly DisabledClass = `${ClearLanguageAction.EnabledClass} disabled`;
constructor(
@@ -384,7 +384,7 @@ Index: code-server/lib/vscode/src/vs/workbench/contrib/extensions/browser/extens
@ILocaleService private readonly localeService: ILocaleService,
) {
super(ClearLanguageAction.ID, ClearLanguageAction.TITLE.value, ClearLanguageAction.DisabledClass, false);
@@ -1840,17 +1825,6 @@ export class ClearLanguageAction extends
@@ -1720,17 +1705,6 @@ export class ClearLanguageAction extends
update(): void {
this.enabled = false;
this.class = ClearLanguageAction.DisabledClass;

View File

@@ -10,16 +10,25 @@ Index: code-server/lib/vscode/src/vs/workbench/contrib/welcomeGettingStarted/bro
===================================================================
--- code-server.orig/lib/vscode/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts
+++ code-server/lib/vscode/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts
@@ -60,7 +60,7 @@ import { GettingStartedIndexList } from
@@ -10,7 +10,7 @@ import { IInstantiationService } from 'v
import { IEditorSerializer, IEditorOpenContext } from 'vs/workbench/common/editor';
import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle';
import { assertIsDefined } from 'vs/base/common/types';
-import { $, addDisposableListener, append, clearNode, Dimension, reset } from 'vs/base/browser/dom';
+import { $, addDisposableListener, append, clearNode, Dimension, reset, prepend } from 'vs/base/browser/dom';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IProductService } from 'vs/platform/product/common/productService';
import { hiddenEntriesConfigurationKey, IResolvedWalkthrough, IResolvedWalkthroughStep, IWalkthroughsService } from 'vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedService';
@@ -59,7 +59,7 @@ import { GettingStartedIndexList } from
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { KeyCode } from 'vs/base/common/keyCodes';
import { getTelemetryLevel } from 'vs/platform/telemetry/common/telemetryUtils';
-import { WorkbenchStateContext } from 'vs/workbench/common/contextkeys';
+import { IsEnabledCoderGettingStarted, WorkbenchStateContext } from 'vs/workbench/common/contextkeys';
import { OpenFolderViaWorkspaceAction } from 'vs/workbench/browser/actions/workspaceActions';
import { OpenFolderAction, OpenFileFolderAction, OpenFolderViaWorkspaceAction } from 'vs/workbench/browser/actions/workspaceActions';
import { OpenRecentAction } from 'vs/workbench/browser/actions/windowActions';
import { Toggle } from 'vs/base/browser/ui/toggle/toggle';
@@ -796,6 +796,72 @@ export class GettingStartedPage extends
@@ -783,6 +783,72 @@ export class GettingStartedPage extends
$('p.subtitle.description', {}, localize({ key: 'gettingStarted.editingEvolved', comment: ['Shown as subtitle on the Welcome page.'] }, "Editing evolved"))
);
@@ -92,62 +101,16 @@ Index: code-server/lib/vscode/src/vs/workbench/contrib/welcomeGettingStarted/bro
const leftColumn = $('.categories-column.categories-column-left', {},);
const rightColumn = $('.categories-column.categories-column-right', {},);
@@ -814,13 +880,23 @@ export class GettingStartedPage extends
const layoutLists = () => {
if (gettingStartedList.itemCount) {
this.container.classList.remove('noWalkthroughs');
- reset(leftColumn, startList.getDomElement(), recentList.getDomElement());
- reset(rightColumn, featuredExtensionList.getDomElement(), gettingStartedList.getDomElement());
+ if (this.contextService.contextMatchesRules(IsEnabledCoderGettingStarted)) {
+ reset(leftColumn, startList.getDomElement(), recentList.getDomElement(), featuredExtensionList.getDomElement(), gettingStartedList.getDomElement());
+ reset(rightColumn, gettingStartedCoder);
+ } else {
+ reset(leftColumn, startList.getDomElement(), recentList.getDomElement());
+ reset(rightColumn, featuredExtensionList.getDomElement(), gettingStartedList.getDomElement());
+ }
@@ -832,6 +898,9 @@ export class GettingStartedPage extends
recentList.setLimit(5);
reset(leftColumn, startList.getDomElement(), recentList.getDomElement());
}
else {
this.container.classList.add('noWalkthroughs');
- reset(leftColumn, startList.getDomElement(), recentList.getDomElement());
- reset(rightColumn, featuredExtensionList.getDomElement());
+ if (this.contextService.contextMatchesRules(IsEnabledCoderGettingStarted)) {
+ reset(leftColumn, startList.getDomElement(), recentList.getDomElement(), featuredExtensionList.getDomElement());
+ reset(rightColumn, gettingStartedCoder);
+ } else {
+ reset(leftColumn, startList.getDomElement(), recentList.getDomElement());
+ reset(rightColumn, featuredExtensionList.getDomElement());
+ }
}
setTimeout(() => this.categoriesPageScrollbar?.scanDomNode(), 50);
};
@@ -828,13 +904,23 @@ export class GettingStartedPage extends
const layoutFeaturedExtension = () => {
if (featuredExtensionList.itemCount) {
this.container.classList.remove('noExtensions');
- reset(leftColumn, startList.getDomElement(), recentList.getDomElement());
- reset(rightColumn, featuredExtensionList.getDomElement(), gettingStartedList.getDomElement());
+ if (this.contextService.contextMatchesRules(IsEnabledCoderGettingStarted)) {
+ reset(leftColumn, startList.getDomElement(), recentList.getDomElement(), featuredExtensionList.getDomElement(), gettingStartedList.getDomElement());
+ reset(rightColumn, gettingStartedCoder);
+ } else {
+ reset(leftColumn, startList.getDomElement(), recentList.getDomElement());
+ reset(rightColumn, featuredExtensionList.getDomElement(), gettingStartedList.getDomElement());
+ }
}
else {
this.container.classList.add('noExtensions');
- reset(leftColumn, startList.getDomElement(), recentList.getDomElement());
- reset(rightColumn, gettingStartedList.getDomElement());
+ if (this.contextService.contextMatchesRules(IsEnabledCoderGettingStarted)) {
+ reset(leftColumn, startList.getDomElement(), recentList.getDomElement(), gettingStartedList.getDomElement());
+ reset(rightColumn, gettingStartedCoder);
+ } else {
+ reset(leftColumn, startList.getDomElement(), recentList.getDomElement());
+ reset(rightColumn, gettingStartedList.getDomElement());
+ }
}
setTimeout(() => this.categoriesPageScrollbar?.scanDomNode(), 50);
+ if (this.contextService.contextMatchesRules(IsEnabledCoderGettingStarted)) {
+ prepend(rightColumn, gettingStartedCoder)
+ }
};
featuredExtensionList.onDidChange(layoutFeaturedExtension);
Index: code-server/lib/vscode/src/vs/workbench/contrib/welcomeGettingStarted/browser/media/gettingStarted.css
===================================================================
--- code-server.orig/lib/vscode/src/vs/workbench/contrib/welcomeGettingStarted/browser/media/gettingStarted.css
@@ -172,7 +135,7 @@ Index: code-server/lib/vscode/src/vs/workbench/browser/web.api.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/workbench/browser/web.api.ts
+++ code-server/lib/vscode/src/vs/workbench/browser/web.api.ts
@@ -265,6 +265,11 @@ export interface IWorkbenchConstructionO
@@ -293,6 +293,11 @@ export interface IWorkbenchConstructionO
*/
readonly isEnabledFileDownloads?: boolean
@@ -188,19 +151,19 @@ Index: code-server/lib/vscode/src/vs/workbench/services/environment/browser/envi
===================================================================
--- code-server.orig/lib/vscode/src/vs/workbench/services/environment/browser/environmentService.ts
+++ code-server/lib/vscode/src/vs/workbench/services/environment/browser/environmentService.ts
@@ -37,6 +37,11 @@ export interface IBrowserWorkbenchEnviro
* Enable downloading files via menu actions.
*/
@@ -39,6 +39,11 @@ export interface IBrowserWorkbenchEnviro
readonly isEnabledFileDownloads?: boolean;
+
+ /**
/**
+ * Enable Coder's custom getting started text.
+ */
+ readonly isEnabledCoderGettingStarted?: boolean;
}
export class BrowserWorkbenchEnvironmentService implements IBrowserWorkbenchEnvironmentService {
@@ -113,6 +118,13 @@ export class BrowserWorkbenchEnvironment
+
+ /**
* Gets whether a resolver extension is expected for the environment.
*/
readonly expectsResolverExtension: boolean;
@@ -123,6 +128,13 @@ export class BrowserWorkbenchEnvironment
return this.options.isEnabledFileDownloads;
}
@@ -218,7 +181,7 @@ Index: code-server/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
+++ code-server/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
@@ -16,6 +16,7 @@ export const serverOptions: OptionDescri
@@ -18,6 +18,7 @@ export const serverOptions: OptionDescri
'auth': { type: 'string' },
'disable-file-downloads': { type: 'boolean' },
'locale': { type: 'string' },
@@ -226,7 +189,7 @@ Index: code-server/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
/* ----- server setup ----- */
@@ -98,6 +99,7 @@ export interface ServerParsedArgs {
@@ -101,6 +102,7 @@ export interface ServerParsedArgs {
'auth'?: string
'disable-file-downloads'?: boolean;
'locale'?: string
@@ -238,7 +201,7 @@ Index: code-server/lib/vscode/src/vs/server/node/webClientServer.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/server/node/webClientServer.ts
+++ code-server/lib/vscode/src/vs/server/node/webClientServer.ts
@@ -308,6 +308,7 @@ export class WebClientServer {
@@ -335,6 +335,7 @@ export class WebClientServer {
webviewEndpoint: vscodeBase + this._staticRoute + '/out/vs/workbench/contrib/webview/browser/pre',
userDataPath: this._environmentService.userDataPath,
isEnabledFileDownloads: !this._environmentService.args['disable-file-downloads'],
@@ -254,12 +217,12 @@ Index: code-server/lib/vscode/src/vs/workbench/browser/contextkeys.ts
import { Disposable } from 'vs/base/common/lifecycle';
import { IContextKeyService, IContextKey, setConstant as setConstantContextKey } from 'vs/platform/contextkey/common/contextkey';
import { InputFocusedContext, IsMacContext, IsLinuxContext, IsWindowsContext, IsWebContext, IsMacNativeContext, IsDevelopmentContext, IsIOSContext, ProductQualityContext, IsMobileContext } from 'vs/platform/contextkey/common/contextkeys';
-import { SplitEditorsVertically, InEditorZenModeContext, ActiveEditorCanRevertContext, ActiveEditorGroupLockedContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext, AuxiliaryBarVisibleContext, SideBarVisibleContext, PanelAlignmentContext, PanelMaximizedContext, PanelVisibleContext, ActiveEditorContext, EditorsVisibleContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, EmbedderIdentifierContext, EditorTabsVisibleContext, IsCenteredLayoutContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext, ActiveEditorReadonlyContext, EditorAreaVisibleContext, ActiveEditorAvailableEditorIdsContext, DirtyWorkingCopiesContext, EmptyWorkspaceSupportContext, EnterMultiRootWorkspaceSupportContext, HasWebFileSystemAccess, IsFullscreenContext, OpenFolderWorkspaceSupportContext, RemoteNameContext, VirtualWorkspaceContext, WorkbenchStateContext, WorkspaceFolderCountContext, PanelPositionContext, TemporaryWorkspaceContext, IsEnabledFileDownloads } from 'vs/workbench/common/contextkeys';
+import { SplitEditorsVertically, InEditorZenModeContext, ActiveEditorCanRevertContext, ActiveEditorGroupLockedContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext, AuxiliaryBarVisibleContext, SideBarVisibleContext, PanelAlignmentContext, PanelMaximizedContext, PanelVisibleContext, ActiveEditorContext, EditorsVisibleContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, EmbedderIdentifierContext, EditorTabsVisibleContext, IsCenteredLayoutContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext, ActiveEditorReadonlyContext, EditorAreaVisibleContext, ActiveEditorAvailableEditorIdsContext, DirtyWorkingCopiesContext, EmptyWorkspaceSupportContext, EnterMultiRootWorkspaceSupportContext, HasWebFileSystemAccess, IsFullscreenContext, OpenFolderWorkspaceSupportContext, RemoteNameContext, VirtualWorkspaceContext, WorkbenchStateContext, WorkspaceFolderCountContext, PanelPositionContext, TemporaryWorkspaceContext, IsEnabledFileDownloads, IsEnabledCoderGettingStarted } from 'vs/workbench/common/contextkeys';
import { TEXT_DIFF_EDITOR_ID, EditorInputCapabilities, SIDE_BY_SIDE_EDITOR_ID, DEFAULT_EDITOR_ASSOCIATION } from 'vs/workbench/common/editor';
-import { SplitEditorsVertically, InEditorZenModeContext, ActiveEditorCanRevertContext, ActiveEditorGroupLockedContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext, AuxiliaryBarVisibleContext, SideBarVisibleContext, PanelAlignmentContext, PanelMaximizedContext, PanelVisibleContext, ActiveEditorContext, EditorsVisibleContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, EmbedderIdentifierContext, EditorTabsVisibleContext, IsCenteredLayoutContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext, ActiveEditorReadonlyContext, EditorAreaVisibleContext, ActiveEditorAvailableEditorIdsContext, DirtyWorkingCopiesContext, EmptyWorkspaceSupportContext, EnterMultiRootWorkspaceSupportContext, HasWebFileSystemAccess, IsFullscreenContext, OpenFolderWorkspaceSupportContext, RemoteNameContext, VirtualWorkspaceContext, WorkbenchStateContext, WorkspaceFolderCountContext, PanelPositionContext, TemporaryWorkspaceContext, IsEnabledFileDownloads, ActiveEditorCanToggleReadonlyContext, applyAvailableEditorIds } from 'vs/workbench/common/contextkeys';
+import { SplitEditorsVertically, InEditorZenModeContext, ActiveEditorCanRevertContext, ActiveEditorGroupLockedContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext, AuxiliaryBarVisibleContext, SideBarVisibleContext, PanelAlignmentContext, PanelMaximizedContext, PanelVisibleContext, ActiveEditorContext, EditorsVisibleContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, EmbedderIdentifierContext, EditorTabsVisibleContext, IsCenteredLayoutContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext, ActiveEditorReadonlyContext, EditorAreaVisibleContext, ActiveEditorAvailableEditorIdsContext, DirtyWorkingCopiesContext, EmptyWorkspaceSupportContext, EnterMultiRootWorkspaceSupportContext, HasWebFileSystemAccess, IsFullscreenContext, OpenFolderWorkspaceSupportContext, RemoteNameContext, VirtualWorkspaceContext, WorkbenchStateContext, WorkspaceFolderCountContext, PanelPositionContext, TemporaryWorkspaceContext, IsEnabledFileDownloads, IsEnabledCoderGettingStarted, ActiveEditorCanToggleReadonlyContext, applyAvailableEditorIds } from 'vs/workbench/common/contextkeys';
import { TEXT_DIFF_EDITOR_ID, EditorInputCapabilities, SIDE_BY_SIDE_EDITOR_ID, EditorResourceAccessor, SideBySideEditor } from 'vs/workbench/common/editor';
import { trackFocus, addDisposableListener, EventType } from 'vs/base/browser/dom';
import { preferredSideBySideGroupDirection, GroupDirection, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
@@ -207,6 +207,7 @@ export class WorkbenchContextKeysHandler
@@ -211,6 +211,7 @@ export class WorkbenchContextKeysHandler
// code-server
IsEnabledFileDownloads.bindTo(this.contextKeyService).set(this.environmentService.isEnabledFileDownloads ?? true)
@@ -271,7 +234,7 @@ Index: code-server/lib/vscode/src/vs/workbench/common/contextkeys.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/workbench/common/contextkeys.ts
+++ code-server/lib/vscode/src/vs/workbench/common/contextkeys.ts
@@ -36,6 +36,7 @@ export const HasWebFileSystemAccess = ne
@@ -39,6 +39,7 @@ export const HasWebFileSystemAccess = ne
export const EmbedderIdentifierContext = new RawContextKey<string | undefined>('embedderIdentifier', undefined, localize('embedderIdentifier', 'The identifier of the embedder according to the product service, if one is defined'));
export const IsEnabledFileDownloads = new RawContextKey<boolean>('isEnabledFileDownloads', true, true);

View File

@@ -1,106 +0,0 @@
Add the ability to provide a GitHub token
To test install the GitHub PR extension and start code-server with GITHUB_TOKEN
or set github-auth in the config file. The extension should be authenticated.
Index: code-server/lib/vscode/src/vs/platform/credentials/node/credentialsMainService.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/platform/credentials/node/credentialsMainService.ts
+++ code-server/lib/vscode/src/vs/platform/credentials/node/credentialsMainService.ts
@@ -5,9 +5,18 @@
import { InMemoryCredentialsProvider } from 'vs/platform/credentials/common/credentials';
import { ILogService } from 'vs/platform/log/common/log';
-import { INativeEnvironmentService } from 'vs/platform/environment/common/environment';
+import { IServerEnvironmentService } from 'vs/server/node/serverEnvironmentService';
import { IProductService } from 'vs/platform/product/common/productService';
import { BaseCredentialsMainService, KeytarModule } from 'vs/platform/credentials/common/credentialsMainService';
+import { generateUuid } from 'vs/base/common/uuid';
+import { equals as arrayEquals } from 'vs/base/common/arrays';
+
+interface IToken {
+ accessToken: string
+ account?: { label: string }
+ id: string
+ scopes: string[]
+}
export class CredentialsWebMainService extends BaseCredentialsMainService {
// Since we fallback to the in-memory credentials provider, we do not need to surface any Keytar load errors
@@ -16,10 +25,15 @@ export class CredentialsWebMainService e
constructor(
@ILogService logService: ILogService,
- @INativeEnvironmentService private readonly environmentMainService: INativeEnvironmentService,
+ @IServerEnvironmentService private readonly environmentMainService: IServerEnvironmentService,
@IProductService private readonly productService: IProductService,
) {
super(logService);
+ if (this.environmentMainService.args["github-auth"]) {
+ this.storeGitHubToken(this.environmentMainService.args["github-auth"]).catch((error) => {
+ this.logService.error('Failed to store provided GitHub token', error)
+ })
+ }
}
// If the credentials service is running on the server, we add a suffix -server to differentiate from the location that the
@@ -48,4 +62,59 @@ export class CredentialsWebMainService e
}
return this._keytarCache;
}
+
+ private async storeGitHubToken(githubToken: string): Promise<void> {
+ const extensionId = 'vscode.github-authentication';
+ const service = `${await this.getSecretStoragePrefix()}${extensionId}`;
+ const account = 'github.auth';
+ const scopes = [['read:user', 'user:email', 'repo']]
+
+ // Oddly the scopes need to match exactly so we cannot just have one token
+ // with all the scopes, instead we have to duplicate the token for each
+ // expected set of scopes.
+ const tokens: IToken[] = scopes.map((scopes) => ({
+ id: generateUuid(),
+ scopes: scopes.sort(), // Sort for comparing later.
+ accessToken: githubToken,
+ }));
+
+ const raw = await this.getPassword(service, account)
+
+ let existing: {
+ content: IToken[]
+ } | undefined;
+
+ if (raw) {
+ try {
+ const json = JSON.parse(raw);
+ json.content = JSON.parse(json.content);
+ existing = json;
+ } catch (error) {
+ this.logService.error('Failed to parse existing GitHub credentials', error)
+ }
+ }
+
+ // Keep tokens for account and scope combinations we do not have in case
+ // there is an extension that uses scopes we have not accounted for (in
+ // these cases the user will need to manually authenticate the extension
+ // through the UI) or the user has tokens for other accounts.
+ if (existing?.content) {
+ existing.content = existing.content.filter((existingToken) => {
+ const scopes = existingToken.scopes.sort();
+ return !(tokens.find((token) => {
+ return arrayEquals(scopes, token.scopes)
+ && token.account?.label === existingToken.account?.label;
+ }))
+ })
+ }
+
+ return this.setPassword(service, account, JSON.stringify({
+ extensionId,
+ ...(existing || {}),
+ content: JSON.stringify([
+ ...tokens,
+ ...(existing?.content || []),
+ ])
+ }));
+ }
}

View File

@@ -95,7 +95,7 @@ Index: code-server/lib/vscode/src/vs/base/common/processes.ts
--- code-server.orig/lib/vscode/src/vs/base/common/processes.ts
+++ code-server/lib/vscode/src/vs/base/common/processes.ts
@@ -111,6 +111,8 @@ export function sanitizeProcessEnvironme
/^VSCODE_(?!(PORTABLE|SHELL_LOGIN)).+$/,
/^VSCODE_(?!(PORTABLE|SHELL_LOGIN|ENV_REPLACE|ENV_APPEND|ENV_PREPEND)).+$/,
/^SNAP(|_.*)$/,
/^GDK_PIXBUF_.+$/,
+ /^CODE_SERVER_.+$/,
@@ -107,7 +107,7 @@ Index: code-server/lib/vscode/src/vs/workbench/browser/parts/dialogs/dialogHandl
===================================================================
--- code-server.orig/lib/vscode/src/vs/workbench/browser/parts/dialogs/dialogHandler.ts
+++ code-server/lib/vscode/src/vs/workbench/browser/parts/dialogs/dialogHandler.ts
@@ -76,8 +76,11 @@ export class BrowserDialogHandler extend
@@ -77,8 +77,11 @@ export class BrowserDialogHandler extend
async about(): Promise<void> {
const detailString = (useAgo: boolean): string => {
@@ -176,7 +176,7 @@ Index: code-server/lib/vscode/src/vs/workbench/browser/web.main.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/workbench/browser/web.main.ts
+++ code-server/lib/vscode/src/vs/workbench/browser/web.main.ts
@@ -69,6 +69,7 @@ import { IndexedDB } from 'vs/base/brows
@@ -67,6 +67,7 @@ import { IndexedDB } from 'vs/base/brows
import { BrowserCredentialsService } from 'vs/workbench/services/credentials/browser/credentialsService';
import { IWorkspace } from 'vs/workbench/services/host/browser/browserHostService';
import { WebFileSystemAccess } from 'vs/platform/files/browser/webFileSystemAccess';
@@ -184,7 +184,7 @@ Index: code-server/lib/vscode/src/vs/workbench/browser/web.main.ts
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IProgressService } from 'vs/platform/progress/common/progress';
import { DelayedLogChannel } from 'vs/workbench/services/output/common/delayedLogChannel';
@@ -119,6 +120,9 @@ export class BrowserMain extends Disposa
@@ -132,6 +133,9 @@ export class BrowserMain extends Disposa
// Startup
const instantiationService = workbench.startup();
@@ -264,11 +264,11 @@ Index: code-server/lib/vscode/src/vs/server/node/webClientServer.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/server/node/webClientServer.ts
+++ code-server/lib/vscode/src/vs/server/node/webClientServer.ts
@@ -308,6 +308,7 @@ export class WebClientServer {
folderUri: resolveWorkspaceURI(this._environmentService.args['default-folder']),
workspaceUri: resolveWorkspaceURI(this._environmentService.args['default-workspace']),
productConfiguration: <Partial<IProductConfiguration>>{
+ codeServerVersion: this._productService.codeServerVersion,
embedderIdentifier: 'server-distro',
extensionsGallery: this._webExtensionResourceUrlTemplate ? {
...this._productService.extensionsGallery,
@@ -306,6 +306,7 @@ export class WebClientServer {
} : undefined;
const productConfiguration = <Partial<IProductConfiguration>>{
+ codeServerVersion: this._productService.codeServerVersion,
embedderIdentifier: 'server-distro',
extensionsGallery: this._webExtensionResourceUrlTemplate ? {
...this._productService.extensionsGallery,

View File

@@ -20,7 +20,7 @@ Index: code-server/lib/vscode/src/vs/server/node/webClientServer.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/server/node/webClientServer.ts
+++ code-server/lib/vscode/src/vs/server/node/webClientServer.ts
@@ -303,6 +303,7 @@ export class WebClientServer {
@@ -327,6 +327,7 @@ export class WebClientServer {
const workbenchWebConfiguration = {
remoteAuthority,
webviewEndpoint: vscodeBase + this._staticRoute + '/out/vs/workbench/contrib/webview/browser/pre',
@@ -32,7 +32,7 @@ Index: code-server/lib/vscode/src/vs/workbench/browser/web.api.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/workbench/browser/web.api.ts
+++ code-server/lib/vscode/src/vs/workbench/browser/web.api.ts
@@ -255,6 +255,11 @@ export interface IWorkbenchConstructionO
@@ -283,6 +283,11 @@ export interface IWorkbenchConstructionO
*/
readonly configurationDefaults?: Record<string, any>;
@@ -48,7 +48,7 @@ Index: code-server/lib/vscode/src/vs/workbench/services/environment/browser/envi
===================================================================
--- code-server.orig/lib/vscode/src/vs/workbench/services/environment/browser/environmentService.ts
+++ code-server/lib/vscode/src/vs/workbench/services/environment/browser/environmentService.ts
@@ -92,7 +92,14 @@ export class BrowserWorkbenchEnvironment
@@ -102,7 +102,14 @@ export class BrowserWorkbenchEnvironment
get logFile(): URI { return joinPath(this.windowLogsPath, 'window.log'); }
@memoize

View File

@@ -20,7 +20,7 @@ Index: code-server/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
+++ code-server/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
@@ -13,6 +13,7 @@ import { IEnvironmentService, INativeEnv
@@ -15,6 +15,7 @@ import { URI } from 'vs/base/common/uri'
export const serverOptions: OptionDescriptions<Required<ServerParsedArgs>> = {
/* ----- code-server ----- */
'disable-update-check': { type: 'boolean' },
@@ -28,7 +28,7 @@ Index: code-server/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
/* ----- server setup ----- */
@@ -92,6 +93,7 @@ export const serverOptions: OptionDescri
@@ -95,6 +96,7 @@ export const serverOptions: OptionDescri
export interface ServerParsedArgs {
/* ----- code-server ----- */
'disable-update-check'?: boolean;
@@ -40,14 +40,14 @@ Index: code-server/lib/vscode/src/vs/server/node/webClientServer.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/server/node/webClientServer.ts
+++ code-server/lib/vscode/src/vs/server/node/webClientServer.ts
@@ -313,6 +313,7 @@ export class WebClientServer {
codeServerVersion: this._productService.codeServerVersion,
rootEndpoint: base,
updateEndpoint: !this._environmentService.args['disable-update-check'] ? base + '/update/check' : undefined,
+ logoutEndpoint: this._environmentService.args['auth'] && this._environmentService.args['auth'] !== "none" ? base + '/logout' : undefined,
embedderIdentifier: 'server-distro',
extensionsGallery: this._productService.extensionsGallery,
},
@@ -311,6 +311,7 @@ export class WebClientServer {
codeServerVersion: this._productService.codeServerVersion,
rootEndpoint: base,
updateEndpoint: !this._environmentService.args['disable-update-check'] ? base + '/update/check' : undefined,
+ logoutEndpoint: this._environmentService.args['auth'] && this._environmentService.args['auth'] !== "none" ? base + '/logout' : undefined,
embedderIdentifier: 'server-distro',
extensionsGallery: this._productService.extensionsGallery,
};
Index: code-server/lib/vscode/src/vs/workbench/browser/client.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/workbench/browser/client.ts

View File

@@ -40,7 +40,7 @@ Index: code-server/lib/vscode/src/vs/server/node/webClientServer.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/server/node/webClientServer.ts
+++ code-server/lib/vscode/src/vs/server/node/webClientServer.ts
@@ -111,7 +111,7 @@ export class WebClientServer {
@@ -113,7 +113,7 @@ export class WebClientServer {
const serverRootPath = getRemoteServerRootPath(_productService);
this._staticRoute = `${serverRootPath}/static`;
this._callbackRoute = `${serverRootPath}/callback`;
@@ -49,22 +49,22 @@ Index: code-server/lib/vscode/src/vs/server/node/webClientServer.ts
}
/**
@@ -312,14 +312,7 @@ export class WebClientServer {
codeServerVersion: this._productService.codeServerVersion,
rootEndpoint: base,
embedderIdentifier: 'server-distro',
- extensionsGallery: this._webExtensionResourceUrlTemplate ? {
- ...this._productService.extensionsGallery,
- 'resourceUrlTemplate': this._webExtensionResourceUrlTemplate.with({
- scheme: 'http',
- authority: remoteAuthority,
- path: `${this._webExtensionRoute}/${this._webExtensionResourceUrlTemplate.authority}${this._webExtensionResourceUrlTemplate.path}`
- }).toString(true)
- } : undefined
+ extensionsGallery: this._productService.extensionsGallery,
},
callbackRoute: this._callbackRoute
@@ -311,14 +311,7 @@ export class WebClientServer {
codeServerVersion: this._productService.codeServerVersion,
rootEndpoint: base,
embedderIdentifier: 'server-distro',
- extensionsGallery: this._webExtensionResourceUrlTemplate ? {
- ...this._productService.extensionsGallery,
- 'resourceUrlTemplate': this._webExtensionResourceUrlTemplate.with({
- scheme: 'http',
- authority: remoteAuthority,
- path: `${this._webExtensionRoute}/${this._webExtensionResourceUrlTemplate.authority}${this._webExtensionResourceUrlTemplate.path}`
- }).toString(true)
- } : undefined
+ extensionsGallery: this._productService.extensionsGallery,
};
if (!this._environmentService.isBuilt) {
Index: code-server/lib/vscode/src/vs/platform/extensionResourceLoader/common/extensionResourceLoader.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/platform/extensionResourceLoader/common/extensionResourceLoader.ts
@@ -74,10 +74,10 @@ Index: code-server/lib/vscode/src/vs/platform/extensionResourceLoader/common/ext
import { TelemetryLevel } from 'vs/platform/telemetry/common/telemetry';
import { getTelemetryLevel, supportsTelemetry } from 'vs/platform/telemetry/common/telemetryUtils';
-import { getRemoteServerRootPath } from 'vs/platform/remote/common/remoteHosts';
import { TargetPlatform } from 'vs/platform/extensions/common/extensions';
const WEB_EXTENSION_RESOURCE_END_POINT = 'web-extension-resource';
@@ -60,7 +59,7 @@ export abstract class AbstractExtensionR
@@ -77,7 +76,7 @@ export abstract class AbstractExtensionR
private readonly _environmentService: IEnvironmentService,
private readonly _configurationService: IConfigurationService,
) {

View File

@@ -42,17 +42,8 @@ Index: code-server/lib/vscode/src/vs/platform/remote/browser/remoteAuthorityReso
===================================================================
--- code-server.orig/lib/vscode/src/vs/platform/remote/browser/remoteAuthorityResolverService.ts
+++ code-server/lib/vscode/src/vs/platform/remote/browser/remoteAuthorityResolverService.ts
@@ -11,7 +11,7 @@ import { StopWatch } from 'vs/base/commo
import { URI } from 'vs/base/common/uri';
import { ILogService } from 'vs/platform/log/common/log';
import { IProductService } from 'vs/platform/product/common/productService';
-import { IRemoteAuthorityResolverService, IRemoteConnectionData, ResolvedAuthority, ResolverResult, getRemoteAuthorityPrefix } from 'vs/platform/remote/common/remoteAuthorityResolver';
+import { IRemoteAuthorityResolverService, IRemoteConnectionData, ResolvedAuthority, ResolvedOptions, ResolverResult, getRemoteAuthorityPrefix } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { getRemoteServerRootPath, parseAuthorityWithOptionalPort } from 'vs/platform/remote/common/remoteHosts';
export class RemoteAuthorityResolverService extends Disposable implements IRemoteAuthorityResolverService {
@@ -29,7 +29,7 @@ export class RemoteAuthorityResolverServ
constructor(
@@ -33,7 +33,7 @@ export class RemoteAuthorityResolverServ
isWorkbenchOptionsBasedResolution: boolean,
connectionToken: Promise<string> | string | undefined,
resourceUriProvider: ((uri: URI) => URI) | undefined,
- @IProductService productService: IProductService,
@@ -60,7 +51,7 @@ Index: code-server/lib/vscode/src/vs/platform/remote/browser/remoteAuthorityReso
@ILogService private readonly _logService: ILogService,
) {
super();
@@ -75,9 +75,14 @@ export class RemoteAuthorityResolverServ
@@ -84,9 +84,14 @@ export class RemoteAuthorityResolverServ
const connectionToken = await Promise.resolve(this._connectionTokens.get(authority) || this._connectionToken);
performance.mark(`code/didResolveConnectionToken/${authorityPrefix}`);
this._logService.info(`Resolved connection token (${authorityPrefix}) after ${sw.elapsed()} ms`);
@@ -71,28 +62,28 @@ Index: code-server/lib/vscode/src/vs/platform/remote/browser/remoteAuthorityReso
+ }
const defaultPort = (/^https:/.test(window.location.href) ? 443 : 80);
const { host, port } = parseAuthorityWithOptionalPort(authority, defaultPort);
- const result: ResolverResult = { authority: { authority, host: host, port: port, connectionToken } };
+ const result: ResolverResult = { authority: { authority, host: host, port: port, connectionToken }, options };
RemoteAuthorities.set(authority, result.authority.host, result.authority.port);
- const result: ResolverResult = { authority: { authority, connectTo: new WebSocketRemoteConnection(host, port), connectionToken } };
+ const result: ResolverResult = { authority: { authority, connectTo: new WebSocketRemoteConnection(host, port), connectionToken }, options };
RemoteAuthorities.set(authority, host, port);
this._cache.set(authority, result);
this._onDidChangeConnectionData.fire();
Index: code-server/lib/vscode/src/vs/server/node/webClientServer.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/server/node/webClientServer.ts
+++ code-server/lib/vscode/src/vs/server/node/webClientServer.ts
@@ -314,6 +314,7 @@ export class WebClientServer {
rootEndpoint: base,
updateEndpoint: !this._environmentService.args['disable-update-check'] ? base + '/update/check' : undefined,
logoutEndpoint: this._environmentService.args['auth'] && this._environmentService.args['auth'] !== "none" ? base + '/logout' : undefined,
+ proxyEndpointTemplate: process.env.VSCODE_PROXY_URI ?? base + '/proxy/{{port}}/',
embedderIdentifier: 'server-distro',
extensionsGallery: this._productService.extensionsGallery,
},
@@ -312,6 +312,7 @@ export class WebClientServer {
rootEndpoint: base,
updateEndpoint: !this._environmentService.args['disable-update-check'] ? base + '/update/check' : undefined,
logoutEndpoint: this._environmentService.args['auth'] && this._environmentService.args['auth'] !== "none" ? base + '/logout' : undefined,
+ proxyEndpointTemplate: process.env.VSCODE_PROXY_URI ?? base + '/proxy/{{port}}/',
embedderIdentifier: 'server-distro',
extensionsGallery: this._productService.extensionsGallery,
};
Index: code-server/lib/vscode/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts
+++ code-server/lib/vscode/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts
@@ -383,7 +383,7 @@ export async function createTerminalEnvi
@@ -271,7 +271,7 @@ export async function createTerminalEnvi
// Sanitize the environment, removing any undesirable VS Code and Electron environment
// variables
@@ -105,33 +96,32 @@ 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
@@ -21,6 +21,7 @@ import type { ICredentialsProvider } fro
import type { IURLCallbackProvider } from 'vs/workbench/services/url/browser/urlService';
@@ -19,6 +19,7 @@ import { isFolderToOpen, isWorkspaceToOp
import type { IWorkbenchConstructionOptions } from 'vs/workbench/browser/web.api';
import { AuthenticationSessionInfo } from 'vs/workbench/services/authentication/browser/authenticationService';
import type { IWorkspace, IWorkspaceProvider } from 'vs/workbench/services/host/browser/browserHostService';
+import { extractLocalHostUriMetaDataForPortMapping, TunnelOptions, TunnelCreationOptions } from 'vs/platform/tunnel/common/tunnel';
import type { IURLCallbackProvider } from 'vs/workbench/services/url/browser/urlService';
import { create } from 'vs/workbench/workbench.web.main';
interface ICredential {
service: string;
@@ -511,6 +512,38 @@ function doCreateUri(path: string, query
} : undefined,
@@ -571,6 +572,39 @@ class WorkspaceProvider implements IWork
settingsSyncOptions: config.settingsSyncOptions ? { enabled: config.settingsSyncOptions.enabled, } : undefined,
workspaceProvider: WorkspaceProvider.create(config),
urlCallbackProvider: new LocalStorageURLCallbackProvider(config.callbackRoute),
- credentialsProvider: config.remoteAuthority ? undefined : new LocalStorageCredentialsProvider() // with a remote, we don't use a local credentials provider
+ credentialsProvider: config.remoteAuthority ? undefined : new LocalStorageCredentialsProvider(), // with a remote, we don't use a local credentials provider
+ resolveExternalUri: (uri: URI): Promise<URI> => {
+ let resolvedUri = uri
+ const localhostMatch = extractLocalHostUriMetaDataForPortMapping(resolvedUri)
+
+ if (localhostMatch && resolvedUri.authority !== location.host) {
+ if (config.productConfiguration && config.productConfiguration.proxyEndpointTemplate) {
+ resolvedUri = URI.parse(new URL(config.productConfiguration.proxyEndpointTemplate.replace('{{port}}', localhostMatch.port.toString()), window.location.href).toString())
+ const renderedTemplate = config.productConfiguration.proxyEndpointTemplate
+ .replace('{{port}}', localhostMatch.port.toString())
+ .replace('{{host}}', window.location.host)
+ resolvedUri = URI.parse(new URL(renderedTemplate, window.location.href).toString())
+ } else {
+ throw new Error(`Failed to resolve external URI: ${uri.toString()}. Could not determine base url because productConfiguration missing.`)
+ }
+ }
+
+ // If not localhost, return unmodified
+ // If not localhost, return unmodified.
+ return Promise.resolve(resolvedUri)
+ },
+ tunnelProvider: {
@@ -150,19 +140,20 @@ Index: code-server/lib/vscode/src/vs/code/browser/workbench/workbench.ts
+ }
+ })
+ }
+ }
});
})();
+ },
secretStorageProvider: config.remoteAuthority && !secretStorageKeyPath
? undefined /* with a remote without embedder-preferred storage, store on the remote */
: new LocalStorageSecretStorageProvider(secretStorageCrypto),
Index: code-server/lib/vscode/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts
+++ code-server/lib/vscode/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts
@@ -73,7 +73,7 @@ export class ForwardedPortsView extends
@@ -76,7 +76,7 @@ export class ForwardedPortsView extends
this.contextKeyListener = undefined;
}
- const viewEnabled: boolean = !!forwardedPortsViewEnabled.getValue(this.contextKeyService);
+ const viewEnabled: boolean = true;
if (this.environmentService.remoteAuthority && viewEnabled) {
if (viewEnabled) {
const viewContainer = await this.getViewContainer();

68
patches/safari.diff Normal file
View File

@@ -0,0 +1,68 @@
Revert back to es2020
es2022 outputs static blocks when using static properties that are not
compatible with Safari, or at least not older versions of Safari.
Index: code-server/lib/vscode/src/tsconfig.base.json
===================================================================
--- code-server.orig/lib/vscode/src/tsconfig.base.json
+++ code-server/lib/vscode/src/tsconfig.base.json
@@ -17,9 +17,30 @@
"./vs/*"
]
},
- "target": "es2022",
- "useDefineForClassFields": false,
+ "target": "es2020",
"lib": [
+ "ES2016",
+ "ES2017.Object",
+ "ES2017.String",
+ "ES2017.Intl",
+ "ES2017.TypedArrays",
+ "ES2018.AsyncIterable",
+ "ES2018.AsyncGenerator",
+ "ES2018.Promise",
+ "ES2018.Regexp",
+ "ES2018.Intl",
+ "ES2019.Array",
+ "ES2019.Object",
+ "ES2019.String",
+ "ES2019.Symbol",
+ "ES2020.BigInt",
+ "ES2020.Promise",
+ "ES2020.String",
+ "ES2020.Symbol.WellKnown",
+ "ES2020.Intl",
+ "ES2021.Promise",
+ "ES2021.String",
+ "ES2021.WeakRef",
"ES2022",
"DOM",
"DOM.Iterable",
Index: code-server/lib/vscode/build/lib/tsb/transpiler.js
===================================================================
--- code-server.orig/lib/vscode/build/lib/tsb/transpiler.js
+++ code-server/lib/vscode/build/lib/tsb/transpiler.js
@@ -293,7 +293,7 @@ class SwcTranspiler {
tsx: false,
decorators: true
},
- target: 'es2022',
+ target: 'es2020',
loose: false,
minify: {
compress: false,
Index: code-server/lib/vscode/build/lib/tsb/transpiler.ts
===================================================================
--- code-server.orig/lib/vscode/build/lib/tsb/transpiler.ts
+++ code-server/lib/vscode/build/lib/tsb/transpiler.ts
@@ -376,7 +376,7 @@ export class SwcTranspiler implements IT
tsx: false,
decorators: true
},
- target: 'es2022',
+ target: 'es2020',
loose: false,
minify: {
compress: false,

View File

@@ -9,7 +9,6 @@ update-check.diff
logout.diff
store-socket.diff
proxy-uri.diff
github-auth.diff
unique-db.diff
local-storage.diff
service-worker.diff
@@ -19,3 +18,4 @@ telemetry.diff
display-language.diff
cli-window-open.diff
getting-started.diff
safari.diff

View File

@@ -54,14 +54,14 @@ Index: code-server/lib/vscode/src/vs/server/node/webClientServer.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/server/node/webClientServer.ts
+++ code-server/lib/vscode/src/vs/server/node/webClientServer.ts
@@ -316,6 +316,10 @@ export class WebClientServer {
updateEndpoint: !this._environmentService.args['disable-update-check'] ? base + '/update/check' : undefined,
logoutEndpoint: this._environmentService.args['auth'] && this._environmentService.args['auth'] !== "none" ? base + '/logout' : undefined,
proxyEndpointTemplate: process.env.VSCODE_PROXY_URI ?? base + '/proxy/{{port}}/',
+ serviceWorker: {
+ scope: vscodeBase + '/',
+ path: base + '/_static/out/browser/serviceWorker.js',
+ },
embedderIdentifier: 'server-distro',
extensionsGallery: this._productService.extensionsGallery,
},
@@ -313,6 +313,10 @@ export class WebClientServer {
updateEndpoint: !this._environmentService.args['disable-update-check'] ? base + '/update/check' : undefined,
logoutEndpoint: this._environmentService.args['auth'] && this._environmentService.args['auth'] !== "none" ? base + '/logout' : undefined,
proxyEndpointTemplate: process.env.VSCODE_PROXY_URI ?? base + '/proxy/{{port}}/',
+ serviceWorker: {
+ scope: vscodeBase + '/',
+ path: base + '/_static/out/browser/serviceWorker.js',
+ },
embedderIdentifier: 'server-distro',
extensionsGallery: this._productService.extensionsGallery,
};

View File

@@ -10,7 +10,7 @@ Index: code-server/lib/vscode/build/gulpfile.reh.js
===================================================================
--- code-server.orig/lib/vscode/build/gulpfile.reh.js
+++ code-server/lib/vscode/build/gulpfile.reh.js
@@ -191,8 +191,7 @@ function packageTask(type, platform, arc
@@ -238,8 +238,7 @@ function packageTask(type, platform, arc
const src = gulp.src(sourceFolderName + '/**', { base: '.' })
.pipe(rename(function (path) { path.dirname = path.dirname.replace(new RegExp('^' + sourceFolderName), 'out'); }))
@@ -20,7 +20,7 @@ Index: code-server/lib/vscode/build/gulpfile.reh.js
const workspaceExtensionPoints = ['debuggers', 'jsonValidation'];
const isUIExtension = (manifest) => {
@@ -231,9 +230,9 @@ function packageTask(type, platform, arc
@@ -278,9 +277,9 @@ function packageTask(type, platform, arc
.map(name => `.build/extensions/${name}/**`);
const extensions = gulp.src(extensionPaths, { base: '.build', dot: true });
@@ -32,7 +32,7 @@ Index: code-server/lib/vscode/build/gulpfile.reh.js
let version = packageJson.version;
const quality = product.quality;
@@ -388,7 +387,7 @@ function tweakProductForServerWeb(produc
@@ -427,7 +426,7 @@ function tweakProductForServerWeb(produc
const minifyTask = task.define(`minify-vscode-${type}`, task.series(
optimizeTask,
util.rimraf(`out-vscode-${type}-min`),

View File

@@ -1,4 +1,4 @@
Store a static reference to the IPC socket
Store the IPC socket with workspace metadata.
This lets us use it to open files inside code-server from outside of
code-server.
@@ -9,29 +9,121 @@ To test this:
It should open in your existing code-server instance.
When the extension host is terminated, the socket is unregistered.
Index: code-server/lib/vscode/src/vs/workbench/api/node/extHostExtensionService.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/workbench/api/node/extHostExtensionService.ts
+++ code-server/lib/vscode/src/vs/workbench/api/node/extHostExtensionService.ts
@@ -2,7 +2,9 @@
@@ -2,7 +2,7 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-
+import { promises as fs } from 'fs';
+import * as os from 'os'
+import * as path from 'vs/base/common/path';
+import * as _http from 'http';
import * as performance from 'vs/base/common/performance';
import { createApiFactoryAndRegisterActors } from 'vs/workbench/api/common/extHost.api.impl';
import { RequireInterceptor } from 'vs/workbench/api/common/extHostRequireInterceptor';
@@ -72,6 +74,10 @@ export class ExtHostExtensionService ext
if (this._initData.remote.isRemote && this._initData.remote.authority) {
const cliServer = this._instaService.createInstance(CLIServer);
process.env['VSCODE_IPC_HOOK_CLI'] = cliServer.ipcHandlePath;
+
+ fs.writeFile(path.join(os.tmpdir(), 'vscode-ipc'), cliServer.ipcHandlePath).catch((error) => {
+ this._logService.error(error);
+ });
}
@@ -17,6 +17,7 @@ import { ExtensionRuntime } from 'vs/wor
import { CLIServer } from 'vs/workbench/api/node/extHostCLIServer';
import { realpathSync } from 'vs/base/node/extpath';
import { ExtHostConsoleForwarder } from 'vs/workbench/api/node/extHostConsoleForwarder';
+import { IExtHostWorkspace } from '../common/extHostWorkspace';
import { ExtHostDiskFileSystemProvider } from 'vs/workbench/api/node/extHostDiskFileSystemProvider';
// Module loading tricks
class NodeModuleRequireInterceptor extends RequireInterceptor {
@@ -83,6 +84,52 @@ export class ExtHostExtensionService ext
await interceptor.install();
performance.mark('code/extHost/didInitAPI');
+ (async () => {
+ const socketPath = process.env['VSCODE_IPC_HOOK_CLI'];
+ const codeServerSocketPath = process.env['CODE_SERVER_SESSION_SOCKET']
+ if (!socketPath || !codeServerSocketPath) {
+ return;
+ }
+ const workspace = this._instaService.invokeFunction((accessor) => {
+ const workspaceService = accessor.get(IExtHostWorkspace);
+ return workspaceService.workspace;
+ });
+ const entry = {
+ workspace,
+ socketPath
+ };
+ const message = JSON.stringify({entry});
+ await new Promise<void>((resolve, reject) => {
+ const opts: _http.RequestOptions = {
+ path: '/add-session',
+ socketPath: codeServerSocketPath,
+ method: 'POST',
+ headers: {
+ 'content-type': 'application/json',
+ }
+ };
+ const req = _http.request(opts, (res) => {
+ res.on('error', reject);
+ res.on('end', () => {
+ try {
+ if (res.statusCode === 200) {
+ resolve();
+ } else {
+ reject(new Error('Unexpected status code: ' + res.statusCode));
+ }
+ } catch (e: unknown) {
+ reject(e);
+ }
+ });
+ });
+ req.on('error', reject);
+ req.write(message);
+ req.end();
+ });
+ })().catch(error => {
+ this._logService.error(error);
+ });
+
// Do this when extension service exists, but extensions are not being activated yet.
const configProvider = await this._extHostConfiguration.getConfigProvider();
await connectProxyResolver(this._extHostWorkspace, configProvider, this, this._logService, this._mainThreadTelemetryProxy, this._initData);
Index: code-server/lib/vscode/src/vs/workbench/api/node/extensionHostProcess.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/workbench/api/node/extensionHostProcess.ts
+++ code-server/lib/vscode/src/vs/workbench/api/node/extensionHostProcess.ts
@@ -3,6 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
+import * as _http from 'http';
import * as nativeWatchdog from 'native-watchdog';
import * as net from 'net';
import * as minimist from 'minimist';
@@ -400,7 +401,28 @@ async function startExtensionHostProcess
);
// rewrite onTerminate-function to be a proper shutdown
- onTerminate = (reason: string) => extensionHostMain.terminate(reason);
+ onTerminate = (reason: string) => {
+ extensionHostMain.terminate(reason);
+
+ const socketPath = process.env['VSCODE_IPC_HOOK_CLI'];
+ const codeServerSocketPath = process.env['CODE_SERVER_SESSION_SOCKET']
+ if (!socketPath || !codeServerSocketPath) {
+ return;
+ }
+ const message = JSON.stringify({socketPath});
+ const opts: _http.RequestOptions = {
+ path: '/delete-session',
+ socketPath: codeServerSocketPath,
+ method: 'POST',
+ headers: {
+ 'content-type': 'application/json',
+ 'accept': 'application/json'
+ }
+ };
+ const req = _http.request(opts);
+ req.write(message);
+ req.end();
+ };
}
startExtensionHostProcess().catch((err) => console.log(err));

View File

@@ -12,7 +12,7 @@ Index: code-server/lib/vscode/src/vs/server/node/serverServices.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/server/node/serverServices.ts
+++ code-server/lib/vscode/src/vs/server/node/serverServices.ts
@@ -69,6 +69,7 @@ import { IExtensionsScannerService } fro
@@ -67,6 +67,7 @@ import { IExtensionsScannerService } fro
import { ExtensionsScannerService } from 'vs/server/node/extensionsScannerService';
import { IExtensionsProfileScannerService } from 'vs/platform/extensionManagement/common/extensionsProfileScannerService';
import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile';
@@ -20,22 +20,18 @@ Index: code-server/lib/vscode/src/vs/server/node/serverServices.ts
import { NullPolicyService } from 'vs/platform/policy/common/policy';
import { OneDataSystemAppender } from 'vs/platform/telemetry/node/1dsAppender';
import { LoggerService } from 'vs/platform/log/node/loggerService';
@@ -150,10 +151,13 @@ export async function setupServerService
@@ -150,7 +151,10 @@ export async function setupServerService
let oneDsAppender: ITelemetryAppender = NullAppender;
const isInternal = isInternalTelemetry(productService, configurationService);
if (supportsTelemetry(productService, environmentService)) {
- if (productService.aiConfig && productService.aiConfig.ariaKey) {
+ const telemetryEndpoint = process.env.CS_TELEMETRY_URL || "https://v1.telemetry.coder.com/track";
+ if (telemetryEndpoint) {
+ oneDsAppender = new OneDataSystemAppender(false, eventPrefix, null, () => new TelemetryClient(telemetryEndpoint));
+ oneDsAppender = new OneDataSystemAppender(requestService, false, eventPrefix, null, () => new TelemetryClient(telemetryEndpoint));
+ } else if (productService.aiConfig && productService.aiConfig.ariaKey) {
oneDsAppender = new OneDataSystemAppender(isInternal, eventPrefix, null, productService.aiConfig.ariaKey);
- disposables.add(toDisposable(() => oneDsAppender?.flush())); // Ensure the AI appender is disposed so that it flushes remaining data
oneDsAppender = new OneDataSystemAppender(requestService, isInternal, eventPrefix, null, productService.aiConfig.ariaKey);
disposables.add(toDisposable(() => oneDsAppender?.flush())); // Ensure the AI appender is disposed so that it flushes remaining data
}
+ disposables.add(toDisposable(() => oneDsAppender?.flush())); // Ensure the AI appender is disposed so that it flushes remaining data
const config: ITelemetryServiceConfig = {
appenders: [oneDsAppender],
Index: code-server/lib/vscode/src/vs/server/node/telemetryClient.ts
===================================================================
--- /dev/null
@@ -94,11 +90,11 @@ Index: code-server/lib/vscode/src/vs/server/node/webClientServer.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/server/node/webClientServer.ts
+++ code-server/lib/vscode/src/vs/server/node/webClientServer.ts
@@ -321,6 +321,7 @@ export class WebClientServer {
scope: vscodeBase + '/',
path: base + '/_static/out/browser/serviceWorker.js',
},
+ enableTelemetry: this._productService.enableTelemetry,
embedderIdentifier: 'server-distro',
extensionsGallery: this._productService.extensionsGallery,
@@ -317,6 +317,7 @@ export class WebClientServer {
scope: vscodeBase + '/',
path: base + '/_static/out/browser/serviceWorker.js',
},
+ enableTelemetry: this._productService.enableTelemetry,
embedderIdentifier: 'server-distro',
extensionsGallery: this._productService.extensionsGallery,
};

View File

@@ -105,20 +105,20 @@ Index: code-server/lib/vscode/src/vs/server/node/webClientServer.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/server/node/webClientServer.ts
+++ code-server/lib/vscode/src/vs/server/node/webClientServer.ts
@@ -312,6 +312,7 @@ export class WebClientServer {
productConfiguration: <Partial<IProductConfiguration>>{
codeServerVersion: this._productService.codeServerVersion,
rootEndpoint: base,
+ updateEndpoint: !this._environmentService.args['disable-update-check'] ? base + '/update/check' : undefined,
embedderIdentifier: 'server-distro',
extensionsGallery: this._productService.extensionsGallery,
},
@@ -310,6 +310,7 @@ export class WebClientServer {
const productConfiguration = <Partial<IProductConfiguration>>{
codeServerVersion: this._productService.codeServerVersion,
rootEndpoint: base,
+ updateEndpoint: !this._environmentService.args['disable-update-check'] ? base + '/update/check' : undefined,
embedderIdentifier: 'server-distro',
extensionsGallery: this._productService.extensionsGallery,
};
Index: code-server/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
+++ code-server/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
@@ -11,6 +11,8 @@ import { refineServiceDecorator } from '
import { IEnvironmentService, INativeEnvironmentService } from 'vs/platform/environment/common/environment';
@@ -13,6 +13,8 @@ import { memoize } from 'vs/base/common/
import { URI } from 'vs/base/common/uri';
export const serverOptions: OptionDescriptions<Required<ServerParsedArgs>> = {
+ /* ----- code-server ----- */
@@ -126,7 +126,7 @@ Index: code-server/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
/* ----- server setup ----- */
@@ -88,6 +90,8 @@ export const serverOptions: OptionDescri
@@ -91,6 +93,8 @@ export const serverOptions: OptionDescri
};
export interface ServerParsedArgs {

View File

@@ -41,7 +41,7 @@ Index: code-server/lib/vscode/src/vs/workbench/services/environment/browser/envi
===================================================================
--- code-server.orig/lib/vscode/src/vs/workbench/services/environment/browser/environmentService.ts
+++ code-server/lib/vscode/src/vs/workbench/services/environment/browser/environmentService.ts
@@ -215,7 +215,7 @@ export class BrowserWorkbenchEnvironment
@@ -225,7 +225,7 @@ export class BrowserWorkbenchEnvironment
@memoize
get webviewExternalEndpoint(): string {
@@ -54,7 +54,7 @@ Index: code-server/lib/vscode/src/vs/server/node/webClientServer.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/server/node/webClientServer.ts
+++ code-server/lib/vscode/src/vs/server/node/webClientServer.ts
@@ -302,6 +302,7 @@ export class WebClientServer {
@@ -323,6 +323,7 @@ export class WebClientServer {
const workbenchWebConfiguration = {
remoteAuthority,
@@ -70,12 +70,12 @@ Index: code-server/lib/vscode/src/vs/workbench/contrib/webview/browser/pre/index
<meta charset="UTF-8">
<meta http-equiv="Content-Security-Policy"
- content="default-src 'none'; script-src 'sha256-4A7BC3A+gc8e1t5I0rfDNtBiGlA0/GTojKdfdDHAWYw=' 'self'; frame-src 'self'; style-src 'unsafe-inline';">
+ content="default-src 'none'; script-src 'sha256-8I6aYsGyWES1C043+SeEOyeZlC0Y0HN5WevEDh97c1Y=' 'self'; frame-src 'self'; style-src 'unsafe-inline';">
- content="default-src 'none'; script-src 'sha256-QA1gXilHYAUFCvp7MpjgcmyBCFzSKV0SpiecMU8aUVc=' 'self'; frame-src 'self'; style-src 'unsafe-inline';">
+ content="default-src 'none'; script-src 'sha256-5X5RiKYn8NTJVx919WStPrAmsV80rIIBbePhKquPcAQ=' 'self'; frame-src 'self'; style-src 'unsafe-inline';">
<!-- Disable pinch zooming -->
<meta name="viewport"
@@ -323,6 +323,12 @@
@@ -335,6 +335,12 @@
const hostname = location.hostname;
@@ -92,7 +92,7 @@ Index: code-server/lib/vscode/src/vs/workbench/contrib/webview/browser/pre/index
===================================================================
--- code-server.orig/lib/vscode/src/vs/workbench/contrib/webview/browser/pre/index-no-csp.html
+++ code-server/lib/vscode/src/vs/workbench/contrib/webview/browser/pre/index-no-csp.html
@@ -322,6 +322,12 @@
@@ -334,6 +334,12 @@
const hostname = location.hostname;

View File

@@ -1,4 +1,4 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />

View File

@@ -46,7 +46,9 @@ button {
.card-box {
background-color: rgb(250, 253, 258);
border-radius: 5px;
box-shadow: rgba(60, 66, 87, 0.117647) 0px 7px 14px 0px, rgba(0, 0, 0, 0.117647) 0px 3px 6px 0px;
box-shadow:
rgba(60, 66, 87, 0.117647) 0px 7px 14px 0px,
rgba(0, 0, 0, 0.117647) 0px 3px 6px 0px;
max-width: 650px;
width: 100%;
}

View File

@@ -1,4 +1,4 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />

View File

@@ -14,7 +14,11 @@ export enum HttpCode {
* used in the HTTP response.
*/
export class HttpError extends Error {
public constructor(message: string, public readonly statusCode: HttpCode, public readonly details?: object) {
public constructor(
message: string,
public readonly statusCode: HttpCode,
public readonly details?: object,
) {
super(message)
this.name = this.constructor.name
}

View File

@@ -9,9 +9,11 @@ import * as util from "../common/util"
import { DefaultedArgs } from "./cli"
import { disposer } from "./http"
import { isNodeJSErrnoException } from "./util"
import { EditorSessionManager, makeEditorSessionManagerServer } from "./vscodeSocket"
import { handleUpgrade } from "./wsRouter"
type ListenOptions = Pick<DefaultedArgs, "socket-mode" | "socket" | "port" | "host">
type SocketOptions = { socket: string; "socket-mode"?: string }
type ListenOptions = DefaultedArgs | SocketOptions
export interface App extends Disposable {
/** Handles regular HTTP requests. */
@@ -20,12 +22,18 @@ export interface App extends Disposable {
wsRouter: Express
/** The underlying HTTP server. */
server: http.Server
/** Handles requests to the editor session management API. */
editorSessionManagerServer: http.Server
}
export const listen = async (server: http.Server, { host, port, socket, "socket-mode": mode }: ListenOptions) => {
if (socket) {
const isSocketOpts = (opts: ListenOptions): opts is SocketOptions => {
return !!(opts as SocketOptions).socket || !(opts as DefaultedArgs).host
}
export const listen = async (server: http.Server, opts: ListenOptions) => {
if (isSocketOpts(opts)) {
try {
await fs.unlink(socket)
await fs.unlink(opts.socket)
} catch (error: any) {
handleArgsSocketCatchError(error)
}
@@ -38,18 +46,20 @@ export const listen = async (server: http.Server, { host, port, socket, "socket-
server.on("error", (err) => util.logError(logger, "http server error", err))
resolve()
}
if (socket) {
server.listen(socket, onListen)
if (isSocketOpts(opts)) {
server.listen(opts.socket, onListen)
} else {
// [] is the correct format when using :: but Node errors with them.
server.listen(port, host.replace(/^\[|\]$/g, ""), onListen)
server.listen(opts.port, opts.host.replace(/^\[|\]$/g, ""), onListen)
}
})
// NOTE@jsjoeio: we need to chmod after the server is finished
// listening. Otherwise, the socket may not have been created yet.
if (socket && mode) {
await fs.chmod(socket, mode)
if (isSocketOpts(opts)) {
if (opts["socket-mode"]) {
await fs.chmod(opts.socket, opts["socket-mode"])
}
}
}
@@ -70,14 +80,22 @@ export const createApp = async (args: DefaultedArgs): Promise<App> => {
)
: http.createServer(router)
const dispose = disposer(server)
const disposeServer = disposer(server)
await listen(server, args)
const wsRouter = express()
handleUpgrade(wsRouter, server)
return { router, wsRouter, server, dispose }
const editorSessionManager = new EditorSessionManager()
const editorSessionManagerServer = await makeEditorSessionManagerServer(args["session-socket"], editorSessionManager)
const disposeEditorSessionManagerServer = disposer(editorSessionManagerServer)
const dispose = async () => {
await Promise.all([disposeServer(), disposeEditorSessionManagerServer()])
}
return { router, wsRouter, server, dispose, editorSessionManagerServer }
}
/**

View File

@@ -1,19 +1,9 @@
import { field, Level, logger } from "@coder/logger"
import { promises as fs } from "fs"
import { load } from "js-yaml"
import * as os from "os"
import * as path from "path"
import {
canConnect,
generateCertificate,
generatePassword,
humanPath,
paths,
isNodeJSErrnoException,
splitOnFirstEquals,
} from "./util"
const DEFAULT_SOCKET_PATH = path.join(os.tmpdir(), "vscode-ipc")
import { generateCertificate, generatePassword, paths, splitOnFirstEquals } from "./util"
import { EditorSessionManagerClient } from "./vscodeSocket"
export enum Feature {
// No current experimental features!
@@ -60,6 +50,8 @@ export interface UserProvidedCodeArgs {
"disable-file-downloads"?: boolean
"disable-workspace-trust"?: boolean
"disable-getting-started-override"?: boolean
"disable-proxy"?: boolean
"session-socket"?: string
}
/**
@@ -87,6 +79,7 @@ export interface UserProvidedArgs extends UserProvidedCodeArgs {
"bind-addr"?: string
socket?: string
"socket-mode"?: string
"trusted-origins"?: string[]
version?: boolean
"proxy-domain"?: string[]
"reuse-window"?: boolean
@@ -169,6 +162,9 @@ export const options: Options<Required<UserProvidedArgs>> = {
"Disable update check. Without this flag, code-server checks every 6 hours against the latest github release and \n" +
"then notifies you once every week that a new release is available.",
},
"session-socket": {
type: "string",
},
"disable-file-downloads": {
type: "boolean",
description:
@@ -182,6 +178,10 @@ export const options: Options<Required<UserProvidedArgs>> = {
type: "boolean",
description: "Disable the coder/coder override in the Help: Getting Started page.",
},
"disable-proxy": {
type: "boolean",
description: "Disable domain and path proxy routes.",
},
// --enable can be used to enable experimental features. These features
// provide no guarantees.
enable: { type: "string[]" },
@@ -213,6 +213,11 @@ export const options: Options<Required<UserProvidedArgs>> = {
socket: { type: "string", path: true, description: "Path to a socket (bind-addr will be ignored)." },
"socket-mode": { type: "string", description: "File mode of the socket." },
"trusted-origins": {
type: "string[]",
description:
"Disables authenticate origin check for trusted origin. Useful if not able to access reverse proxy configuration.",
},
version: { type: "boolean", short: "v", description: "Display version information." },
_: { type: "string[]" },
@@ -433,19 +438,23 @@ export const parse = (
throw new Error("--cert-key is missing")
}
logger.debug(() => [
`parsed ${opts?.configFile ? "config" : "command line"}`,
field("args", {
...args,
password: args.password ? "<redacted>" : undefined,
"hashed-password": args["hashed-password"] ? "<redacted>" : undefined,
"github-auth": args["github-auth"] ? "<redacted>" : undefined,
}),
])
logger.debug(() => [`parsed ${opts?.configFile ? "config" : "command line"}`, field("args", redactArgs(args))])
return args
}
/**
* Redact sensitive information from arguments for logging.
*/
export const redactArgs = (args: UserProvidedArgs): UserProvidedArgs => {
return {
...args,
password: args.password ? "<redacted>" : undefined,
"hashed-password": args["hashed-password"] ? "<redacted>" : undefined,
"github-auth": args["github-auth"] ? "<redacted>" : undefined,
}
}
/**
* User-provided arguments with defaults. The distinction between user-provided
* args and defaulted args exists so we can tell the difference between end
@@ -464,6 +473,7 @@ export interface DefaultedArgs extends ConfigArgs {
usingEnvHashedPassword: boolean
"extensions-dir": string
"user-data-dir": string
"session-socket": string
/* Positional arguments. */
_: string[]
}
@@ -484,6 +494,11 @@ export async function setDefaults(cliArgs: UserProvidedArgs, configArgs?: Config
args["extensions-dir"] = path.join(args["user-data-dir"], "extensions")
}
if (!args["session-socket"]) {
args["session-socket"] = path.join(args["user-data-dir"], "code-server-ipc.sock")
}
process.env.CODE_SERVER_SESSION_SOCKET = args["session-socket"]
// --verbose takes priority over --log and --log takes priority over the
// environment variable.
if (args.verbose) {
@@ -553,6 +568,10 @@ export async function setDefaults(cliArgs: UserProvidedArgs, configArgs?: Config
args["disable-getting-started-override"] = true
}
if (process.env.CS_DISABLE_PROXY?.match(/^(1|true)$/)) {
args["disable-proxy"] = true
}
const usingEnvHashedPassword = !!process.env.HASHED_PASSWORD
if (process.env.HASHED_PASSWORD) {
args["hashed-password"] = process.env.HASHED_PASSWORD
@@ -570,14 +589,24 @@ export async function setDefaults(cliArgs: UserProvidedArgs, configArgs?: Config
// Filter duplicate proxy domains and remove any leading `*.`.
const proxyDomains = new Set((args["proxy-domain"] || []).map((d) => d.replace(/^\*\./, "")))
args["proxy-domain"] = Array.from(proxyDomains)
if (args["proxy-domain"].length > 0 && !process.env.VSCODE_PROXY_URI) {
process.env.VSCODE_PROXY_URI = `{{port}}.${args["proxy-domain"][0]}`
const finalProxies = []
for (const proxyDomain of proxyDomains) {
if (!proxyDomain.includes("{{port}}")) {
finalProxies.push("{{port}}." + proxyDomain)
} else {
finalProxies.push(proxyDomain)
}
}
if (typeof args._ === "undefined") {
args._ = []
// all proxies are of format anyprefix-{{port}}-anysuffix.{{host}}, where {{host}} is optional
// e.g. code-8080.domain.tld would match for code-{{port}}.domain.tld and code-{{port}}.{{host}}
if (finalProxies.length > 0 && !process.env.VSCODE_PROXY_URI) {
process.env.VSCODE_PROXY_URI = `//${finalProxies[0]}`
}
args["proxy-domain"] = finalProxies
args._ = getResolvedPathsFromArgs(args)
return {
...args,
@@ -586,6 +615,10 @@ export async function setDefaults(cliArgs: UserProvidedArgs, configArgs?: Config
} as DefaultedArgs // TODO: Technically no guarantee this is fulfilled.
}
export function getResolvedPathsFromArgs(args: UserProvidedArgs): string[] {
return (args._ ?? []).map((p) => path.resolve(p))
}
/**
* Helper function to return the default config file.
*
@@ -629,7 +662,7 @@ export async function readConfigFile(configPath?: string): Promise<ConfigArgs> {
await fs.writeFile(configPath, defaultConfigFile(generatedPassword), {
flag: "wx", // wx means to fail if the path exists.
})
logger.info(`Wrote default config file to ${humanPath(os.homedir(), configPath)}`)
logger.info(`Wrote default config file to ${configPath}`)
} catch (error: any) {
// EEXIST is fine; we don't want to overwrite existing configurations.
if (error.code !== "EEXIST") {
@@ -699,6 +732,9 @@ export function bindAddrFromArgs(addr: Addr, args: UserProvidedArgs): Addr {
if (args["bind-addr"]) {
addr = parseBindAddr(args["bind-addr"])
}
if (process.env.CODE_SERVER_HOST) {
addr.host = process.env.CODE_SERVER_HOST
}
if (args.host) {
addr.host = args.host
}
@@ -725,47 +761,38 @@ function bindAddrFromAllSources(...argsConfig: UserProvidedArgs[]): Addr {
return addr
}
/**
* Reads the socketPath based on path passed in.
*
* The one usually passed in is the DEFAULT_SOCKET_PATH.
*
* If it can't read the path, it throws an error and returns undefined.
*/
export async function readSocketPath(path: string): Promise<string | undefined> {
try {
return await fs.readFile(path, "utf8")
} catch (error) {
// If it doesn't exist, we don't care.
// But if it fails for some reason, we should throw.
// We want to surface that to the user.
if (!isNodeJSErrnoException(error) || error.code !== "ENOENT") {
throw error
}
}
return undefined
}
/**
* Determine if it looks like the user is trying to open a file or folder in an
* existing instance. The arguments here should be the arguments the user
* explicitly passed on the command line, *NOT DEFAULTS* or the configuration.
*/
export const shouldOpenInExistingInstance = async (args: UserProvidedArgs): Promise<string | undefined> => {
export const shouldOpenInExistingInstance = async (
args: UserProvidedArgs,
sessionSocket: string,
): Promise<string | undefined> => {
// Always use the existing instance if we're running from VS Code's terminal.
if (process.env.VSCODE_IPC_HOOK_CLI) {
logger.debug("Found VSCODE_IPC_HOOK_CLI")
return process.env.VSCODE_IPC_HOOK_CLI
}
const paths = getResolvedPathsFromArgs(args)
const client = new EditorSessionManagerClient(sessionSocket)
// If these flags are set then assume the user is trying to open in an
// existing instance since these flags have no effect otherwise.
// existing instance since these flags have no effect otherwise. That means
// if there is no existing instance we should error rather than falling back
// to spawning code-server normally.
const openInFlagCount = ["reuse-window", "new-window"].reduce((prev, cur) => {
return args[cur as keyof UserProvidedArgs] ? prev + 1 : prev
}, 0)
if (openInFlagCount > 0) {
logger.debug("Found --reuse-window or --new-window")
return readSocketPath(DEFAULT_SOCKET_PATH)
const socketPath = await client.getConnectedSocketPath(paths[0])
if (!socketPath) {
throw new Error(`No opened code-server instances found to handle ${paths[0]}`)
}
return socketPath
}
// It's possible the user is trying to spawn another instance of code-server.
@@ -773,9 +800,13 @@ export const shouldOpenInExistingInstance = async (args: UserProvidedArgs): Prom
// code-server is invoked exactly like this: `code-server my-file`).
// 2. That a file or directory was passed.
// 3. That the socket is active.
// 4. That an instance exists to handle the path (implied by #3).
if (Object.keys(args).length === 1 && typeof args._ !== "undefined" && args._.length > 0) {
const socketPath = await readSocketPath(DEFAULT_SOCKET_PATH)
if (socketPath && (await canConnect(socketPath))) {
if (!(await client.canConnect())) {
return undefined
}
const socketPath = await client.getConnectedSocketPath(paths[0])
if (socketPath) {
logger.debug("Found existing code-server socket")
return socketPath
}

View File

@@ -51,7 +51,7 @@ async function entry(): Promise<void> {
return runCodeCli(args)
}
const socketPath = await shouldOpenInExistingInstance(cliArgs)
const socketPath = await shouldOpenInExistingInstance(cliArgs, args["session-socket"])
if (socketPath) {
logger.debug("Trying to open in existing instance")
return openInExistingInstance(args, socketPath)

View File

@@ -9,7 +9,10 @@ export class Heart {
private heartbeatInterval = 60000
public lastHeartbeat = 0
public constructor(private readonly heartbeatPath: string, private readonly isActive: () => Promise<boolean>) {
public constructor(
private readonly heartbeatPath: string,
private readonly isActive: () => Promise<boolean>,
) {
this.beat = this.beat.bind(this)
this.alive = this.alive.bind(this)
}

View File

@@ -75,6 +75,25 @@ export const replaceTemplates = <T extends object>(
.replace("{{OPTIONS}}", () => escapeJSON(serverOptions))
}
/**
* Throw an error if proxy is not enabled. Call `next` if provided.
*/
export const ensureProxyEnabled = (req: express.Request, _?: express.Response, next?: express.NextFunction): void => {
if (!proxyEnabled(req)) {
throw new HttpError("Forbidden", HttpCode.Forbidden)
}
if (next) {
next()
}
}
/**
* Return true if proxy is enabled.
*/
export const proxyEnabled = (req: express.Request): boolean => {
return !req.args["disable-proxy"]
}
/**
* Throw an error if not authorized. Call `next` if provided.
*/
@@ -355,6 +374,11 @@ export function authenticateOrigin(req: express.Request): void {
throw new Error(`unable to parse malformed origin "${originRaw}"`)
}
const trustedOrigins = req.args["trusted-origins"] || []
if (trustedOrigins.includes(origin) || trustedOrigins.includes("*")) {
return
}
const host = getHost(req)
if (typeof host === "undefined") {
// A missing host likely means the reverse proxy has not been configured to
@@ -373,7 +397,7 @@ export function authenticateOrigin(req: express.Request): void {
/**
* Get the host from headers. It will be trimmed and lowercased.
*/
function getHost(req: express.Request): string | undefined {
export function getHost(req: express.Request): string | undefined {
// Honor Forwarded if present.
const forwardedRaw = getFirstHeader(req, "forwarded")
if (forwardedRaw) {
@@ -386,10 +410,14 @@ function getHost(req: express.Request): string | undefined {
}
}
// Honor X-Forwarded-Host if present.
// Honor X-Forwarded-Host if present. Some reverse proxies will set multiple
// comma-separated hosts.
const xHost = getFirstHeader(req, "x-forwarded-host")
if (xHost) {
return xHost.trim().toLowerCase()
const firstXHost = xHost.split(",")[0]
if (firstXHost) {
return firstXHost.trim().toLowerCase()
}
}
const host = getFirstHeader(req, "host")

View File

@@ -1,7 +1,10 @@
import i18next, { init } from "i18next"
import * as en from "./locales/en.json"
import * as zhCn from "./locales/zh-cn.json"
import * as ja from "./locales/ja.json"
import * as th from "./locales/th.json"
import * as ur from "./locales/ur.json"
import * as zhCn from "./locales/zh-cn.json"
init({
lng: "en",
fallbackLng: "en", // language to use if translations in user language are not available.
@@ -18,6 +21,12 @@ init({
th: {
translation: th,
},
ja: {
translation: ja,
},
ur: {
translation: ur,
},
},
})

View File

@@ -0,0 +1,13 @@
{
"LOGIN_TITLE": "{{app}} ログイン",
"LOGIN_BELOW": "以下によりログインしてください。",
"WELCOME": "ようこそ {{app}} へ!",
"LOGIN_PASSWORD": "パスワードは設定ファイル( {{configFile}} )を確認してください。",
"LOGIN_USING_ENV_PASSWORD": "パスワードは環境変数 $PASSWORD で設定されています。",
"LOGIN_USING_HASHED_PASSWORD": "パスワードは環境変数 $HASHED_PASSWORD で設定されています。",
"SUBMIT": "実行",
"PASSWORD_PLACEHOLDER": "パスワード",
"LOGIN_RATE_LIMIT": "ログイン制限を超えました!",
"MISS_PASSWORD": "パスワードを入力してください。",
"INCORRECT_PASSWORD": "パスワードが間違っています。"
}

View File

@@ -0,0 +1,13 @@
{
"LOGIN_TITLE": "{{app}} لاگ ان کریں",
"LOGIN_BELOW": "براہ کرم نیچے لاگ ان کریں۔",
"WELCOME": "میں خوش آمدید {{app}}",
"LOGIN_PASSWORD": "پاس ورڈ کے لیے {{configFile}} پر کنفگ فائل چیک کریں۔",
"LOGIN_USING_ENV_PASSWORD": "پاس ورڈ $PASSWORD سے سیٹ کیا گیا تھا۔",
"LOGIN_USING_HASHED_PASSWORD": "پاس ورڈ $HASHED_PASSWORD سے سیٹ کیا گیا تھا۔",
"SUBMIT": "جمع کرائیں",
"PASSWORD_PLACEHOLDER": "پاس ورڈ",
"LOGIN_RATE_LIMIT": "لاگ ان کی شرح محدود!",
"MISS_PASSWORD": "پاس ورڈ غائب ہے۔",
"INCORRECT_PASSWORD": "غلط پاس ورڈ"
}

View File

@@ -1,14 +1,12 @@
import { field, logger } from "@coder/logger"
import http from "http"
import * as os from "os"
import path from "path"
import { Disposable } from "../common/emitter"
import { plural } from "../common/util"
import { createApp, ensureAddress } from "./app"
import { AuthType, DefaultedArgs, Feature, SpawnCodeCli, toCodeArgs, UserProvidedArgs } from "./cli"
import { commit, version } from "./constants"
import { register } from "./routes"
import { humanPath, isDirectory, loadAMDModule, open } from "./util"
import { isDirectory, loadAMDModule, open } from "./util"
/**
* Return true if the user passed an extension-related VS Code flag.
@@ -70,9 +68,8 @@ export const openInExistingInstance = async (args: DefaultedArgs, socketPath: st
forceNewWindow: args["new-window"],
gotoLineMode: true,
}
const paths = args._ || []
for (let i = 0; i < paths.length; i++) {
const fp = path.resolve(paths[i])
for (let i = 0; i < args._.length; i++) {
const fp = args._[i]
if (await isDirectory(fp)) {
pipeArgs.folderURIs.push(fp)
} else {
@@ -111,8 +108,8 @@ export const runCodeServer = async (
): Promise<{ dispose: Disposable["dispose"]; server: http.Server }> => {
logger.info(`code-server ${version} ${commit}`)
logger.info(`Using user-data-dir ${humanPath(os.homedir(), args["user-data-dir"])}`)
logger.trace(`Using extensions-dir ${humanPath(os.homedir(), args["extensions-dir"])}`)
logger.info(`Using user-data-dir ${args["user-data-dir"]}`)
logger.trace(`Using extensions-dir ${args["extensions-dir"]}`)
if (args.auth === AuthType.Password && !args.password && !args["hashed-password"]) {
throw new Error(
@@ -125,9 +122,8 @@ export const runCodeServer = async (
const serverAddress = ensureAddress(app.server, protocol)
const disposeRoutes = await register(app, args)
logger.info(`Using config file ${humanPath(os.homedir(), args.config)}`)
logger.info(`Using config file ${args.config}`)
logger.info(`${protocol.toUpperCase()} server listening on ${serverAddress.toString()}`)
if (args.auth === AuthType.Password) {
logger.info(" - Authentication is enabled")
if (args.usingEnvPassword) {
@@ -135,21 +131,31 @@ export const runCodeServer = async (
} else if (args.usingEnvHashedPassword) {
logger.info(" - Using password from $HASHED_PASSWORD")
} else {
logger.info(` - Using password from ${humanPath(os.homedir(), args.config)}`)
logger.info(` - Using password from ${args.config}`)
}
} else {
logger.info(" - Authentication is disabled")
}
if (args.cert) {
logger.info(` - Using certificate for HTTPS: ${humanPath(os.homedir(), args.cert.value)}`)
logger.info(` - Using certificate for HTTPS: ${args.cert.value}`)
} else {
logger.info(" - Not serving HTTPS")
}
if (args["proxy-domain"].length > 0) {
if (args["disable-proxy"]) {
logger.info(" - Proxy disabled")
} else if (args["proxy-domain"].length > 0) {
logger.info(` - ${plural(args["proxy-domain"].length, "Proxying the following domain")}:`)
args["proxy-domain"].forEach((domain) => logger.info(` - *.${domain}`))
args["proxy-domain"].forEach((domain) => logger.info(` - ${domain}`))
}
if (process.env.VSCODE_PROXY_URI) {
logger.info(`Using proxy URI in PORTS tab: ${process.env.VSCODE_PROXY_URI}`)
}
const sessionServerAddress = app.editorSessionManagerServer.address()
if (sessionServerAddress) {
logger.info(`Session server listening on ${sessionServerAddress.toString()}`)
}
if (args.enable && args.enable.length > 0) {

View File

@@ -1,34 +1,56 @@
import { Request, Router } from "express"
import { HttpCode, HttpError } from "../../common/http"
import { authenticated, ensureAuthenticated, ensureOrigin, redirect, self } from "../http"
import { getHost, ensureProxyEnabled, authenticated, ensureAuthenticated, ensureOrigin, redirect, self } from "../http"
import { proxy } from "../proxy"
import { Router as WsRouter } from "../wsRouter"
export const router = Router()
const proxyDomainToRegex = (matchString: string): RegExp => {
const escapedMatchString = matchString.replace(/[.*+?^$()|[\]\\]/g, "\\$&")
// Replace {{port}} with a regex group to capture the port
// Replace {{host}} with .+ to allow any host match (so rely on DNS record here)
let regexString = escapedMatchString.replace("{{port}}", "(\\d+)")
regexString = regexString.replace("{{host}}", ".+")
regexString = regexString.replace(/[{}]/g, "\\$&") //replace any '{}' that might be left
return new RegExp("^" + regexString + "$")
}
let proxyRegexes: RegExp[] = []
const proxyDomainsToRegex = (proxyDomains: string[]): RegExp[] => {
if (proxyDomains.length !== proxyRegexes.length) {
proxyRegexes = proxyDomains.map(proxyDomainToRegex)
}
return proxyRegexes
}
/**
* Return the port if the request should be proxied. Anything that ends in a
* proxy domain and has a *single* subdomain should be proxied. Anything else
* should return `undefined` and will be handled as normal.
* Return the port if the request should be proxied.
*
* The proxy-domain should be of format anyprefix-{{port}}-anysuffix.{{host}}, where {{host}} is optional
* e.g. code-8080.domain.tld would match for code-{{port}}.domain.tld and code-{{port}}.{{host}}.
*
* For example if `coder.com` is specified `8080.coder.com` will be proxied
* but `8080.test.coder.com` and `test.8080.coder.com` will not.
*/
const maybeProxy = (req: Request): string | undefined => {
// Split into parts.
const host = req.headers.host || ""
const idx = host.indexOf(":")
const domain = idx !== -1 ? host.substring(0, idx) : host
const parts = domain.split(".")
// There must be an exact match.
const port = parts.shift()
const proxyDomain = parts.join(".")
if (!port || !req.args["proxy-domain"].includes(proxyDomain)) {
const reqDomain = getHost(req)
if (reqDomain === undefined) {
return undefined
}
return port
const regexs = proxyDomainsToRegex(req.args["proxy-domain"])
for (const regex of regexs) {
const match = reqDomain.match(regex)
if (match) {
return match[1] // match[1] contains the port
}
}
return undefined
}
router.all("*", async (req, res, next) => {
@@ -37,6 +59,8 @@ router.all("*", async (req, res, next) => {
return next()
}
ensureProxyEnabled(req)
// Must be authenticated to use the proxy.
const isAuthenticated = await authenticated(req)
if (!isAuthenticated) {
@@ -78,6 +102,8 @@ wsRouter.ws("*", async (req, _, next) => {
if (!port) {
return next()
}
ensureProxyEnabled(req)
ensureOrigin(req)
await ensureAuthenticated(req)
proxy.ws(req, req.ws, req.head, {

View File

@@ -62,12 +62,16 @@ export const errorHandler: express.ErrorRequestHandler = async (err, req, res, n
}
export const wsErrorHandler: express.ErrorRequestHandler = async (err, req, res, next) => {
logger.error(`${err.message} ${err.stack}`)
let statusCode = 500
if (errorHasStatusCode(err)) {
statusCode = err.statusCode
} else if (errorHasCode(err) && notFoundCodes.includes(err.code)) {
statusCode = HttpCode.NotFound
}
if (statusCode >= 500) {
logger.error(`${err.message} ${err.stack}`)
} else {
logger.debug(`${err.message} ${err.stack}`)
}
;(req as WebsocketRequest).ws.end(`HTTP/1.1 ${statusCode} ${err.message}\r\n\r\n`)
}

View File

@@ -1,13 +1,12 @@
import { Router, Request } from "express"
import { promises as fs } from "fs"
import { RateLimiter as Limiter } from "limiter"
import * as os from "os"
import * as path from "path"
import { CookieKeys } from "../../common/http"
import { rootPath } from "../constants"
import { authenticated, getCookieOptions, redirect, replaceTemplates } from "../http"
import { getPasswordMethod, handlePasswordValidation, humanPath, sanitizeString, escapeHtml } from "../util"
import i18n from "../i18n"
import { getPasswordMethod, handlePasswordValidation, sanitizeString, escapeHtml } from "../util"
// RateLimiter wraps around the limiter library for logins.
// It allows 2 logins every minute plus 12 logins every hour.
@@ -33,7 +32,7 @@ const getRoot = async (req: Request, error?: Error): Promise<string> => {
i18n.changeLanguage(locale)
const appName = req.args["app-name"] || "code-server"
const welcomeText = req.args["welcome-text"] || (i18n.t("WELCOME", { app: appName }) as string)
let passwordMsg = i18n.t("LOGIN_PASSWORD", { configFile: humanPath(os.homedir(), req.args.config) })
let passwordMsg = i18n.t("LOGIN_PASSWORD", { configFile: req.args.config })
if (req.args.usingEnvPassword) {
passwordMsg = i18n.t("LOGIN_USING_ENV_PASSWORD")
} else if (req.args.usingEnvHashedPassword) {

View File

@@ -3,7 +3,7 @@ import * as path from "path"
import * as qs from "qs"
import * as pluginapi from "../../../typings/pluginapi"
import { HttpCode, HttpError } from "../../common/http"
import { authenticated, ensureAuthenticated, ensureOrigin, redirect, self } from "../http"
import { ensureProxyEnabled, authenticated, ensureAuthenticated, ensureOrigin, redirect, self } from "../http"
import { proxy as _proxy } from "../proxy"
const getProxyTarget = (req: Request, passthroughPath?: boolean): string => {
@@ -11,7 +11,7 @@ const getProxyTarget = (req: Request, passthroughPath?: boolean): string => {
return `http://0.0.0.0:${req.params.port}/${req.originalUrl}`
}
const query = qs.stringify(req.query)
return `http://0.0.0.0:${req.params.port}/${req.params[0] || ""}${query ? `?${query}` : ""}`
return encodeURI(`http://0.0.0.0:${req.params.port}${req.params[0] || ""}${query ? `?${query}` : ""}`)
}
export async function proxy(
@@ -21,6 +21,8 @@ export async function proxy(
passthroughPath?: boolean
},
): Promise<void> {
ensureProxyEnabled(req)
if (!(await authenticated(req))) {
// If visiting the root (/:port only) redirect to the login page.
if (!req.params[0] || req.params[0] === "/") {
@@ -50,6 +52,7 @@ export async function wsProxy(
passthroughPath?: boolean
},
): Promise<void> {
ensureProxyEnabled(req)
ensureOrigin(req)
await ensureAuthenticated(req)
_proxy.ws(req, req.ws, req.head, {

View File

@@ -1,5 +1,7 @@
import { logger } from "@coder/logger"
import * as crypto from "crypto"
import * as express from "express"
import { promises as fs } from "fs"
import * as http from "http"
import * as net from "net"
import * as path from "path"
@@ -32,6 +34,7 @@ export class CodeServerRouteWrapper {
private _wsRouterWrapper = WsRouter()
private _socketProxyProvider = new SocketProxyProvider()
public router = express.Router()
private mintKeyPromise: Promise<Buffer> | undefined
public get wsRouter() {
return this._wsRouterWrapper.router
@@ -66,6 +69,33 @@ export class CodeServerRouteWrapper {
)
}
private mintKey: express.Handler = async (req, res, next) => {
if (!this.mintKeyPromise) {
this.mintKeyPromise = new Promise(async (resolve) => {
const keyPath = path.join(req.args["user-data-dir"], "serve-web-key-half")
logger.debug(`Reading server web key half from ${keyPath}`)
try {
resolve(await fs.readFile(keyPath))
return
} catch (error: any) {
if (error.code !== "ENOENT") {
logError(logger, `read ${keyPath}`, error)
}
}
// VS Code wants 256 bits.
const key = crypto.randomBytes(32)
try {
await fs.writeFile(keyPath, key)
} catch (error: any) {
logError(logger, `write ${keyPath}`, error)
}
resolve(key)
})
}
const key = await this.mintKeyPromise
res.end(key)
}
private $root: express.Handler = async (req, res, next) => {
const isAuthenticated = await authenticated(req)
const NO_FOLDER_OR_WORKSPACE_QUERY = !req.query.folder && !req.query.workspace
@@ -173,6 +203,7 @@ export class CodeServerRouteWrapper {
constructor() {
this.router.get("/", this.ensureCodeServerLoaded, this.$root)
this.router.get("/manifest.json", this.manifest)
this.router.post("/mint-key", this.mintKey)
this.router.all("*", ensureAuthenticated, this.ensureCodeServerLoaded, this.$proxyRequest)
this._wsRouterWrapper.ws("*", ensureOrigin, ensureAuthenticated, this.ensureCodeServerLoaded, this.$proxyWebsocket)
}

View File

@@ -1,7 +1,7 @@
import { field, logger } from "@coder/logger"
import * as http from "http"
import * as https from "https"
import ProxyAgent from "proxy-agent"
import { ProxyAgent } from "proxy-agent"
import * as semver from "semver"
import * as url from "url"
import { httpProxyUri, version } from "./constants"
@@ -104,7 +104,9 @@ export class UpdateProvider {
const request = (uri: string): void => {
logger.debug("Making request", field("uri", uri))
const isHttps = uri.startsWith("https")
const agent = httpProxyUri ? new ProxyAgent(httpProxyUri) : undefined
const agent = new ProxyAgent({
getProxyForUrl: () => httpProxyUri || "",
})
const httpx = isHttps ? https : http
const client = httpx.get(uri, { headers: { "User-Agent": "code-server" }, agent }, (response) => {
if (!response.statusCode || response.statusCode < 200 || response.statusCode >= 400) {

View File

@@ -87,20 +87,6 @@ export function getEnvPaths(platform = process.platform): Paths {
}
}
/**
* humanPath replaces the home directory in path with ~.
* Makes it more readable.
*
* @param homedir - the home directory(i.e. `os.homedir()`)
* @param path - a file path
*/
export function humanPath(homedir: string, path?: string): string {
if (!path) {
return ""
}
return path.replace(homedir, "~")
}
export const generateCertificate = async (hostname: string): Promise<{ cert: string; certKey: string }> => {
const certPath = path.join(paths.data, `${hostname.replace(/\./g, "_")}.crt`)
const certKeyPath = path.join(paths.data, `${hostname.replace(/\./g, "_")}.key`)

206
src/node/vscodeSocket.ts Normal file
View File

@@ -0,0 +1,206 @@
import { logger } from "@coder/logger"
import express from "express"
import * as http from "http"
import * as path from "path"
import { HttpCode } from "../common/http"
import { listen } from "./app"
import { canConnect } from "./util"
export interface EditorSessionEntry {
workspace: {
id: string
folders: {
uri: {
path: string
}
}[]
}
socketPath: string
}
interface DeleteSessionRequest {
socketPath: string
}
interface AddSessionRequest {
entry: EditorSessionEntry
}
interface GetSessionResponse {
socketPath?: string
}
export async function makeEditorSessionManagerServer(
codeServerSocketPath: string,
editorSessionManager: EditorSessionManager,
): Promise<http.Server> {
const router = express()
// eslint-disable-next-line import/no-named-as-default-member
router.use(express.json())
router.get("/session", async (req, res) => {
const filePath = req.query.filePath as string
if (!filePath) {
res.status(HttpCode.BadRequest).send("filePath is required")
return
}
try {
const socketPath = await editorSessionManager.getConnectedSocketPath(filePath)
const response: GetSessionResponse = { socketPath }
res.json(response)
} catch (error: unknown) {
res.status(HttpCode.ServerError).send(error)
}
})
router.post("/add-session", async (req, res) => {
const request = req.body as AddSessionRequest
if (!request.entry) {
res.status(400).send("entry is required")
}
editorSessionManager.addSession(request.entry)
res.status(200).send()
})
router.post("/delete-session", async (req, res) => {
const request = req.body as DeleteSessionRequest
if (!request.socketPath) {
res.status(400).send("socketPath is required")
}
editorSessionManager.deleteSession(request.socketPath)
res.status(200).send()
})
const server = http.createServer(router)
try {
await listen(server, { socket: codeServerSocketPath })
} catch (e) {
logger.warn(`Could not create socket at ${codeServerSocketPath}`)
}
return server
}
export class EditorSessionManager {
// Map from socket path to EditorSessionEntry.
private entries = new Map<string, EditorSessionEntry>()
addSession(entry: EditorSessionEntry): void {
logger.debug(`Adding session to session registry: ${entry.socketPath}`)
this.entries.set(entry.socketPath, entry)
}
getCandidatesForFile(filePath: string): EditorSessionEntry[] {
const matchCheckResults = new Map<string, boolean>()
const checkMatch = (entry: EditorSessionEntry): boolean => {
if (matchCheckResults.has(entry.socketPath)) {
return matchCheckResults.get(entry.socketPath)!
}
const result = entry.workspace.folders.some((folder) => filePath.startsWith(folder.uri.path + path.sep))
matchCheckResults.set(entry.socketPath, result)
return result
}
return Array.from(this.entries.values())
.reverse() // Most recently registered first.
.sort((a, b) => {
// Matches first.
const aMatch = checkMatch(a)
const bMatch = checkMatch(b)
if (aMatch === bMatch) {
return 0
}
if (aMatch) {
return -1
}
return 1
})
}
deleteSession(socketPath: string): void {
logger.debug(`Deleting session from session registry: ${socketPath}`)
this.entries.delete(socketPath)
}
/**
* Returns the best socket path that we can connect to.
* We also delete any sockets that we can't connect to.
*/
async getConnectedSocketPath(filePath: string): Promise<string | undefined> {
const candidates = this.getCandidatesForFile(filePath)
let match: EditorSessionEntry | undefined = undefined
for (const candidate of candidates) {
if (await canConnect(candidate.socketPath)) {
match = candidate
break
}
this.deleteSession(candidate.socketPath)
}
return match?.socketPath
}
}
export class EditorSessionManagerClient {
constructor(private codeServerSocketPath: string) {}
async canConnect() {
return canConnect(this.codeServerSocketPath)
}
async getConnectedSocketPath(filePath: string): Promise<string | undefined> {
const response = await new Promise<GetSessionResponse>((resolve, reject) => {
const opts = {
path: "/session?filePath=" + encodeURIComponent(filePath),
socketPath: this.codeServerSocketPath,
method: "GET",
}
const req = http.request(opts, (res) => {
let rawData = ""
res.setEncoding("utf8")
res.on("data", (chunk) => {
rawData += chunk
})
res.on("end", () => {
try {
const obj = JSON.parse(rawData)
if (res.statusCode === 200) {
resolve(obj)
} else {
reject(new Error("Unexpected status code: " + res.statusCode))
}
} catch (e: unknown) {
reject(e)
}
})
})
req.on("error", reject)
req.end()
})
return response.socketPath
}
// Currently only used for tests.
async addSession(request: AddSessionRequest): Promise<void> {
await new Promise<void>((resolve, reject) => {
const opts = {
path: "/add-session",
socketPath: this.codeServerSocketPath,
method: "POST",
headers: {
"content-type": "application/json",
accept: "application/json",
},
}
const req = http.request(opts, () => {
resolve()
})
req.on("error", reject)
req.write(JSON.stringify(request))
req.end()
})
}
}

View File

@@ -3,7 +3,7 @@ import * as cp from "child_process"
import * as path from "path"
import * as rfs from "rotating-file-stream"
import { Emitter } from "../common/emitter"
import { DefaultedArgs } from "./cli"
import { DefaultedArgs, redactArgs } from "./cli"
import { paths } from "./util"
const timeoutInterval = 10000 // 10s, matches VS Code's timeouts.
@@ -44,10 +44,11 @@ export function onMessage<M, T extends M>(
}
const onMessage = (message: M) => {
;(customLogger || logger).trace("got message", field("message", message))
if (fn(message)) {
cleanup()
resolve(message)
} else {
;(customLogger || logger).debug("got unhandled message", field("message", message))
}
}
@@ -77,7 +78,10 @@ type ChildMessage = RelaunchMessage | ChildHandshakeMessage
type ParentMessage = ParentHandshakeMessage
class ProcessError extends Error {
public constructor(message: string, public readonly code: number | undefined) {
public constructor(
message: string,
public readonly code: number | undefined,
) {
super(message)
this.name = this.constructor.name
Error.captureStackTrace(this, this.constructor)
@@ -172,6 +176,7 @@ export class ChildProcess extends Process {
* Initiate the handshake and wait for a response from the parent.
*/
public async handshake(): Promise<DefaultedArgs> {
this.logger.debug("initiating handshake")
this.send({ type: "handshake" })
const message = await onMessage<ParentMessage, ParentHandshakeMessage>(
process,
@@ -180,6 +185,13 @@ export class ChildProcess extends Process {
},
this.logger,
)
this.logger.debug(
"got message",
field("message", {
type: message.type,
args: redactArgs(message.args),
}),
)
return message.args
}
@@ -280,6 +292,10 @@ export class ParentProcess extends Process {
}
public start(args: DefaultedArgs): Promise<void> {
// Our logger was created before we parsed CLI arguments so update the level
// in case it has changed.
this.logger.level = logger.level
// Store for relaunches.
this.args = args
if (!this.started) {
@@ -306,7 +322,7 @@ export class ParentProcess extends Process {
})
}
this.logger.debug(`spawned inner process ${child.pid}`)
this.logger.debug(`spawned child process ${child.pid}`)
await this.handshake(child)
@@ -334,13 +350,14 @@ export class ParentProcess extends Process {
if (!this.args) {
throw new Error("started without args")
}
await onMessage<ChildMessage, ChildHandshakeMessage>(
const message = await onMessage<ChildMessage, ChildHandshakeMessage>(
child,
(message): message is ChildHandshakeMessage => {
return message.type === "handshake"
},
this.logger,
)
this.logger.debug("got message", field("message", message))
this.send(child, { type: "handshake", args: this.args })
}

View File

@@ -117,40 +117,26 @@ export class CodeServer {
* directories.
*/
private async spawn(): Promise<CodeServerProcess> {
// This will be used both as the workspace and data directory to ensure
// instances don't bleed into each other.
const dir = await this.createWorkspace()
const args = await this.argsWithDefaults([
"--auth",
"none",
// The workspace to open.
...(this.args.includes("--ignore-last-opened") ? [] : [dir]),
...this.args,
// Using port zero will spawn on a random port.
"--bind-addr",
"127.0.0.1:0",
])
return new Promise((resolve, reject) => {
const args = [
this.entry,
"--extensions-dir",
path.join(dir, "extensions"),
"--auth",
"none",
// The workspace to open.
...(this.args.includes("--ignore-last-opened") ? [] : [dir]),
...this.args,
// Using port zero will spawn on a random port.
"--bind-addr",
"127.0.0.1:0",
// Setting the XDG variables would be easier and more thorough but the
// modules we import ignores those variables for non-Linux operating
// systems so use these flags instead.
"--config",
path.join(dir, "config.yaml"),
"--user-data-dir",
dir,
]
this.logger.debug("spawning `node " + args.join(" ") + "`")
const proc = cp.spawn("node", args, {
cwd: path.join(__dirname, "../../.."),
env: {
...process.env,
...this.env,
// Set to empty string to prevent code-server from
// using the existing instance when running the e2e tests
// from an integrated terminal.
// Prevent code-server from using the existing instance when running
// the e2e tests from an integrated terminal.
VSCODE_IPC_HOOK_CLI: "",
PASSWORD,
},
@@ -173,11 +159,15 @@ export class CodeServer {
reject(error)
})
// Tracks when the HTTP and session servers are ready.
let httpAddress: string | undefined
let sessionAddress: string | undefined
let resolved = false
proc.stdout.setEncoding("utf8")
onLine(proc, (line) => {
// As long as we are actively getting input reset the timer. If we stop
// getting input and still have not found the address the timer will
// getting input and still have not found the addresses the timer will
// reject.
timer.reset()
@@ -186,20 +176,69 @@ export class CodeServer {
if (resolved) {
return
}
const match = line.trim().match(/HTTPS? server listening on (https?:\/\/[.:\d]+)\/?$/)
let match = line.trim().match(/HTTPS? server listening on (https?:\/\/[.:\d]+)\/?$/)
if (match) {
// Cookies don't seem to work on IP address so swap to localhost.
// Cookies don't seem to work on IP addresses so swap to localhost.
// TODO: Investigate whether this is a bug with code-server.
const address = match[1].replace("127.0.0.1", "localhost")
this.logger.debug(`spawned on ${address}`)
httpAddress = match[1].replace("127.0.0.1", "localhost")
}
match = line.trim().match(/Session server listening on (.+)$/)
if (match) {
sessionAddress = match[1]
}
if (typeof httpAddress !== "undefined" && typeof sessionAddress !== "undefined") {
resolved = true
timer.dispose()
resolve({ process: proc, address })
this.logger.debug(`code-server is ready: ${httpAddress} ${sessionAddress}`)
resolve({ process: proc, address: httpAddress })
}
})
})
}
/**
* Execute a short-lived command.
*/
async run(args: string[]): Promise<void> {
args = await this.argsWithDefaults(args)
this.logger.debug("executing `node " + args.join(" ") + "`")
await util.promisify(cp.exec)("node " + args.join(" "), {
cwd: path.join(__dirname, "../../.."),
env: {
...process.env,
...this.env,
// Prevent code-server from using the existing instance when running
// the e2e tests from an integrated terminal.
VSCODE_IPC_HOOK_CLI: "",
},
})
}
/**
* Combine arguments with defaults.
*/
private async argsWithDefaults(args: string[]): Promise<string[]> {
// This will be used both as the workspace and data directory to ensure
// instances don't bleed into each other.
const dir = await this.workspaceDir
return [
this.entry,
"--extensions-dir",
path.join(dir, "extensions"),
...args,
// Setting the XDG variables would be easier and more thorough but the
// modules we import ignores those variables for non-Linux operating
// systems so use these flags instead.
"--config",
path.join(dir, "config.yaml"),
"--user-data-dir",
dir,
]
}
/**
* Close the code-server process.
*/
@@ -230,7 +269,10 @@ export class CodeServer {
export class CodeServerPage {
private readonly editorSelector = "div.monaco-workbench"
constructor(private readonly codeServer: CodeServer, public readonly page: Page) {
constructor(
private readonly codeServer: CodeServer,
public readonly page: Page,
) {
this.page.on("console", (message) => {
this.codeServer.logger.debug(message.text())
})
@@ -364,6 +406,13 @@ export class CodeServerPage {
await this.waitForTab(file)
}
/**
* Open a file through an external command.
*/
async openFileExternally(file: string) {
await this.codeServer.run(["--reuse-window", file])
}
/**
* Wait for a tab to open for the specified file.
*/

View File

@@ -1,5 +1,5 @@
import { describe, test, expect } from "./baseFixture"
import { clean, getMaybeProxiedPathname } from "../utils/helpers"
import { describe, test, expect } from "./baseFixture"
const routes = ["/", "/vscode", "/vscode/"]
@@ -103,7 +103,7 @@ describe("VS Code Routes with no workspace or folder", ["--disable-workspace-tru
// Closing the folder should stop the redirecting.
await codeServerPage.navigate("/?ew=true")
let url = new URL(codeServerPage.page.url())
const url = new URL(codeServerPage.page.url())
const pathname = getMaybeProxiedPathname(url)
expect(pathname).toBe("/")
expect(url.search).toBe("?ew=true")

View File

@@ -35,13 +35,19 @@ describe("Integrated Terminal", ["--disable-workspace-trust"], {}, () => {
const tmpFolderPath = await tmpdir(testName)
const tmpFile = path.join(tmpFolderPath, "test-file")
await fs.writeFile(tmpFile, "test")
const fileName = path.basename(tmpFile)
await codeServerPage.focusTerminal()
await codeServerPage.page.keyboard.type(`code-server ${tmpFile}`)
await codeServerPage.page.keyboard.press("Enter")
await codeServerPage.waitForTab(fileName)
await codeServerPage.waitForTab(path.basename(tmpFile))
const externalTmpFile = path.join(tmpFolderPath, "test-external-file")
await fs.writeFile(externalTmpFile, "foobar")
await codeServerPage.openFileExternally(externalTmpFile)
await codeServerPage.waitForTab(path.basename(externalTmpFile))
})
})

View File

@@ -12,7 +12,7 @@ describe("--install-extension", () => {
setupFlags = ["--extensions-dir", tempDir]
})
it("should use EXTENSIONS_GALLERY when set", async () => {
const extName = `author.extension-1.0.0`
const extName = "author.extension"
const { stderr } = await runCodeServerCommand([...setupFlags, "--install-extension", extName], {
EXTENSIONS_GALLERY: "{}",
})

View File

@@ -17,7 +17,7 @@ describe("createApp", () => {
beforeAll(async () => {
mockLogger()
const testName = "unlink-socket"
const testName = "app"
await clean(testName)
tmpDirPath = await tmpdir(testName)
tmpFilePath = path.join(tmpDirPath, "unlink-socket-file")
@@ -92,7 +92,7 @@ describe("createApp", () => {
app.dispose()
}
expect(() => masterBall()).rejects.toThrow(`listen EACCES: permission denied 127.0.0.1:${port}`)
expect(() => masterBall()).rejects.toThrow("listen EACCES: permission denied")
})
it("should unlink a socket before listening on the socket", async () => {
@@ -103,7 +103,7 @@ describe("createApp", () => {
const app = await createApp(defaultArgs)
expect(unlinkSpy).toHaveBeenCalledTimes(1)
expect(unlinkSpy).toHaveBeenCalledWith(tmpFilePath)
app.dispose()
})

View File

@@ -1,14 +1,11 @@
import { Level, logger } from "@coder/logger"
import { promises as fs } from "fs"
import * as net from "net"
import * as os from "os"
import * as path from "path"
import {
UserProvidedArgs,
bindAddrFromArgs,
defaultConfigFile,
parse,
readSocketPath,
setDefaults,
shouldOpenInExistingInstance,
toCodeArgs,
@@ -20,7 +17,12 @@ import {
} from "../../../src/node/cli"
import { shouldSpawnCliProcess } from "../../../src/node/main"
import { generatePassword, paths } from "../../../src/node/util"
import { clean, useEnv, tmpdir } from "../../utils/helpers"
import {
EditorSessionManager,
EditorSessionManagerClient,
makeEditorSessionManagerServer,
} from "../../../src/node/vscodeSocket"
import { clean, useEnv, tmpdir, listenOn } from "../../utils/helpers"
// The parser should not set any defaults so the caller can determine what
// values the user actually set. These are only set after explicitly calling
@@ -34,6 +36,7 @@ const defaults = {
usingEnvHashedPassword: false,
"extensions-dir": path.join(paths.data, "extensions"),
"user-data-dir": paths.data,
"session-socket": path.join(paths.data, "code-server-ipc.sock"),
_: [],
}
@@ -44,6 +47,7 @@ describe("parser", () => {
delete process.env.CS_DISABLE_FILE_DOWNLOADS
delete process.env.CS_DISABLE_GETTING_STARTED_OVERRIDE
delete process.env.VSCODE_PROXY_URI
delete process.env.CS_DISABLE_PROXY
console.log = jest.fn()
})
@@ -100,6 +104,10 @@ describe("parser", () => {
"--disable-getting-started-override",
"--disable-proxy",
["--session-socket", "/tmp/override-code-server-ipc-socket"],
["--host", "0.0.0.0"],
"4",
"--",
@@ -118,6 +126,7 @@ describe("parser", () => {
},
"disable-file-downloads": true,
"disable-getting-started-override": true,
"disable-proxy": true,
enable: ["feature1", "feature2"],
help: true,
host: "0.0.0.0",
@@ -133,6 +142,7 @@ describe("parser", () => {
"welcome-text": "welcome to code",
version: true,
"bind-addr": "192.169.0.1:8080",
"session-socket": "/tmp/override-code-server-ipc-socket",
})
})
@@ -386,6 +396,30 @@ describe("parser", () => {
})
})
it("should use env var CS_DISABLE_PROXY", async () => {
process.env.CS_DISABLE_PROXY = "1"
const args = parse([])
expect(args).toEqual({})
const defaultArgs = await setDefaults(args)
expect(defaultArgs).toEqual({
...defaults,
"disable-proxy": true,
})
})
it("should use env var CS_DISABLE_PROXY set to true", async () => {
process.env.CS_DISABLE_PROXY = "true"
const args = parse([])
expect(args).toEqual({})
const defaultArgs = await setDefaults(args)
expect(defaultArgs).toEqual({
...defaults,
"disable-proxy": true,
})
})
it("should error if password passed in", () => {
expect(() => parse(["--password", "supersecret123"])).toThrowError(
"--password can only be set in the config file or passed in via $PASSWORD",
@@ -413,7 +447,7 @@ describe("parser", () => {
const defaultArgs = await setDefaults(args)
expect(defaultArgs).toEqual({
...defaults,
"proxy-domain": ["coder.com", "coder.org"],
"proxy-domain": ["{{port}}.coder.com", "{{port}}.coder.org"],
})
})
it("should allow '=,$/' in strings", async () => {
@@ -466,14 +500,14 @@ describe("parser", () => {
it("should set proxy uri", async () => {
await setDefaults(parse(["--proxy-domain", "coder.org"]))
expect(process.env.VSCODE_PROXY_URI).toEqual("{{port}}.coder.org")
expect(process.env.VSCODE_PROXY_URI).toEqual("//{{port}}.coder.org")
})
it("should set proxy uri to first domain", async () => {
await setDefaults(
parse(["--proxy-domain", "*.coder.com", "--proxy-domain", "coder.com", "--proxy-domain", "coder.org"]),
)
expect(process.env.VSCODE_PROXY_URI).toEqual("{{port}}.coder.com")
expect(process.env.VSCODE_PROXY_URI).toEqual("//{{port}}.coder.com")
})
it("should not override existing proxy uri", async () => {
@@ -487,7 +521,7 @@ describe("parser", () => {
describe("cli", () => {
const testName = "cli"
const vscodeIpcPath = path.join(os.tmpdir(), "vscode-ipc")
let tmpDirPath: string
beforeAll(async () => {
await clean(testName)
@@ -495,68 +529,170 @@ describe("cli", () => {
beforeEach(async () => {
delete process.env.VSCODE_IPC_HOOK_CLI
await fs.rm(vscodeIpcPath, { force: true, recursive: true })
tmpDirPath = await tmpdir(testName)
})
it("should use existing if inside code-server", async () => {
process.env.VSCODE_IPC_HOOK_CLI = "test"
const args: UserProvidedArgs = {}
expect(await shouldOpenInExistingInstance(args)).toStrictEqual("test")
expect(await shouldOpenInExistingInstance(args, "")).toStrictEqual("test")
args.port = 8081
args._ = ["./file"]
expect(await shouldOpenInExistingInstance(args)).toStrictEqual("test")
expect(await shouldOpenInExistingInstance(args, "")).toStrictEqual("test")
})
it("should use existing if --reuse-window is set", async () => {
const sessionSocket = path.join(tmpDirPath, "session-socket")
const server = await makeEditorSessionManagerServer(sessionSocket, new EditorSessionManager())
const args: UserProvidedArgs = {}
args["reuse-window"] = true
await expect(shouldOpenInExistingInstance(args)).resolves.toStrictEqual(undefined)
await expect(shouldOpenInExistingInstance(args, sessionSocket)).rejects.toThrow()
await fs.writeFile(vscodeIpcPath, "test")
await expect(shouldOpenInExistingInstance(args)).resolves.toStrictEqual("test")
const socketPath = path.join(tmpDirPath, "socket")
const client = new EditorSessionManagerClient(sessionSocket)
await client.addSession({
entry: {
workspace: {
id: "aaa",
folders: [
{
uri: {
path: "/aaa",
},
},
],
},
socketPath,
},
})
const vscodeSockets = listenOn(socketPath)
await expect(shouldOpenInExistingInstance(args, sessionSocket)).resolves.toStrictEqual(socketPath)
args.port = 8081
await expect(shouldOpenInExistingInstance(args)).resolves.toStrictEqual("test")
await expect(shouldOpenInExistingInstance(args, sessionSocket)).resolves.toStrictEqual(socketPath)
server.close()
vscodeSockets.close()
})
it("should use existing if --new-window is set", async () => {
const sessionSocket = path.join(tmpDirPath, "session-socket")
const server = await makeEditorSessionManagerServer(sessionSocket, new EditorSessionManager())
const args: UserProvidedArgs = {}
args["new-window"] = true
expect(await shouldOpenInExistingInstance(args)).toStrictEqual(undefined)
await expect(shouldOpenInExistingInstance(args, sessionSocket)).rejects.toThrow()
await fs.writeFile(vscodeIpcPath, "test")
expect(await shouldOpenInExistingInstance(args)).toStrictEqual("test")
const socketPath = path.join(tmpDirPath, "socket")
const client = new EditorSessionManagerClient(sessionSocket)
await client.addSession({
entry: {
workspace: {
id: "aaa",
folders: [
{
uri: {
path: "/aaa",
},
},
],
},
socketPath,
},
})
const vscodeSockets = listenOn(socketPath)
expect(await shouldOpenInExistingInstance(args, sessionSocket)).toStrictEqual(socketPath)
args.port = 8081
expect(await shouldOpenInExistingInstance(args)).toStrictEqual("test")
expect(await shouldOpenInExistingInstance(args, sessionSocket)).toStrictEqual(socketPath)
server.close()
vscodeSockets.close()
})
it("should use existing if no unrelated flags are set, has positional, and socket is active", async () => {
const sessionSocket = path.join(tmpDirPath, "session-socket")
const server = await makeEditorSessionManagerServer(sessionSocket, new EditorSessionManager())
const args: UserProvidedArgs = {}
expect(await shouldOpenInExistingInstance(args)).toStrictEqual(undefined)
expect(await shouldOpenInExistingInstance(args, sessionSocket)).toStrictEqual(undefined)
args._ = ["./file"]
expect(await shouldOpenInExistingInstance(args)).toStrictEqual(undefined)
expect(await shouldOpenInExistingInstance(args, sessionSocket)).toStrictEqual(undefined)
const testDir = await tmpdir(testName)
const socketPath = path.join(testDir, "socket")
await fs.writeFile(vscodeIpcPath, socketPath)
expect(await shouldOpenInExistingInstance(args)).toStrictEqual(undefined)
await new Promise((resolve) => {
const server = net.createServer(() => {
// Close after getting the first connection.
server.close()
})
server.once("listening", () => resolve(server))
server.listen(socketPath)
const client = new EditorSessionManagerClient(sessionSocket)
const socketPath = path.join(tmpDirPath, "socket")
await client.addSession({
entry: {
workspace: {
id: "aaa",
folders: [
{
uri: {
path: "/aaa",
},
},
],
},
socketPath,
},
})
const vscodeSockets = listenOn(socketPath)
expect(await shouldOpenInExistingInstance(args)).toStrictEqual(socketPath)
expect(await shouldOpenInExistingInstance(args, sessionSocket)).toStrictEqual(socketPath)
args.port = 8081
expect(await shouldOpenInExistingInstance(args)).toStrictEqual(undefined)
expect(await shouldOpenInExistingInstance(args, sessionSocket)).toStrictEqual(undefined)
server.close()
vscodeSockets.close()
})
it("should prefer matching sessions for only the first path", async () => {
const sessionSocket = path.join(tmpDirPath, "session-socket")
const server = await makeEditorSessionManagerServer(sessionSocket, new EditorSessionManager())
const client = new EditorSessionManagerClient(sessionSocket)
await client.addSession({
entry: {
workspace: {
id: "aaa",
folders: [
{
uri: {
path: "/aaa",
},
},
],
},
socketPath: `${tmpDirPath}/vscode-ipc-aaa.sock`,
},
})
await client.addSession({
entry: {
workspace: {
id: "bbb",
folders: [
{
uri: {
path: "/bbb",
},
},
],
},
socketPath: `${tmpDirPath}/vscode-ipc-bbb.sock`,
},
})
listenOn(`${tmpDirPath}/vscode-ipc-aaa.sock`, `${tmpDirPath}/vscode-ipc-bbb.sock`)
const args: UserProvidedArgs = {}
args._ = ["/aaa/file", "/bbb/file"]
expect(await shouldOpenInExistingInstance(args, sessionSocket)).toStrictEqual(`${tmpDirPath}/vscode-ipc-aaa.sock`)
server.close()
})
})
@@ -653,6 +789,50 @@ describe("bindAddrFromArgs", () => {
expect(actual).toStrictEqual(expected)
})
it("should use process.env.CODE_SERVER_HOST if set", () => {
const [setValue, resetValue] = useEnv("CODE_SERVER_HOST")
setValue("coder")
const args: UserProvidedArgs = {}
const addr = {
host: "localhost",
port: 8080,
}
const actual = bindAddrFromArgs(addr, args)
const expected = {
host: "coder",
port: 8080,
}
expect(actual).toStrictEqual(expected)
resetValue()
})
it("should use the args.host over process.env.CODE_SERVER_HOST if both set", () => {
const [setValue, resetValue] = useEnv("CODE_SERVER_HOST")
setValue("coder")
const args: UserProvidedArgs = {
host: "123.123.123.123",
}
const addr = {
host: "localhost",
port: 8080,
}
const actual = bindAddrFromArgs(addr, args)
const expected = {
host: "123.123.123.123",
port: 8080,
}
expect(actual).toStrictEqual(expected)
resetValue()
})
it("should use process.env.PORT if set", () => {
const [setValue, resetValue] = useEnv("PORT")
setValue("8000")
@@ -729,44 +909,6 @@ cert: false`)
})
})
describe("readSocketPath", () => {
const fileContents = "readSocketPath file contents"
let tmpDirPath: string
let tmpFilePath: string
const testName = "readSocketPath"
beforeAll(async () => {
await clean(testName)
})
beforeEach(async () => {
tmpDirPath = await tmpdir(testName)
tmpFilePath = path.join(tmpDirPath, "readSocketPath.txt")
await fs.writeFile(tmpFilePath, fileContents)
})
it("should throw an error if it can't read the file", async () => {
// TODO@jsjoeio - implement
// Test it on a directory.... ESDIR
// TODO@jsjoeio - implement
expect(() => readSocketPath(tmpDirPath)).rejects.toThrow("EISDIR")
})
it("should return undefined if it can't read the file", async () => {
// TODO@jsjoeio - implement
const socketPath = await readSocketPath(path.join(tmpDirPath, "not-a-file"))
expect(socketPath).toBeUndefined()
})
it("should return the file contents", async () => {
const contents = await readSocketPath(tmpFilePath)
expect(contents).toBe(fileContents)
})
it("should return the same file contents for two different calls", async () => {
const contents1 = await readSocketPath(tmpFilePath)
const contents2 = await readSocketPath(tmpFilePath)
expect(contents2).toBe(contents1)
})
})
describe("toCodeArgs", () => {
const vscodeDefaults = {
...defaults,

View File

@@ -58,6 +58,7 @@ describe("http", () => {
;[
["host", test.host],
["x-forwarded-host", test.host],
["x-forwarded-host", `${test.host}, ${test.host}`],
["forwarded", `for=127.0.0.1, host=${test.host}, proto=http`],
["forwarded", `for=127.0.0.1;proto=http;host=${test.host}`],
["forwarded", `proto=http;host=${test.host}, for=127.0.0.1`],
@@ -69,6 +70,7 @@ describe("http", () => {
origin: test.origin,
[key]: value,
},
args: {},
})
if (typeof test.expected === "string") {
expect(() => http.authenticateOrigin(req)).toThrow(test.expected)

View File

@@ -43,6 +43,7 @@ describe("plugin", () => {
usingEnvHashedPassword: false,
"extensions-dir": "",
"user-data-dir": "",
"session-socket": "",
}
next()
}

View File

@@ -1,4 +1,3 @@
import * as bodyParser from "body-parser"
import * as express from "express"
import * as http from "http"
import nodeFetch from "node-fetch"
@@ -45,6 +44,17 @@ describe("proxy", () => {
jest.clearAllMocks()
})
it("should return 403 Forbidden if proxy is disabled", async () => {
e.get("/wsup", (req, res) => {
res.json("you cannot see this")
})
codeServer = await integration.setup(["--auth=none", "--disable-proxy"], "")
const resp = await codeServer.fetch(proxyPath)
expect(resp.status).toBe(403)
const json = await resp.json()
expect(json).toEqual({ error: "Forbidden" })
})
it("should rewrite the base path", async () => {
e.get("/wsup", (req, res) => {
res.json("asher is the best")
@@ -99,7 +109,7 @@ describe("proxy", () => {
})
it("should allow post bodies", async () => {
e.use(bodyParser.json({ strict: false }))
e.use(express.json({ strict: false }))
e.post("/wsup", (req, res) => {
res.json(req.body)
})
@@ -116,7 +126,7 @@ describe("proxy", () => {
})
it("should handle bad requests", async () => {
e.use(bodyParser.json({ strict: false }))
e.use(express.json({ strict: false }))
e.post("/wsup", (req, res) => {
res.json(req.body)
})
@@ -143,7 +153,7 @@ describe("proxy", () => {
})
it("should handle errors", async () => {
e.use(bodyParser.json({ strict: false }))
e.use(express.json({ strict: false }))
e.post("/wsup", (req, res) => {
throw new Error("BROKEN")
})
@@ -187,6 +197,17 @@ describe("proxy", () => {
})
}).rejects.toThrow()
})
it("should proxy non-ASCII", async () => {
e.get("*", (req, res) => {
res.json("ほげ")
})
codeServer = await integration.setup(["--auth=none"], "")
const resp = await codeServer.fetch(proxyPath.replace("wsup", "ほげ"))
expect(resp.status).toBe(200)
const json = await resp.json()
expect(json).toBe("ほげ")
})
})
// NOTE@jsjoeio

View File

@@ -1,6 +1,6 @@
import { mockLogger } from "../../../utils/helpers"
import * as httpserver from "../../../utils/httpserver"
import * as integration from "../../../utils/integration"
import { mockLogger } from "../../../utils/helpers"
describe("vscode", () => {
let codeServer: httpserver.HttpServer | undefined

View File

@@ -1,4 +1,4 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />

View File

@@ -1,10 +1,10 @@
import { logger } from "@coder/logger"
import * as http from "http"
import { AddressInfo } from "net"
import * as path from "path"
import { ensureAddress } from "../../../src/node/app"
import { SettingsProvider, UpdateSettings } from "../../../src/node/settings"
import { LatestResponse, UpdateProvider } from "../../../src/node/update"
import { clean, isAddressInfo, mockLogger, tmpdir } from "../../utils/helpers"
import { clean, mockLogger, tmpdir } from "../../utils/helpers"
describe("update", () => {
let version = "1.0.0"
@@ -79,7 +79,6 @@ describe("update", () => {
}
let _provider: UpdateProvider | undefined
let _address: string | AddressInfo | null
const provider = (): UpdateProvider => {
if (!_provider) {
throw new Error("Update provider has not been created")
@@ -87,6 +86,7 @@ describe("update", () => {
return _provider
}
let address = new URL("http://localhost")
beforeAll(async () => {
mockLogger()
@@ -105,12 +105,13 @@ describe("update", () => {
})
})
_address = server.address()
if (!isAddressInfo(_address)) {
throw new Error("unexpected address")
const addr = ensureAddress(server, "http")
if (typeof addr === "string") {
throw new Error("unable to run update tests with unix sockets")
}
_provider = new UpdateProvider(`http://${_address?.address}:${_address?.port}/latest`, _settings)
address = addr
address.pathname = "/latest"
_provider = new UpdateProvider(address.toString(), _settings)
})
afterAll(() => {
@@ -220,59 +221,51 @@ describe("update", () => {
})
it("should reject if response has status code 500", async () => {
if (isAddressInfo(_address)) {
const mockURL = `http://${_address.address}:${_address.port}/reject-status-code`
const provider = new UpdateProvider(mockURL, settings())
const update = await provider.getUpdate(true)
address.pathname = "/reject-status-code"
const provider = new UpdateProvider(address.toString(), settings())
const update = await provider.getUpdate(true)
expect(update.version).toBe("unknown")
expect(logger.error).toHaveBeenCalled()
expect(logger.error).toHaveBeenCalledWith("Failed to get latest version", {
identifier: "error",
value: `${mockURL}: 500`,
})
}
expect(update.version).toBe("unknown")
expect(logger.error).toHaveBeenCalled()
expect(logger.error).toHaveBeenCalledWith("Failed to get latest version", {
identifier: "error",
value: `${address.toString()}: 500`,
})
})
it("should reject if no location header provided", async () => {
if (isAddressInfo(_address)) {
const mockURL = `http://${_address.address}:${_address.port}/no-location-header`
const provider = new UpdateProvider(mockURL, settings())
const update = await provider.getUpdate(true)
address.pathname = "/no-location-header"
const provider = new UpdateProvider(address.toString(), settings())
const update = await provider.getUpdate(true)
expect(update.version).toBe("unknown")
expect(logger.error).toHaveBeenCalled()
expect(logger.error).toHaveBeenCalledWith("Failed to get latest version", {
identifier: "error",
value: `received redirect with no location header`,
})
}
expect(update.version).toBe("unknown")
expect(logger.error).toHaveBeenCalled()
expect(logger.error).toHaveBeenCalledWith("Failed to get latest version", {
identifier: "error",
value: `received redirect with no location header`,
})
})
it("should resolve the request with response.headers.location", async () => {
version = "4.1.1"
if (isAddressInfo(_address)) {
const mockURL = `http://${_address.address}:${_address.port}/with-location-header`
const provider = new UpdateProvider(mockURL, settings())
const update = await provider.getUpdate(true)
address.pathname = "/with-location-header"
const provider = new UpdateProvider(address.toString(), settings())
const update = await provider.getUpdate(true)
expect(logger.error).not.toHaveBeenCalled()
expect(update.version).toBe("4.1.1")
}
expect(logger.error).not.toHaveBeenCalled()
expect(update.version).toBe("4.1.1")
})
it("should reject if more than 10 redirects", async () => {
if (isAddressInfo(_address)) {
const mockURL = `http://${_address.address}:${_address.port}/redirect/11`
const provider = new UpdateProvider(mockURL, settings())
const update = await provider.getUpdate(true)
address.pathname = "/redirect/11"
const provider = new UpdateProvider(address.toString(), settings())
const update = await provider.getUpdate(true)
expect(update.version).toBe("unknown")
expect(logger.error).toHaveBeenCalled()
expect(logger.error).toHaveBeenCalledWith("Failed to get latest version", {
identifier: "error",
value: `reached max redirects`,
})
}
expect(update.version).toBe("unknown")
expect(logger.error).toHaveBeenCalled()
expect(logger.error).toHaveBeenCalledWith("Failed to get latest version", {
identifier: "error",
value: `reached max redirects`,
})
})
})

View File

@@ -491,22 +491,6 @@ describe("isDirectory", () => {
})
})
describe("humanPath", () => {
it("should return an empty string if no path provided", () => {
const mockHomedir = "/home/coder"
const actual = util.humanPath(mockHomedir)
const expected = ""
expect(actual).toBe(expected)
})
it("should replace the homedir with ~", () => {
const mockHomedir = "/home/coder"
const path = `${mockHomedir}/code-server`
const actual = util.humanPath(mockHomedir, path)
const expected = "~/code-server"
expect(actual).toBe(expected)
})
})
describe("isWsl", () => {
const testName = "wsl"

View File

@@ -0,0 +1,277 @@
import { logger } from "@coder/logger"
import * as app from "../../../src/node/app"
import { EditorSessionManager, makeEditorSessionManagerServer } from "../../../src/node/vscodeSocket"
import { clean, tmpdir, listenOn, mockLogger } from "../../utils/helpers"
describe("makeEditorSessionManagerServer", () => {
let tmpDirPath: string
const testName = "mesms"
beforeAll(async () => {
jest.clearAllMocks()
mockLogger()
await clean(testName)
})
afterAll(() => {
jest.resetModules()
})
beforeEach(async () => {
tmpDirPath = await tmpdir(testName)
})
it("warns if socket cannot be created", async () => {
jest.spyOn(app, "listen").mockImplementation(() => {
throw new Error()
})
const server = await makeEditorSessionManagerServer(
`${tmpDirPath}/code-server-ipc.sock`,
new EditorSessionManager(),
)
expect(logger.warn).toHaveBeenCalledWith(`Could not create socket at ${tmpDirPath}/code-server-ipc.sock`)
server.close()
})
})
describe("EditorSessionManager", () => {
let tmpDirPath: string
const testName = "esm"
beforeAll(async () => {
await clean(testName)
})
beforeEach(async () => {
tmpDirPath = await tmpdir(testName)
})
describe("getCandidatesForFile", () => {
it("should prefer the last added socket path for a matching path", async () => {
const manager = new EditorSessionManager()
manager.addSession({
workspace: {
id: "aaa",
folders: [
{
uri: {
path: "/aaa",
},
},
],
},
socketPath: `${tmpDirPath}/vscode-ipc-aaa-1.sock`,
})
manager.addSession({
workspace: {
id: "aaa",
folders: [
{
uri: {
path: "/aaa",
},
},
],
},
socketPath: `${tmpDirPath}/vscode-ipc-aaa-2.sock`,
})
manager.addSession({
workspace: {
id: "bbb",
folders: [
{
uri: {
path: "/bbb",
},
},
],
},
socketPath: `${tmpDirPath}/vscode-ipc-bbb.sock`,
})
const socketPaths = manager.getCandidatesForFile("/aaa/some-file:1:1")
expect(socketPaths.map((x) => x.socketPath)).toEqual([
// Matches
`${tmpDirPath}/vscode-ipc-aaa-2.sock`,
`${tmpDirPath}/vscode-ipc-aaa-1.sock`,
// Non-matches
`${tmpDirPath}/vscode-ipc-bbb.sock`,
])
})
it("should return the last added socketPath if there are no matches", async () => {
const manager = new EditorSessionManager()
manager.addSession({
workspace: {
id: "aaa",
folders: [
{
uri: {
path: "/aaa",
},
},
],
},
socketPath: `${tmpDirPath}/vscode-ipc-aaa.sock`,
})
manager.addSession({
workspace: {
id: "bbb",
folders: [
{
uri: {
path: "/bbb",
},
},
],
},
socketPath: `${tmpDirPath}/vscode-ipc-bbb.sock`,
})
const socketPaths = manager.getCandidatesForFile("/ccc/some-file:1:1")
expect(socketPaths.map((x) => x.socketPath)).toEqual([
`${tmpDirPath}/vscode-ipc-bbb.sock`,
`${tmpDirPath}/vscode-ipc-aaa.sock`,
])
})
it("does not just directly do a substring match", async () => {
const manager = new EditorSessionManager()
manager.addSession({
workspace: {
id: "aaa",
folders: [
{
uri: {
path: "/aaa",
},
},
],
},
socketPath: `${tmpDirPath}/vscode-ipc-aaa.sock`,
})
manager.addSession({
workspace: {
id: "bbb",
folders: [
{
uri: {
path: "/bbb",
},
},
],
},
socketPath: `${tmpDirPath}/vscode-ipc-bbb.sock`,
})
const entries = manager.getCandidatesForFile("/aaaxxx/some-file:1:1")
expect(entries.map((x) => x.socketPath)).toEqual([
`${tmpDirPath}/vscode-ipc-bbb.sock`,
`${tmpDirPath}/vscode-ipc-aaa.sock`,
])
})
})
describe("getConnectedSocketPath", () => {
it("should return socket path if socket is active", async () => {
listenOn(`${tmpDirPath}/vscode-ipc-aaa.sock`).once()
const manager = new EditorSessionManager()
manager.addSession({
workspace: {
id: "aaa",
folders: [
{
uri: {
path: "/aaa",
},
},
],
},
socketPath: `${tmpDirPath}/vscode-ipc-aaa.sock`,
})
const socketPath = await manager.getConnectedSocketPath("/aaa/some-file:1:1")
expect(socketPath).toBe(`${tmpDirPath}/vscode-ipc-aaa.sock`)
})
it("should return undefined if socket is inactive", async () => {
const manager = new EditorSessionManager()
manager.addSession({
workspace: {
id: "aaa",
folders: [
{
uri: {
path: "/aaa",
},
},
],
},
socketPath: `${tmpDirPath}/vscode-ipc-aaa.sock`,
})
const socketPath = await manager.getConnectedSocketPath("/aaa/some-file:1:1")
expect(socketPath).toBeUndefined()
})
it("should return undefined given no matching active sockets", async () => {
const vscodeSockets = listenOn(`${tmpDirPath}/vscode-ipc-bbb.sock`)
const manager = new EditorSessionManager()
manager.addSession({
workspace: {
id: "aaa",
folders: [
{
uri: {
path: "/aaa",
},
},
],
},
socketPath: `${tmpDirPath}/vscode-ipc-aaa.sock`,
})
const socketPath = await manager.getConnectedSocketPath("/aaa/some-file:1:1")
expect(socketPath).toBeUndefined()
vscodeSockets.close()
})
it("should return undefined if there are no entries", async () => {
const manager = new EditorSessionManager()
const socketPath = await manager.getConnectedSocketPath("/aaa/some-file:1:1")
expect(socketPath).toBeUndefined()
})
it("should return most recently used socket path available", async () => {
listenOn(`${tmpDirPath}/vscode-ipc-aaa-1.sock`).once()
const manager = new EditorSessionManager()
manager.addSession({
workspace: {
id: "aaa",
folders: [
{
uri: {
path: "/aaa",
},
},
],
},
socketPath: `${tmpDirPath}/vscode-ipc-aaa-1.sock`,
})
manager.addSession({
workspace: {
id: "aaa",
folders: [
{
uri: {
path: "/aaa",
},
},
],
},
socketPath: `${tmpDirPath}/vscode-ipc-aaa-2.sock`,
})
const socketPath = await manager.getConnectedSocketPath("/aaa/some-file:1:1")
expect(socketPath).toBe(`${tmpDirPath}/vscode-ipc-aaa-1.sock`)
// Failed sockets should be removed from the entries.
expect((manager as any).entries.has(`${tmpDirPath}/vscode-ipc-aaa-2.sock`)).toBe(false)
})
})
})

View File

@@ -1,8 +1,8 @@
import { promises as fs } from "fs"
import * as path from "path"
import { workspaceDir } from "./constants"
import { clean, tmpdir } from "./helpers"
import * as wtfnode from "./wtfnode"
import * as path from "path"
import { promises as fs } from "fs"
/**
* Perform workspace cleanup and authenticate. This should be ran before e2e

View File

@@ -108,20 +108,6 @@ export function idleTimer(message: string, reject: (error: Error) => void, delay
}
}
/**
* A helper function which returns a boolean indicating whether
* the given address is AddressInfo and has .address
* and a .port property.
*/
export function isAddressInfo(address: unknown): address is net.AddressInfo {
return (
address !== null &&
typeof address !== "string" &&
(address as net.AddressInfo).port !== undefined &&
(address as net.AddressInfo).address !== undefined
)
}
/**
* If using a proxy, return the address of the proxy.
*
@@ -150,3 +136,52 @@ export function getMaybeProxiedPathname(url: URL): string {
return url.pathname
}
interface FakeVscodeSockets {
/* If called, closes all servers after the first connection. */
once(): FakeVscodeSockets
/* Manually close all servers. */
close(): Promise<void>
}
/**
* Creates servers for each socketPath specified.
*/
export function listenOn(...socketPaths: string[]): FakeVscodeSockets {
let once = false
const servers = socketPaths.map((socketPath) => {
const server = net.createServer(() => {
if (once) {
close()
}
})
server.listen(socketPath)
return server
})
async function close() {
await Promise.all(
servers.map(
(server) =>
new Promise<void>((resolve, reject) => {
server.close((err) => {
if (err) {
reject(err)
return
}
resolve()
})
}),
),
)
}
const fakeVscodeSockets = {
close,
once: () => {
once = true
return fakeVscodeSockets
},
}
return fakeVscodeSockets
}

View File

@@ -35,7 +35,7 @@ export class HttpServer {
return new Promise((resolve, reject) => {
this.hs.on("error", reject)
this.hs.listen(0, "localhost", () => {
this.hs.listen(0, "127.0.0.1", () => {
this.hs.off("error", reject)
resolve()

View File

@@ -2856,6 +2856,11 @@ qs@^6.10.1:
dependencies:
side-channel "^1.0.4"
querystringify@^2.1.1:
version "2.2.0"
resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6"
integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==
react-is@^17.0.1:
version "17.0.2"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
@@ -2875,6 +2880,11 @@ require-directory@^2.1.1:
resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I=
requires-port@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==
resolve-cwd@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d"
@@ -2936,16 +2946,16 @@ saxes@^5.0.1:
xmlchars "^2.2.0"
semver@7.x, semver@^7.3.2, semver@^7.3.5:
version "7.3.5"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7"
integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==
version "7.5.4"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e"
integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==
dependencies:
lru-cache "^6.0.0"
semver@^6.0.0, semver@^6.3.0:
version "6.3.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
version "6.3.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4"
integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
set-blocking@^2.0.0:
version "2.0.0"
@@ -3222,13 +3232,14 @@ to-regex-range@^5.0.1:
is-number "^7.0.0"
tough-cookie@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.0.0.tgz#d822234eeca882f991f0f908824ad2622ddbece4"
integrity sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg==
version "4.1.3"
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.3.tgz#97b9adb0728b42280aa3d814b6b999b2ff0318bf"
integrity sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==
dependencies:
psl "^1.1.33"
punycode "^2.1.1"
universalify "^0.1.2"
universalify "^0.2.0"
url-parse "^1.5.3"
tr46@^2.1.0:
version "2.1.0"
@@ -3280,10 +3291,18 @@ typedarray-to-buffer@^3.1.5:
dependencies:
is-typedarray "^1.0.0"
universalify@^0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==
universalify@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.2.0.tgz#6451760566fa857534745ab1dde952d1b1761be0"
integrity sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==
url-parse@^1.5.3:
version "1.5.10"
resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1"
integrity sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==
dependencies:
querystringify "^2.1.1"
requires-port "^1.0.0"
util-deprecate@^1.0.1:
version "1.0.2"
@@ -3379,9 +3398,9 @@ wide-align@^1.1.2:
string-width "^1.0.2 || 2 || 3 || 4"
word-wrap@~1.2.3:
version "1.2.3"
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"
integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==
version "1.2.4"
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.4.tgz#cb4b50ec9aca570abd1f52f33cd45b6c61739a9f"
integrity sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==
wrap-ansi@^7.0.0:
version "7.0.0"

2517
yarn.lock

File diff suppressed because it is too large Load Diff