Compare commits

..

144 Commits

Author SHA1 Message Date
a 4820d42e3a Delete '.nvmrc' 2022-02-23 02:53:35 +00:00
Tim d7c1894fb4
Update requirements.md (#4882)
Grammar/typo correction.

Co-authored-by: Joe Previte <jjprevite@gmail.com>
2022-02-18 11:06:58 -07:00
Joe Previte 01a0e95174
docs: update sync vscode instructions (#4879)
* docs: update sync vscode instructions

* fixup!

* fixup: formatting
2022-02-17 15:18:17 -07:00
Joe Previte 94f378c196
feat(testing): add test for `parse` when error in args + config (#4866)
* chore: fmt cleanup

* feat(parse): add test error w/config
2022-02-15 16:19:22 -07:00
Joe Previte e3e9f052c4
fix: wrap socket in proxy before passing to vscode (#4840)
* chore: add ipc hook to e2e script

* refactor: allow codeServerArgs in e2e tests

* feat: add --cert e2e extension test

* fix: wrap websocket in proxy

* fixup: remvoe ignoreHTTPSErrors

* fixup: make codeServerArgs readonly

* fixup! add back ignoreHTTPSErrors
2022-02-15 14:51:42 -07:00
Joe Previte b26cce589f
chore: update vscode commit (#4857)
* chore: update vscode commit

This includes two fixes from coder/vscode:
- https://github.com/coder/vscode/pull/43
- https://github.com/coder/vscode/pull/42

* fix: use double-bracket in ext. e2e test
2022-02-15 14:15:53 -07:00
Joe Previte 8fc4832722
chore: update follow-redirects resolution (#4868)
* chore: update follow-redirects resolution

* chore: update vm2 resolution
2022-02-15 13:40:53 -07:00
Ben Potter 36eae3b9f2
fix: Slack community link (#4864)
resolves #4843

Co-authored-by: Joe Previte <jjprevite@gmail.com>
2022-02-14 18:23:20 -06:00
Thomas John Wesolowski 3ad618db97
feat: Add support for imagePullSecrets to Helm chart (#4838)
* add support for imagePullSecrets

* Add doc and example value for imagePullSecrets

* simplify syntax for imagePullSecrets

Co-authored-by: Joe Previte <jjprevite@gmail.com>
2022-02-14 15:34:07 -07:00
Joe Previte c9c5c54cda
feat: add tests for update.ts (#4835)
* feat: add isAddressInfo helper function

* feat(update): add test for rejection UpdateProvider

* feat: add more tests for UpdateProvider

* fixup! move isAddressInfo, add .address check

* fixup! remove extra writeHead

* fixup! use -1 in redirect logic

* fixup! remove unnecessary String call

* fixup! use /latest for redirect

* fixup! use match group for regex

* fixup!: replace match/split logic
2022-02-14 13:53:28 -07:00
Lorenz Brun 102478bdea
fix: ensure dumb-init is PID 1 (#4846)
Exec to dumb-init in entrypoint script, so that it can
handle signals and reap subprocesses.
2022-02-11 11:44:01 -08:00
Jonathan Yu 03c62242ee
chore: allow overwriting Docker images (#4850)
Allow Docker images to be overwritten, which is required to update
dependencies in the base image when the upstream vendor resolves
security issues.
2022-02-11 09:32:02 -08:00
Jonathan Yu 8135d2ecc3
chore: update Docker image publish workflow (#4847)
* Use official action to log in to Docker Hub
* Run using pinned ubuntu-20.04 environment
2022-02-10 17:40:41 -08:00
Winston R. Milling 177f0ed163
feat: support ingressClassName in Helm Chart (#4844)
Allow configuring the ingress class via a value.
2022-02-10 15:16:35 -08:00
Joe Previte 25b1340771
revert: @node-rs/argon2 -> node-argon2 (#4829)
* revert: partial revert of 723469ab5b

This reverts part of the changes introduced in refactor: migrate from argon2 ->
@node-rs/argon2 (#4733)

Switching to @node-rs/argon2 introduced bugs that we couldn't solve due to
limitations in npm.

see here
https://github.com/coder/code-server/issues/4804#issuecomment-1030338395
2022-02-04 15:52:42 -07:00
Joe Previte 00224fa73a
feat: add test for hash when error (#4814) 2022-02-03 14:22:16 -07:00
Joe Previte fd643dcbc3
refactor(ci): fix npm workflows (#4797)
* feat: refactor npm workflows to use download-artifact

This refactors the npm workflows to use the download-artifact GitHub Action. We
had problems in the past with our download_artifact custom bash function. This
also fixes an issue where we weren't downloading the correct artifacts when
publishing beta and dev tags to npm.

* fixup: remove unused env var

* fixup! add download-artifcat to npm-brew"

* fixup! remove unnecessary code comment

* fixup! move NPM_ENVIRONMENT logic to script
2022-02-03 13:54:36 -07:00
renovate[bot] 79412eb137
chore(deps): update aquasecurity/trivy-action commit hash to a7a829a (#4821)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-02-03 12:43:31 -07:00
LG 10f57bac65
docs: Update some more links (#4806)
* Update links in package.json

I will try checking the docs too

* docs: Update links in triage.md

* docs: Update links in npm.md

* docs: Update links in whatever files that have `cdr`

* Replace globally, thanks @bpmct!

* fix: coderer instead of coder
I should've used all three toggles in the Search/Replace tab in the GItHub.dev editor.

* Code Formatting
2022-02-01 09:45:19 -07:00
Asher 439f27c568
release: v4.0.2 (#4801)
* Disable BROWSER env var

Right now the browser helper script does not actually work.  It seems
safer to skip this until we can fix it.

* Bump to 4.0.2

* Update changelog for 4.0.2
2022-01-27 19:00:06 -06:00
Asher c20b0b390b
fix: make browser helper executable (#4792)
Surprisingly it does not come already executable like the dev-bin
scripts.
2022-01-25 17:18:45 -06:00
Joe Previte 14d0feff0d
chore(deps): update node-fetch and nanoid (#4787) 2022-01-25 10:52:17 -07:00
Joe Previte 9954a88d3a
refactor(ci): clean up npm workflows (#4786)
This extracst the publish on npm workflow from ci.yaml and adds a new workflow
called `npm-beta.yaml`.

Now we have three workflows that publish to npm.
- `npm-beta.yaml` only runs on pushes and merges into `main`
- `npm-dev.yaml` only runs on PRs into `main` with approval from
  code-server-reviewers team
- `npm-brew.yaml` only runs on releases

This should fix problems we had previously where anyone could open a PR and
publish under the code-server namespace. It also separates out the workflows
based on environment and when they should run.
2022-01-24 15:33:42 -07:00
Joe Previte 48bbbd6c37
chore: update bug-report.yaml (#4780)
* chore: update bug-report.yml

Fix `OS/Web Information` section and don't render as Markdown

* chore: add repro steps to bug template
2022-01-24 14:58:26 -07:00
Joe Previte 8816ab93fe
fix: update npm-dev.yaml (#4781) 2022-01-22 08:48:57 -07:00
Joe Previte a2f530161f
feat: publish beta and development tags on npm (#4769)
* feat: add logic to publish beta/dev npm

This adds new log to publish the npm package both while working on PRs and when
PRs are merged into main, allowing us to easily test changes in a
production-like setting.

Co-authored-by: Asher <ash@coder.com>
2022-01-21 16:28:56 -07:00
Asher 63f99da087
fix: package bin/helper directory in the right spot (#4776)
VS Code moves this as part of their packaging task.  We will probably
want to look into using theirs instead of replicating it.

Fixes https://github.com/coder/code-server/issues/4721
2022-01-21 10:54:48 -06:00
LG 6e04501ec5
docs: Fix links (#4770)
* docs: Change `cdr` to `coder`, read desc please

Yeah, includes links.

* docs: Update README (links and the extra comma)

Co-authored-by: Joe Previte <jjprevite@gmail.com>
2022-01-20 11:38:19 -07:00
Joe Previte 51818dc419
chore: update issue templates (#4768)
* chore: delete release issue template

* chore: use bug-report form template

* chore: clean up config.yml for templates

* chore: clean up doc issue template

* chore: clean up feature request template
2022-01-20 09:39:31 -07:00
n1ghtBl00d 48cd3c8b11
Fix Typos in guide.md (#4674)
The Guide mentioned "HTTP/HTTP traffic" when it should say "HTTP/HTTPS traffic"

Co-authored-by: Joe Previte <jjprevite@gmail.com>
2022-01-18 16:44:02 -07:00
Joe Previte 723469ab5b
refactor: migrate from argon2 -> @node-rs/argon2 (#4733)
* chore(deps): replace argon2 w/@node-rs/argon2

* refactor: clean up hashPassword functions

* refactor(util): pass in process.platform

* fix: use correct settings for test-extension

Before, it was running into errors with an @types package.

Now, we're correctly running `tsc` so it picks up our `tsconfig.json` and we're
telling TypeScript to not typecheck our lib and exclude `node_modules`
2022-01-18 16:13:39 -07:00
renovate[bot] 2752d95ff6
chore(deps): update minor dependency updates (#4722)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-01-14 21:48:52 +00:00
Joe Previte b621911e61
chore: use 1.14.7 follow-redirects (#4742) 2022-01-13 11:07:19 -07:00
Joe Previte 48ddad8351
fix: reverse logic in brew-bump env fns (#4732) 2022-01-12 15:51:11 -07:00
Dmitriy Fishman 15500bb388
docs: fix a typo in guide.md (#4728) 2022-01-12 13:00:58 -07:00
Jonathan Yu ad81a30db5
chore: add configuration for Semantic Pull Requests app (#4652)
* chore: add configuration for Semantic Pull Requests app

* update lists

* remove tests scope
2022-01-10 15:11:59 -07:00
Joe Previte 70c91c857a
feat: add tests for node/settings.ts (#4717) 2022-01-10 20:44:28 +00:00
Dmitriy Fishman 33c50972bc
docs: fix a typo in MAINTAINING.md (#4711) 2022-01-10 11:38:43 -07:00
Asher ca15d4b7a0
docs: add marketplace change to changelog (#4692) 2022-01-06 13:20:11 -06:00
Joe Previte 484429b5da
release: 4.0.1 (#4678)
* chore(release): bump version to 4.0.1

* fix: ignore pre-commit hook for release-prep script

* chore: bump helm-chart to 2.0.0

* chore: update CHANGELOG for 4.0.1
2022-01-05 13:06:32 -07:00
Asher 3d999986b2
feat: proxy URI environment variable (#4681)
* Add test extension

This will let us test extension-related features (like the proxy URI).

I removed the environment variables in the script because they override
the ones you set yourself. We still set defaults in constants.ts.

* Add changelog entry for VSCODE_PROXY_URI

* Add terminal test for VSCODE_PROXY_URI

* Update VS Code

This adds the VSCODE_PROXY_URI environment variable.
2022-01-04 15:02:25 -06:00
Asher f04fcf2d64
chore: update vscode (#4680)
* Update VS Code

Fixes https://github.com/coder/code-server/issues/4598
Fixes https://github.com/coder/code-server/issues/4620
Fixes https://github.com/coder/code-server/issues/4600

* Add locale flag back

This is valid again now that we have restored display language support.
2022-01-04 12:37:11 -06:00
Joe Previte 7695de2831
fix(ci): add environment to docs-preview (#4677) 2022-01-04 11:05:04 -07:00
Asher 003480881b
fix: infinite proxy loop (#4676)
I think the problem is that when a proxy is not in use proxy-agent
returns the global agent...which is itself since we set it globally,
causing the loop.

VS Code already covers proxies meaning we only need to do it in our own
requests so to fix this pass in the agent in the version fetch request
instead of overidding globally.

Also avoid proxy-from-env and pass in the proxy URI instead as both
http_proxy and https_proxy can be used for either http or https requests
but it does not allow that.
2022-01-03 18:19:21 -06:00
Simon Elsbrock 47f0b6f4fa
docs(ipad): fix link to ios-ipad related issues (#4651)
Co-authored-by: Joe Previte <jjprevite@gmail.com>
2021-12-28 12:45:28 -07:00
renovate[bot] 047a4f01f2
chore(deps): update aquasecurity/trivy-action commit hash to 8f4c716 (#4650)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-12-27 10:38:13 -07:00
Joe Previte 95d965a27b
fix: add codeServerVersion to vscode/product.json (#4632)
* fix: add codeServerVersion to vscode/product/json

* chore: update vscode

* chore: update modulePathIgnorePattern jest
2021-12-17 12:58:46 -07:00
Asher c4c480a068
Implement last opened functionality (#4633)
* Implement last opened functionality

Fixes https://github.com/cdr/code-server/issues/4619

* Fix test temp dirs not being cleaned up

* Mock logger everywhere

This suppresses all the error and debug output we generate which makes
it hard to actually find which test has failed.  It also gives us a
standard way to test logging for the few places we do that.

* Use separate data directories for unit test instances

Exactly as we do for the e2e tests.

* Add integration tests for vscode route

* Make settings use --user-data-dir

Without this test instances step on each other feet and they also
clobber your own non-test settings.

* Make redirects consistent

They will preserve the trailing slash if there is one.

* Remove compilation check

If you do a regular non-watch build there are no compilation stats so
this bricks VS Code in CI when running the unit tests.

I am not sure how best to fix this for the case where you have a build
that has not been packaged yet so I just removed it for now and added a
message to check if VS Code is compiling when in dev mode.

* Update code-server update endpoint name
2021-12-17 13:06:52 -06:00
Jonathan Yu b990dabed1
chore: use readonly deploy key for preview (#4645) 2021-12-17 10:27:22 -07:00
Joe Previte 4943e9418d
docs: fix install.sh link (#4627)
Co-authored-by: Asher <ash@coder.com>
2021-12-16 16:59:07 -07:00
Asher f10ef42f04
Add codeowner group for new name (#4641) 2021-12-16 16:43:05 -06:00
Asher c27dbd0463
Revert .editorconfig (#4640)
I have been wondering why my editor is not defaulting to two spaces and
it is because indent_size was removed from the * block.

I am not sure why we would need a separate block for package.json and
yaml anyway since they use the same settings as the rest of our code.

I would revert the commit but this change was made in a larger commit
with a bunch of unrelated changes.
2021-12-16 15:30:07 -06:00
Kyle Carberry ec3d9974b3
fix: Update install.sh to use "coder" GitHub organization (#4638)
* fix: Update install.sh to use "coder" GitHub organization

* Update docs preview
2021-12-16 10:49:16 -06:00
Asher 73e0b79d7f
Update VS Code (#4631)
Fixes https://github.com/cdr/code-server/issues/3410
Fixes https://github.com/cdr/code-server/issues/4604
Fixes https://github.com/cdr/code-server/issues/4607
Fixes https://github.com/cdr/code-server/issues/4608
Fixes https://github.com/cdr/code-server/issues/4609

Also has the foundation for
https://github.com/cdr/code-server/issues/4619.
2021-12-15 15:26:59 -07:00
Joe Previte 3059d72a5d
docs: add section for Android (#4621)
* Add Instructions To Install 100% In Android (#4536)

* Add Instructions To Install 100% In Android

Thx

* Update android.md

Co-authored-by: Joe Previte <jjprevite@gmail.com>

* docs: clean up android.md

* docs: add android to manifest

* Add missing backtick

Co-authored-by: The AliX Legend <alixgamer175@gmail.com>
Co-authored-by: Asher <ash@coder.com>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2021-12-15 20:37:16 +00:00
Joe Previte 6e9e411542
fix: add --clobber to release-github-assets script (#4628)
Co-authored-by: Asher <ash@coder.com>
2021-12-15 20:11:35 +00:00
renovate[bot] 7561fc4306
chore(deps): update aquasecurity/trivy-action commit hash to 81cc8cd (#4601)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
Co-authored-by: Joe Previte <jjprevite@gmail.com>
2021-12-15 10:38:18 -07:00
Len 259363bcdb
Support browsers from before 2020 (#4579)
* Support browsers from before 2020

As reported in #2825, #2826 and #3051, almost everything works in older browsers. This setting here prevented me from updating and I think it is an obvious enhancement to not restrict to browsers from 2020+. There should not be any measurable downsides of this change since es6 and es2020 are pretty similar with only minor differences.

* Include lib (polyfills) for <es2020 targets

* Assume all modern dom features despite es6 syntax

* Add modern dom iterators to es6 environment

Co-authored-by: Joe Previte <jjprevite@gmail.com>
Co-authored-by: Asher <ash@coder.com>
2021-12-15 10:07:42 -07:00
Joe Previte 1b796d19bf
docs: add section for iOS (#4622)
* Added instructions to install it on iOSi (#4614)

* Added instructions to install it on iOS

* Update ios.md

Co-authored-by: Joe Previte <jjprevite@gmail.com>

* docs: add ios to manifest

* docs: clean up ios docs

Co-authored-by: The AliX Legend <alixgamer175@gmail.com>
2021-12-14 23:43:52 +00:00
hyk 5960a6728c
support git-lfs default (#4611)
Co-authored-by: Joe Previte <jjprevite@gmail.com>
2021-12-14 16:08:25 -07:00
Leask Wong 18e66c4299
adding option --edge to install latest edge / preview version (#4605)
* adding option --edge to install latest edge / preview version

* updated arg --edge parsing to match the style of --dryrun
2021-12-14 15:30:35 -07:00
Joe Previte c77999bcd3
release: 4.0.0 (#4597)
* chore(release): bump version to 4.0.0

* Bump test plugin code-server engine

Co-authored-by: Asher <ash@coder.com>
2021-12-10 13:30:53 -07:00
Asher 9e583fa562
Add separate function for VS Code arguments (#4599)
The problem before was that the pop() caused the open in existing
instance functionality to break because the arguments no longer
contained the file.

We could simply remove the pop() but since `workspace` and `folder` are
not CLI arguments I think it makes sense to handle them in a separate
function which can be called at the point where they are needed.  This
also lets us de-duplicate some logic since we create these arguments in
two spots and lets us skip this logic when we do not need it.

The pop() is still avoided because manipulating a passed-in object
in-place seems like a risky move.  If we really need to do this we
should copy the positional argument array instead.
2021-12-10 12:01:35 -06:00
Joe Previte 3b91cffae5
chore: update CHANGELOG (#4595) 2021-12-09 14:21:02 -07:00
Asher 4b4ec37880
Fix relative paths (#4594)
* Add tests for relativeRoot

* Remove path.posix.join

Since this is for file system paths it feels incorrect to use it on
URL paths as they are different in many ways.

* Rewrite cookie path logic

Before we relied on the client to resolve the base given to it by the
backend against the path.

Instead have the client pass that information along so we can resolve it
on the backend.  This means the client has to do less work.

* Do not remove out directory before watch

This is re-used for incremental compilation.

Also remove del since that was the only use (and we can use fs.rmdir in
the future if we need something like this).

* Remove unused function resolveBase
2021-12-08 15:52:15 -06:00
Asher 9d9f3a41ab
Fix spawning code-server within code-server (#4590)
* Remove extra VS Code CLI spawn

We already spawn VS Code's CLI when necessary in the lines below.
Having the CLI spawn unconditionally when in a VS Code environment makes
it impossible to run code-server within code-server (for example to
develop it).

* Update VS Code

This sanitizes our environment variables so code-server does not always
think it is a child spawn.

Fixes https://github.com/cdr/code-server/issues/4519.
2021-12-07 18:20:57 -06:00
Asher c3eb9b800e
Remove excessive reloading before VS Code is ready (#4589)
The watch script was reloading the web server after every extension
compilation which is not necessary plus VS Code will not even be ready
at that point anyway.

Instead restart when the main compilation is finished.  The string to
match with includes a "with" because otherwise it would match "Finished
compilation extensions" which is not the main compilation task where we
actually need to restart the web server.

I also replaced this.log with console.log because the former does not
include a newline and it appears we want newlines with all
these (otherwise the next log starts on the same line which looks odd).

I removed the cache clean as well because the cache is meant to stay
there to speed up builds.
2021-12-07 17:38:03 -06:00
JounQin 6c9c84090e
chore: replace eslint-import-resolver-alias with eslint-import-resolver-typescript (#4546)
Co-authored-by: Joe Previte <jjprevite@gmail.com>
2021-12-07 14:39:01 -07:00
Joe Previte d15731a570
docs: update sections around VS Code fork (#4571)
* docs(CONTRIBUTING): update workflow based on vscode changes

* docs(MAINTAINING): add section for syncing VS Code upstream

Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
2021-12-06 17:58:04 +00:00
renovate[bot] 80f30fc42c
fix(deps): update dependency qs to v6.10.2 (#4581)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-12-06 10:33:13 -07:00
Teffen 62b3a6fd9f
Proxy path fixes (#4548)
* Fix issue where HTTP error status codes are not read.

* Fix issues surrounding sessions when accessed from a proxy.

- Updated vscode args to match latest upstream.
- Fixed issues surrounding trailing slashes affecting base paths.
- Updated cookie names to better match upstream's usage, debuggability.

* Bump vendor.

* Update tests.

* Fix issue where tests lack cookie key.

Co-authored-by: Asher <ash@coder.com>
2021-12-01 18:21:52 -06:00
Ben Potter 6a2740f57e
chore: add deprecation notice for --link (#4562)
Co-authored-by: Joe Previte <jjprevite@gmail.com>
2021-11-29 12:03:33 -08:00
renovate[bot] 3d4660491a
chore(deps): update minor dependency updates (#4531)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-11-29 11:31:35 -07:00
Joe Previte 65d7420ee7
feat: add test for onLine throw error (#4542) 2021-11-22 20:18:58 +00:00
Joe Previte 0bc96989c2
chore(deps): upgrade json-schema version to 0.4.0 (#4543) 2021-11-22 19:36:04 +00:00
Teffen bd09533f40
Fix issue where 404 errors are mistaken for server errors. (#4534)
* Fix issue where 404 errors are mistaken for server errors.

* Bump vendor.
2021-11-19 18:14:13 -05:00
Joe Previte db35c47315
chore(codecov): add slack webhook integration (#4532)
Co-authored-by: Teffen <TeffenEllis@users.noreply.github.com>
2021-11-19 22:10:27 +00:00
Teffen 3157a40044
Fix issues surrounding initial web server load. (#4509)
- Clean up watcher behaviors.
2021-11-19 16:03:40 -05:00
renovate[bot] 5fe16be62d
chore(deps): update aquasecurity/trivy-action commit hash to 2a2157e (#4493)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
Co-authored-by: Joe Previte <jjprevite@gmail.com>
2021-11-19 12:04:08 -05:00
Asher dd29a823c3
Fix /vscode route being shadowed by / (#4522)
This causes / to always take precedence and on the VS Code side we would
see /vscode instead of / so the matching does not work correctly.
2021-11-17 10:20:01 -06:00
Asher cd26f84bc6
Fix watcher not restarting code-server on VS Code compilation (#4520)
The "Starting watch-client" string no longer appears in the latest build
output.  We could look for "Finished compilation with" to avoid
restarting when other tasks restart (since they also include the name
i.e. "Finished compilation extensions with 0 errors") but I figure we
might as well restart code-server when any compilation task completes in
case other tasks include changes that need to be reloaded.
2021-11-16 16:21:14 -06:00
Jinu ccb9d948c0
fix NOTE.txt (#4515) 2021-11-16 09:46:39 -07:00
Joe Previte 0a072f7532
feat(testing): refactor humanPath and add tests (#4511)
* feat: add test for humanPath

* refactor: make humanPath pure and pass in homedir
2021-11-15 19:40:34 +00:00
Joe Previte 16a5f2e171
docs: update CONTRIBUTING with new flow (#4502) 2021-11-15 11:37:16 -07:00
Teffen e705948ef3
Spawn vscode on demand (#4499)
* Refactor vscode router to load async.

* Bump vscode.

* fix volumes (#4497)

* Fix : recreate the termux guide to adapt the recent changes (#4472)

* Fix : recreate the termux guide to adapt the recent changes

Termux nodejs-lts changed from v14 to v16 and there are many issues people are facing such as with argon2. Hence I recommend changing it to this install process which is comparably better and has one less issue :^)

I've also added some extra things such as installing GO and Python, idk about the TOC tree but this is pretty much it.

* yarn-fmt and minor typos

https://github.com/cdr/code-server/pull/4472#issuecomment-964752180

* Fix : replace unnecessary steps to be linked to a guide

* Change from private gist to a section in Extra

* Remove reference to non-existent step

* ready to merge!

Co-authored-by: Joe Previte <jjprevite@gmail.com>

Co-authored-by: Jinu <jlandowner8@gmail.com>
Co-authored-by: Han Seung Min - 한승민 <hanseungmin.ar@gmail.com>
Co-authored-by: Joe Previte <jjprevite@gmail.com>
2021-11-14 20:03:20 -05:00
Han Seung Min - 한승민 6606040835
Fix : recreate the termux guide to adapt the recent changes (#4472)
* Fix : recreate the termux guide to adapt the recent changes

Termux nodejs-lts changed from v14 to v16 and there are many issues people are facing such as with argon2. Hence I recommend changing it to this install process which is comparably better and has one less issue :^)

I've also added some extra things such as installing GO and Python, idk about the TOC tree but this is pretty much it.

* yarn-fmt and minor typos

https://github.com/cdr/code-server/pull/4472#issuecomment-964752180

* Fix : replace unnecessary steps to be linked to a guide

* Change from private gist to a section in Extra

* Remove reference to non-existent step

* ready to merge!

Co-authored-by: Joe Previte <jjprevite@gmail.com>
2021-11-12 14:25:48 -07:00
Jinu cdf0deb534
fix volumes (#4497) 2021-11-12 10:10:47 -07:00
Asher d052cc246d
Update changelog (#4492)
I added the missing versions and some changelog entries for the latest
version.  I also added some extra details to the entries that should
help users understand what changed and what they need to do about
it.  The overall format is based on keepachangelog.com.

In that same spirit I removed entries that do not affect
users (documentation and development changes).

I removed the names because it seems unlikely users will be interested,
that information can be found in the PR, and code is really the work of
multiple developers (even if only one is writing code and the other is
reviewing) which is not something that is reflected in this document.
2021-11-10 16:45:19 -06:00
Teffen e4a797dc9e
Open VSX switch, Part II (#4319)
* docs: update FAQ with extension gallery info (#2672)

* Update app to reflect Open VSX switch.

- Remove extension related github configs.
- Update tests to reflect new upstream behavior.

Co-authored-by: Akash Satheesan <akash@coder.com>
2021-11-10 10:00:59 -05:00
Teffen 1b60ef418c
Use upstream server (#4414)
* Flesh out fixes to align with upstream.

* Update route handlers to better reflect fallback behavior.

* Add platform to vscode-reh-web task

Our strategy has been to build once and then recompile native modules
for individual platforms.  It looks like VS Code builds from scratch for
each platform.

But we can target any platform, grab the pre-packaged folder, then
continue with own packaging.

In the future we may want to rework to match upstream.

* Fix issue where workspace args are not parsed.

* Fix issues surrounding opening files within code-server's terminal.

* Readd parent wrapper for hot reload.

* Allow more errors.

* Fix issues surrounding Coder link.

* Add dir creation and fix cli

It seems VS Code explodes when certain directories do not exist so
import the reh agent instead of the server component since it creates
the directories (require patching thus the VS Code update).

Also the CLI (for installing extensions) did not seem to be working so
point that to the same place since it also exports a function for
running that part of the CLI.

* Remove hardcoded VSCODE_DEV=1

This causes VS Code to use the development HTML file.  Move this to the
watch command instead.

I deleted the other stuff before it as well since in the latest main.js
they do not have this code so I figure we should be safe to omit it.

* Fix mismatching commit between client and server

* Mostly restore command-line parity

Restore most everything and remove the added server arguments.  This
will let us add and remove options after later so we can contain the
number of breaking changes.

To accomplish this a hard separation is added between the CLI arguments
and the server arguments.

The separation between user-provided arguments and arguments with
defaults is also made more clear.

The extra directory flags have been left out as they were buggy and
should be implemented upstream although I think there are better
solutions anyway.  locale and install-source are unsupported with the
web remote and are left removed.  It is unclear whether they were used
before anyway.

Some restored flags still need to have their behavior re-implemented.

* Fix static endpoint not emitting 404s

This fixes the last failing unit test.

Fix a missing dependency, add some generic reverse proxy support for the
protocol, and add back a missing nfpm fix.

* Import missing logError

* Fix 403 errors

* Add code-server version to about dialog

* Use user settings to disable welcome page

The workspace setting seems to be recognized but if so it is having no
effect.

* Update VS Code cache step with new build directories

Co-authored-by: Asher <ash@coder.com>
2021-11-09 23:28:31 -06:00
Mauricio Garavaglia 31d5823d10
Escape HTML from messages in error page (#4430)
Co-authored-by: Asher <ash@coder.com>
Co-authored-by: Joe Previte <jjprevite@gmail.com>
2021-11-09 14:39:54 -07:00
LG 605c3c6367
Add a gist of the difference between code-server and Coder (#4419)
* Add a gist of the difference

* Update the gist

* Update README.md

As told by @tmikaeld here: https://github.com/cdr/code-server/discussions/3102#discussioncomment-1565789

* Update docs/README.md

Co-authored-by: Joe Previte <jjprevite@gmail.com>

* `yarn fmt` results

Co-authored-by: Joe Previte <jjprevite@gmail.com>
Co-authored-by: Asher <ash@coder.com>
2021-11-09 10:30:27 -07:00
Matthew Beckett 94b2774f8c
Drop duplicate Helm values from values.yaml (#4450)
* Drop duplicate Helm values from values.yaml

* Use frozen lockfile for test dependencies in CI (#4442)

* Use frozen lockfile for test dependencies in CI

This might be causing more Playwright issues.

* Bump Playwright

Mostly just to trigger a reinstall of dependencies since it is cached
and still failing.

Once updated it errors saying install needs to run so add that too.

* Drop duplicate Helm values from values.yaml

Co-authored-by: Asher <ash@coder.com>
2021-11-05 12:04:52 -07:00
renovate[bot] 4399c7df64
fix(deps): update dependency rotating-file-stream to v3 (#4451)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-11-05 12:04:10 -07:00
Asher 03651e5e0b
Use frozen lockfile for test dependencies in CI (#4442)
* Use frozen lockfile for test dependencies in CI

This might be causing more Playwright issues.

* Bump Playwright

Mostly just to trigger a reinstall of dependencies since it is cached
and still failing.

Once updated it errors saying install needs to run so add that too.
2021-11-04 11:39:27 -05:00
Joe Previte 219cad1783
Revert "Add linkup command to improve link functionality" (#4439)
This reverts commit 2504f6fce4.
2021-11-03 14:42:14 -07:00
Asher dcc9cf3dd2
Avoid npx for Playwright dependencies (#4437)
It was causing version mismatch errors.

It might make more sense to have this in the postinstall but for now I
have foregone that as it would be installed in every step including
those that do not run e2e tests.
2021-11-03 16:17:43 -05:00
Joe Previte 13404df267
chore(deps): fix issues identified by audit-ci (#4438)
* fix(deps): ansi-regex issue

* fix(deps): upgrade tar to safe version

* chore(deps): fix vm2 issue

* fix(ci): increase timeout for audit-ci to 15
2021-11-03 14:09:31 -07:00
Joe Previte 13e91dedc1
chore(deps): fix security vulnerabilities (#4428) 2021-11-03 10:32:08 -07:00
renovate[bot] 0cbf1ca58c
chore(deps): update dependency audit-ci to v5 (#4326)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-11-01 15:15:34 -07:00
Joe Previte 9c861570cd
docs(contrib): update linux requirements (#4328) 2021-11-01 15:15:22 -07:00
Joe Previte 743bab09cd
refactor(ci): only run release workflows on released event (#4288) 2021-11-01 10:06:10 -07:00
renovate[bot] 976891a2e2
chore(deps): update typescript-eslint monorepo to v5 (#4338)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-10-29 16:34:24 -07:00
Joe Previte 946e4e8843
feat(cli): add test for readSocketPath (#4284)
* fix: update isNodeJSErrnoException

* refactor(cli): export and purify readSocketPath

* feat: add tests for readSocketPath

* fix(ci): temporarily disable install deps from cache
2021-10-29 16:03:57 -07:00
Asher 49c9c191b9
Fix script test workflow triggers (#4412) 2021-10-29 14:48:21 -05:00
renovate[bot] 433a3d91dd
fix(deps): update dependency split2 to v4 (#4357)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-10-29 12:32:49 -07:00
renovate[bot] 0100d4c72a
chore(deps): update aquasecurity/trivy-action commit hash to 1ccef26 (#4401)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-10-29 02:12:26 +00:00
Jonathan Yu aa9755b8fb
Remove unused Lock config (#4304) 2021-10-28 16:21:48 -07:00
Asher 4abc525801
Update cross-build image apt before installing (#4411) 2021-10-28 15:36:21 -07:00
Joe Previte 705e821741
fix(testing): revert change & fix playwright tests (#4310)
* fix(testing): revert change & fix playwright tests

* fix(constants): add type to import statement

* refactor(e2e): delete browser test

This test was originally added to ensure playwright was working.

At this point, we know it works so removing this test because it doesn't help
with anything specific to code-server and only adds unnecessary code to the
codebase plus increases the e2e test job duration.

* chore(e2e): use 1 worker for e2e test

I don't know if it's a resources issue, playwright, or code-server but it seems
like the e2e tests choke when multiple workers are used.

This change is okay because our CI runner only has 2 cores so it would only use
1 worker anyway, but by specifying it in our playwright config, we ensure more
stability in our e2e tests working correctly.

See these PRs:
- https://github.com/cdr/code-server/pull/3263
- https://github.com/cdr/code-server/pull/4310

* revert(vscode): add missing route with redirect

* chore(vscode): update to latest fork

* Touch up compilation step.

* Bump vendor.

* Fix VS Code minification step

* Move ClientConfiguration to common

Common code must not import Node code as it is imported by the browser.

* Ensure lib directory exists before curling

cURL errors now because VS Code was moved and the directory does not
exist.

* Update incorrect e2e test help output

Revert workers change as well; this can be overridden when desired.

* Add back extension compilation step

* Include missing resources in release

This includes a favicon, for example.  I opted to include the entire
directory to make sure we do not miss anything.  Some of the other
stuff looks potentially useful (like completions).

* Set quality property in product configuration

When httpWebWorkerExtensionHostIframe.html is fetched it uses the web
endpoint template (in which we do not include the commit) but if the
quality is not set it prepends the commit to the web endpoint instead.
The new static endpoint does not use/handle commits so this 404s.

Long-term we might want to make the new static endpoint use commits like
the old one but we will also need to update the various other static
URLs to include the commit.

For now I just fixed this by adding the quality since:
  1. Probably faster than trying to find and update all static uses.
  2. VS Code probably expects it anyway.
  3. Gives us better control over the endpoint.

* Update VS Code

This fixes several build issues.

* Bump vscode.

* Bump.

* Bump.

* Use CLI directly.

* Update tests to reflect new upstream behavior.

* Move unit tests to after the build

Our code has new dependencies on VS Code that are pulled in when the
unit tests run.  Because of this we need to build VS Code before running
the unit tests (as it only pulls built code).

* Upgrade proxy-agent dependencies

This resolves a security report with one of its dependencies (vm2).

* Symlink VS Code output directory before unit tests

This is necessary now that we import from the out directory.

* Fix issues surrounding persistent processes between tests.

* Update VS Code cache directories

These were renamed so the cached paths need to be updated.  I changed
the key as well to force a rebuild.

* Move test symlink to script

This way it works for local testing as well.

I had to use out-build instead of out-vscode-server-min because Jest
throws some obscure error about a handlebars haste map.

* Fix listening on a socket

* Update VS Code

It contains fixes for missing files in the build.

* Standardize disposals

* Dispose HTTP server

Shares code with the test HTTP server.  For now it is a function but
maybe we should make it a class that is extended by tests.

* Dispose app on exit

* Fix logging link errors

Unfortunately the logger currently chokes when provided with error
objects.

Also for some reason the bracketed text was not displaying...

* Update regex used by e2e to extract address

The address was recently changed to use URL which seems to add a
trailing slash when using toString, causing the regex match to fail.

* Log browser console in e2e tests

* Add base back to login page

This is used to set cookies when using a base path.

* Remove login page test

The file this was testing no longer exists.

* Use path.posix for static base

Since this is a web path and not platform-dependent.

* Add test for invalid password

Co-authored-by: Teffen Ellis <teffen@nirri.us>
Co-authored-by: Asher <ash@coder.com>
2021-10-28 15:27:17 -05:00
Mr. Blogger 0e97a94acf
Replace nodejs with nodejs-lts in Termux docs (#4336)
nodejs installs v16 which is not compatible.  nodejs-lts currently uses v14.
2021-10-25 11:14:37 -05:00
Asher 3520ad2286
Mention that Safari needs TLS 1.2 (#4346)
* Mention that Safari needs TLS 1.2

https://github.com/cdr/code-server/issues/3850

* Delete duplicated guide sections
2021-10-18 10:31:30 -05:00
McChen ad17c7dc8e
docs: fix nfpm link in contributing (#4369)
Co-authored-by: chenjiahao <chenjiahao@jd.com>
2021-10-18 10:25:42 -05:00
Joe Previte 271bc06754
Merge pull request #4309 from cdr/jsjoeio-test-monday
feat(cli): add tests for password, hashed-password
2021-10-05 11:14:07 -07:00
Ben Potter dfc505b977
Merge pull request #4307 from cdr/bpmct/comparison
update comparisons in FAQ
2021-10-05 12:24:58 -05:00
Ben Potter ee1de79af6
fix weird format 2021-10-05 11:40:57 -05:00
Ben Potter fa6f47965a
Update docs/FAQ.md
Co-authored-by: Ashley McClelland <ashley@coder.com>
2021-10-05 11:31:40 -05:00
Ben Potter 1af83a3c56
Update docs/FAQ.md
Co-authored-by: Ashley McClelland <ashley@coder.com>
2021-10-05 11:31:34 -05:00
Ben Potter 8745f1aa63
Update docs/FAQ.md
Co-authored-by: Ashley McClelland <ashley@coder.com>
2021-10-05 11:31:29 -05:00
Ben Potter bb1799ba04
Update docs/FAQ.md
Co-authored-by: Ashley McClelland <ashley@coder.com>
2021-10-05 11:31:24 -05:00
Ben Potter 70fbfc7ada
Update docs/FAQ.md
Co-authored-by: Ashley McClelland <ashley@coder.com>
2021-10-05 11:31:17 -05:00
Tian Jian 59f667ec14
Fix: use pipe to spawn child process to re-enable file logging (#4293)
We pipe the child's stdout and stderr to the log file (and to the parent's streams) but since we used `inherit` for `stdio` this caused the child to use the parent's streams directly which made `child.stdout` and `child.stderr` non-existent and thus we had no file logging.

Using `pipe` creates stdin and stderr on the child.
2021-10-05 10:55:40 -05:00
Ben Potter 816152ddbd
fix typo 2021-10-04 17:41:36 -05:00
Joe Previte 45319ec648
woo! 2021-10-04 15:04:07 -07:00
Joe Previte e588f8b0b0
feat(cli): add tests for password, hashed-password 2021-10-04 14:51:14 -07:00
Ben Potter 7c26a82a17
format 2021-10-04 14:57:58 -05:00
Ben Potter 75e44fdc84
update comparisons in FAQ 2021-10-04 14:48:31 -05:00
Joe Previte 8a4ed5a2e0
Merge pull request #4306 from cdr/jsjoeio-fix-broken-tests
fix: revert CI change + fix broken tests
2021-10-04 12:20:31 -07:00
Joe Previte a8d873f9f8
refactor(ci): fix unit tests job 2021-10-04 12:07:45 -07:00
Joe Previte 7183b5a43e
chore(testing): fix casing in error messsage 2021-10-04 12:06:33 -07:00
Joe Previte 8136769b66
fix: close vscodeServer on server close if defined 2021-10-04 12:06:12 -07:00
Teffen Ellis 672038c6af Fix issue where marketplace is undefined.
- Fix error logging from child process.
2021-10-04 13:26:16 -04:00
Joe Previte 2d7aeb5bf9
Merge pull request #4290 from cdr/jsjoeio-vscode
chore(vscode): update to latest commit of fork
2021-10-01 14:57:02 -07:00
Joe Previte f342c327f3
chore(vscode): update to latest commit of fork 2021-10-01 14:47:20 -07:00
Joe Previte 4619786019
Merge pull request #4274 from cdr/jsjoeio-testing-guidelines
docs: update section around testing
2021-10-01 11:15:15 -07:00
Joe Previte 1dfcd0448e
Merge pull request #4273 from cdr/jsjoeio-bump-helm-chart
chore(helm-chart): bump to 1.0.5
2021-10-01 11:10:21 -07:00
Joe Previte 48ce49eec8
Merge pull request #4281 from toddewilliams/patch-1
Update collaboration.md
2021-09-30 16:43:48 -07:00
Teffen Ellis d8c344beda Refactor vscode endpoints to use fork directly. 2021-09-30 19:14:17 -04:00
Teffen Ellis beebf53adc Add linkup command to improve link functionality 2021-09-30 19:14:17 -04:00
Joe Previte a14fa862da
fixup! Update collaboration.md 2021-09-30 15:23:16 -07:00
Joe Previte b9989ca2db
docs(MAINTAINING): add Testing section 2021-09-29 15:02:11 -07:00
Joe Previte 4e310b4985
docs(CONTRIBUTING): add scrip tests section 2021-09-29 15:02:11 -07:00
Joe Previte d54b4accac
fix 2021-09-29 14:39:48 -07:00
Joe Previte 5e7b419820
chore: bump helm-chart version to 1.0.5
We've had two patches to the helm-chart since the last time we bumped the Chart
version.

- 1ffca5751c
- 5a36627aae

This version bump ensures that chart version has the correct date.
2021-09-29 14:38:13 -07:00
159 changed files with 5188 additions and 8877 deletions

View File

@ -36,11 +36,8 @@ rules:
import/order: import/order:
[error, { alphabetize: { order: "asc" }, groups: [["builtin", "external", "internal"], "parent", "sibling"] }] [error, { alphabetize: { order: "asc" }, groups: [["builtin", "external", "internal"], "parent", "sibling"] }]
no-async-promise-executor: off no-async-promise-executor: off
# This isn't a real module, just types, which apparently doesn't resolve.
import/no-unresolved: [error, { ignore: ["express-serve-static-core"] }]
settings: settings:
# Does not work with CommonJS unfortunately. import/resolver:
import/ignore: typescript:
- env-paths alwaysTryTypes: true
- xdg-basedir

2
.github/CODEOWNERS vendored
View File

@ -1,3 +1,3 @@
* @cdr/code-server-reviewers * @coder/code-server-reviewers
ci/helm-chart/ @Matthew-Beckett @alexgorbatchev ci/helm-chart/ @Matthew-Beckett @alexgorbatchev

View File

@ -1,79 +0,0 @@
---
name: Bug report
about: Report a bug and help us improve
title: ""
labels: ""
assignees: ""
---
<!--
Hi there! 👋
Thanks for reporting a bug.
Please search for existing issues before filing, as they may contain additional
information about the problem and descriptions of workarounds. Provide as much
information as you can, so that we can reproduce the issue. Otherwise, we may
not be able to help diagnose the problem, and may close the issue as
unreproducible or incomplete. For visual defects, please include screenshots to
help us understand the issue.
-->
## OS/Web Information
- Web Browser:
- Local OS:
- Remote OS:
- Remote Architecture:
- `code-server --version`:
## Steps to Reproduce
1.
2.
3.
## Expected
<!-- What should happen? -->
## Actual
<!-- What actually happens? -->
## Logs
<!--
First run code-server with at least debug logging (or trace to be really
thorough) by setting the --log flag or the LOG_LEVEL environment variable. -vvv
and --verbose are aliases for --log trace. For example:
code-server --log debug
Once this is done, replicate the issue you're having then collect logging
information from the following places:
1. The most recent files from ~/.local/share/code-server/coder-logs.
2. The browser console.
3. The browser network tab.
Additionally, collecting core dumps (you may need to enable them first) if
code-server crashes can be helpful.
-->
<!--
If you're having issues with installation please include the installation logs
i.e. the output of `yarn global add code-server` if you installed with `yarn`
-->
## Screenshot
<!-- Ideally provide a screenshot, gif, video or screen recording. -->
## Notes
<!-- If you can reproduce the issue on vanilla VS Code,
please file the issue at the VS Code repository instead. -->
This issue can be reproduced in VS Code: Yes/No

86
.github/ISSUE_TEMPLATE/bug-report.yml vendored Normal file
View File

@ -0,0 +1,86 @@
name: Bug report
description: File a bug report
title: "[Bug]: "
labels: ["bug", "triage"]
body:
- type: checkboxes
attributes:
label: Is there an existing issue for this?
description: Please search to see if an issue already exists for the bug you encountered.
options:
- label: I have searched the existing issues
required: true
- type: textarea
attributes:
label: OS/Web Information
description: |
examples:
- **Web Browser**: Chrome
- **Local OS**: macOS
- **Remote OS**: Ubuntu
- **Remote Architecture**: amd64
- **`code-server --version`**: 4.0.1
value: |
- Web Browser:
- Local OS:
- Remote OS:
- Remote Architecture:
- `code-server --version`:
validations:
required: true
- type: textarea
attributes:
label: Steps to Reproduce
description: |
1. open code-server
2. install extension
3. run command
value: |
1.
2.
3.
validations:
required: true
- type: textarea
attributes:
label: Expected
description: What should happen?
validations:
required: true
- type: textarea
attributes:
label: Actual
description: What actually happens?
validations:
required: true
- type: textarea
id: logs
attributes:
label: Logs
description: Run code-server with the --verbose flag and then paste any relevant logs from the server, from the browser console and/or the browser network tab. For issues with installation, include installation logs (i.e. output of `yarn global add code-server`).
- type: textarea
attributes:
label: Screenshot/Video
description: Please include a screenshot, gif or screen recording of your issue.
validations:
required: false
- type: checkboxes
attributes:
label: Does this issue happen in VS Code?
description: Please try reproducing this issue in VS Code
options:
- label: I cannot reproduce this in VS Code.
required: true
- type: checkboxes
attributes:
label: Are you accessing code-server over HTTPS?
description: code-server relies on service workers for many features. Double-check that you are using HTTPS.
options:
- label: I am using HTTPS.
required: true
- type: textarea
attributes:
label: Notes
description: Please include any addition notes that will help us resolve this issue.
validations:
required: false

View File

@ -1,8 +1,8 @@
blank_issues_enabled: false blank_issues_enabled: false
contact_links: contact_links:
- name: Question - name: Question?
url: https://github.com/cdr/code-server/discussions/new?category_id=22503114 url: https://github.com/coder/code-server/discussions/new?category_id=22503114
about: Ask the community for help on our GitHub Discussions board about: Ask the community for help on our GitHub Discussions board
- name: Chat - name: code-server Slack Community
about: Need immediate help or just want to talk? Hop in our Slack about: Need immediate help or just want to talk? Hop in our Slack. Note - this Slack is not actively monitored by code-server maintainers.
url: https://cdr.co/join-community url: https://cdr.co/join-community

View File

@ -1,7 +1,13 @@
--- ---
name: Documentation improvement name: Documentation improvement
about: Suggest a documentation improvement about: Suggest a documentation improvement
title: "" title: "[Docs]: "
labels: "docs" labels: "docs"
assignees: "" assignees: "@jsjoeio"
--- ---
## What is your suggestion?
## How will this improve the docs?
## Are you interested in submitting a PR for this?

View File

@ -1,18 +0,0 @@
---
name: Extension request
about: Request an extension missing from the code-server marketplace
title: ""
labels: extension-request
assignees: ""
---
<!--
Details on the code-server extension marketplace are at
https://github.com/cdr/code-server/blob/master/docs/FAQ.md#whats-the-deal-with-extensions
Please fill in the issue template!
-->
- [ ] Extension name:
- [ ] Extension GitHub or homepage:

View File

@ -1,13 +1,15 @@
--- ---
name: Feature request name: Feature request
about: Suggest an idea about: Suggest an idea to improve code-server
title: "" title: "[Feat]: "
labels: feature labels: enhancement
assignees: "" assignees: ""
--- ---
<!-- ## What is your suggestion?
Please search for existing issues before filing.
Please describe the feature as clearly as possible! ## Why do you want this feature?
-->
## Are there any workarounds to get this functionality today?
## Are you interested in submitting a PR for this?

View File

@ -1,16 +0,0 @@
---
name: Release
about: "*For maintainers only*"
title: "release: 0.0.0"
labels: ""
assignees: "@cdr/code-server-reviewers"
---
<!-- Maintainer: fill out the checklist -->
## Checklist
- [ ] Assign to next release manager
- [ ] Close previous release milestone
- [ ] Create next release milestone
- [ ] Associate issue with next release milestone

8
.github/codecov.yml vendored
View File

@ -8,6 +8,14 @@ coverage:
range: "40...70" range: "40...70"
status: status:
patch: off patch: off
notify:
slack:
default:
url: secret:v1::tXC7VwEIKYjNU8HRgRv2GdKOSCt5UzpykKZb+o1eCDqBgb2PEqwE3A26QUPYMLo4BO2qtrJhFIvwhUvlPwyzDCNGoNiuZfXr0UeZZ0y1TcZu672R/NBNMwEPO/e1Ye0pHxjzKHnuH7HqbjFucox/RBQLtiL3J56SWGE3JtbkC6o=
threshold: 1%
only_pulls: false
branches:
- "main"
parsers: parsers:
gcov: gcov:

37
.github/lock.yml vendored
View File

@ -1,37 +0,0 @@
# Configuration for Lock Threads - https://github.com/dessant/lock-threads-app
# Number of days of inactivity before a closed issue or pull request is locked
daysUntilLock: 90
# Skip issues and pull requests created before a given timestamp. Timestamp must
# follow ISO 8601 (`YYYY-MM-DD`). Set to `false` to disable
skipCreatedBefore: false
# Issues and pull requests with these labels will be ignored. Set to `[]` to disable
exemptLabels: []
# Label to add before locking, such as `outdated`. Set to `false` to disable
lockLabel: false
# Comment to post before locking. Set to `false` to disable
lockComment: >
This thread has been automatically locked since there has not been
any recent activity after it was closed. Please open a new issue for
related bugs.
# Assign `resolved` as the reason for locking. Set to `false` to disable
setLockReason: true
# Limit to only `issues` or `pulls`
# only: issues
# Optionally, specify configuration settings just for `issues` or `pulls`
# issues:
# exemptLabels:
# - help-wanted
# lockLabel: outdated
# pulls:
# daysUntilLock: 30
# Repository to extend settings from
# _extends: repo

12
.github/ranger.yml vendored
View File

@ -19,18 +19,6 @@ labels:
action: comment action: comment
delay: 5s delay: 5s
message: "Thanks for making your first contribution! :slightly_smiling_face:" message: "Thanks for making your first contribution! :slightly_smiling_face:"
extension-request:
action: close
delay: 5s
comment: >
Thanks for opening an extension request!
We are currently in the process of switching extension
marketplaces and transitioning over to [Open VSX](https://open-vsx.org/).
Once https://github.com/eclipse/openvsx/issues/249 is implemented, we
can fully make this transition. Therefore, we are no longer accepting
new requests for extension requests. We suggest installing the VSIX
file and then installing into code-server as a temporary workaround.
See [docs](https://github.com/cdr/code-server/blob/main/docs/FAQ.md#installing-vsix-extensions-via-the-command-line) for more info.
"upstream:vscode": "upstream:vscode":
action: close action: close
delay: 5s delay: 5s

63
.github/semantic.yaml vendored Normal file
View File

@ -0,0 +1,63 @@
###############################################################################
# This file configures "Semantic Pull Requests", which is documented here:
# https://github.com/zeke/semantic-pull-requests
###############################################################################
# Scopes are optionally supplied after a 'type'. For example, in
#
# feat(docs): autostart ui
#
# '(docs)' is the scope. Scopes are used to signify where the change occurred.
scopes:
# docs: changes to the code-server documentation.
- docs
# vendor: changes to vendored dependencies.
- vendor
# deps: changes to code-server's dependencies.
- deps
# cs: changes to code specific to code-server.
- cs
# cli: changes to the command-line interface.
- cli
# We only check that the PR title is semantic. The PR title is automatically
# applied to the "Squash & Merge" flow as the suggested commit message, so this
# should suffice unless someone drastically alters the message in that flow.
titleOnly: true
# Types are the 'tag' types in a commit or PR title. For example, in
#
# chore: fix thing
#
# 'chore' is the type.
types:
# A build of any kind.
- build
# A user-facing change that corrects a defect in code-server.
- fix
# Any code task that is ignored for changelog purposes. Examples include
# devbin scripts and internal-only configurations.
- chore
# Any work performed on CI.
- ci
# Work that directly implements or supports the implementation of a feature.
- feat
# A refactor changes code structure without any behavioral change.
- refactor
# A git revert for any style of commit.
- revert
# Adding tests of any kind. Should be separate from feature or fix
# implementations. For example, if a commit adds a fix + test, it's a fix
# commit. If a commit is simply bumping coverage, it's a test commit.
- test

View File

@ -19,8 +19,6 @@ jobs:
name: Pre-build checks name: Pre-build checks
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 15 timeout-minutes: 15
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
steps: steps:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@v2 uses: actions/checkout@v2
@ -33,17 +31,21 @@ jobs:
- name: Install helm - name: Install helm
uses: azure/setup-helm@v1.1 uses: azure/setup-helm@v1.1
- name: Fetch dependencies from cache # NOTE@jsjoeio
id: cache-yarn # disabling this until we can audit the build process
uses: actions/cache@v2 # and the usefulness of this step
with: # See: https://github.com/coder/code-server/issues/4287
path: "**/node_modules" # - name: Fetch dependencies from cache
key: yarn-build-${{ hashFiles('**/yarn.lock') }} # id: cache-yarn
restore-keys: | # uses: actions/cache@v2
yarn-build- # with:
# path: "**/node_modules"
# key: yarn-build-${{ hashFiles('**/yarn.lock') }}
# restore-keys: |
# yarn-build-
- name: Install dependencies - name: Install dependencies
if: steps.cache-yarn.outputs.cache-hit != 'true' # if: steps.cache-yarn.outputs.cache-hit != 'true'
run: yarn --frozen-lockfile run: yarn --frozen-lockfile
- name: Run yarn fmt - name: Run yarn fmt
@ -54,19 +56,11 @@ jobs:
run: yarn lint run: yarn lint
if: success() if: success()
- name: Run code-server unit tests
run: yarn test:unit
if: success()
- name: Upload coverage report to Codecov
run: yarn coverage
if: success()
audit-ci: audit-ci:
name: Run audit-ci name: Run audit-ci
needs: prebuild needs: prebuild
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 5 timeout-minutes: 15
steps: steps:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@v2 uses: actions/checkout@v2
@ -98,6 +92,8 @@ jobs:
needs: prebuild needs: prebuild
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 30 timeout-minutes: 30
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
with: with:
@ -146,14 +142,25 @@ jobs:
path: | path: |
vendor/modules/code-oss-dev/.build vendor/modules/code-oss-dev/.build
vendor/modules/code-oss-dev/out-build vendor/modules/code-oss-dev/out-build
vendor/modules/code-oss-dev/out-vscode vendor/modules/code-oss-dev/out-vscode-reh-web
vendor/modules/code-oss-dev/out-vscode-min vendor/modules/code-oss-dev/out-vscode-reh-web-min
key: vscode-build-${{ steps.vscode-rev.outputs.rev }} key: vscode-reh-build-${{ steps.vscode-rev.outputs.rev }}
- name: Build vscode - name: Build vscode
if: steps.cache-vscode.outputs.cache-hit != 'true' if: steps.cache-vscode.outputs.cache-hit != 'true'
run: yarn build:vscode run: yarn build:vscode
# Our code imports code from VS Code's `out` directory meaning VS Code
# must be built before running these tests.
# TODO: Move to its own step?
- name: Run code-server unit tests
run: yarn test:unit
if: success()
- name: Upload coverage report to Codecov
run: yarn coverage
if: success()
# The release package does not contain any native modules # The release package does not contain any native modules
# and is neutral to architecture/os/libc version. # and is neutral to architecture/os/libc version.
- name: Create release package - name: Create release package
@ -170,6 +177,33 @@ jobs:
name: npm-package name: npm-package
path: ./package.tar.gz path: ./package.tar.gz
npm:
# the npm-package gets uploaded as an artifact in Build
# so we need that to complete before this runs
needs: build
# This environment "npm" requires someone from
# coder/code-server-reviewers to approve the PR before this job runs.
environment: npm
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/download-artifact@v2
id: download
with:
name: "npm-package"
path: release-npm-package
- name: Run ./ci/steps/publish-npm.sh
run: yarn publish:npm
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
# NOTE@jsjoeio
# NPM_ENVIRONMENT intentionally not set here.
# Instead, itis determined in publish-npm.sh script
# using GITHUB environment variables
# TODO: cache building yarn --production # TODO: cache building yarn --production
# possibly 2m30s of savings(?) # possibly 2m30s of savings(?)
# this requires refactoring our release scripts # this requires refactoring our release scripts
@ -283,7 +317,7 @@ jobs:
echo "$HOME/.local/bin" >> $GITHUB_PATH echo "$HOME/.local/bin" >> $GITHUB_PATH
- name: Install cross-compiler - name: Install cross-compiler
run: sudo apt install $PACKAGE run: sudo apt update && sudo apt install $PACKAGE
env: env:
PACKAGE: ${{ format('g++-{0}', matrix.prefix) }} PACKAGE: ${{ format('g++-{0}', matrix.prefix) }}
@ -371,9 +405,6 @@ jobs:
with: with:
node-version: "14" node-version: "14"
- name: Install playwright OS dependencies
run: npx playwright install-deps
- name: Fetch dependencies from cache - name: Fetch dependencies from cache
id: cache-yarn id: cache-yarn
uses: actions/cache@v2 uses: actions/cache@v2
@ -399,14 +430,10 @@ jobs:
if: steps.cache-yarn.outputs.cache-hit != 'true' if: steps.cache-yarn.outputs.cache-hit != 'true'
run: yarn --frozen-lockfile run: yarn --frozen-lockfile
# HACK: this shouldn't need to exist, but put it here anyway - name: Install Playwright OS dependencies
# in an attempt to solve Playwright cache failures.
- name: Reinstall playwright
if: steps.cache-yarn.outputs.cache-hit == 'true'
run: | run: |
cd test/ ./test/node_modules/.bin/playwright install-deps
rm -r node_modules/playwright ./test/node_modules/.bin/playwright install
yarn install --check-files
- name: Run end-to-end tests - name: Run end-to-end tests
run: yarn test:e2e run: yarn test:e2e
@ -428,7 +455,7 @@ jobs:
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: Run Trivy vulnerability scanner in repo mode - name: Run Trivy vulnerability scanner in repo mode
#Commit SHA for v0.0.17 #Commit SHA for v0.0.17
uses: aquasecurity/trivy-action@8eccb5539730451af599c84f444c6d6cf0fc2bb0 uses: aquasecurity/trivy-action@a7a829a4345428ddd92ca57b18257440f6a18c90
with: with:
scan-type: "fs" scan-type: "fs"
scan-ref: "." scan-ref: "."

View File

@ -6,13 +6,15 @@ on:
workflow_dispatch: workflow_dispatch:
release: release:
types: [published] types:
- released
jobs: jobs:
docker-images: docker-images:
runs-on: ubuntu-latest runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@v2 - name: Checkout
uses: actions/checkout@v2
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v1 uses: docker/setup-qemu-action@v1
@ -20,9 +22,13 @@ jobs:
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1 uses: docker/setup-buildx-action@v1
- name: Login to Docker Hub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Run ./ci/steps/docker-buildx-push.sh - name: Run ./ci/steps/docker-buildx-push.sh
run: ./ci/steps/docker-buildx-push.sh run: ./ci/steps/docker-buildx-push.sh
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ github.token }}
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}

View File

@ -21,6 +21,7 @@ jobs:
preview: preview:
name: Docs preview name: Docs preview
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
environment: CI
steps: steps:
- name: Cancel Previous Runs - name: Cancel Previous Runs
uses: styfle/cancel-workflow-action@0.9.1 uses: styfle/cancel-workflow-action@0.9.1
@ -28,9 +29,9 @@ jobs:
- name: Checkout m - name: Checkout m
uses: actions/checkout@v2 uses: actions/checkout@v2
with: with:
repository: cdr/m repository: coder/m
ref: refs/heads/master ref: refs/heads/master
token: ${{ secrets.GH_ACCESS_TOKEN }} ssh-key: ${{ secrets.READONLY_M_DEPLOY_KEY }}
submodules: true submodules: true
fetch-depth: 0 fetch-depth: 0

View File

@ -5,10 +5,12 @@ on:
branches: branches:
- main - main
paths: paths:
- "installer.sh" - "install.sh"
pull_request: pull_request:
branches: branches:
- main - main
paths:
- "install.sh"
jobs: jobs:
ubuntu: ubuntu:

View File

@ -6,7 +6,7 @@ on:
workflow_dispatch: workflow_dispatch:
release: release:
types: [published] types: [released]
jobs: jobs:
# NOTE: this job requires curl, jq and yarn # NOTE: this job requires curl, jq and yarn
@ -16,11 +16,18 @@ jobs:
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Run ./ci/steps/publish-npm.sh - uses: actions/download-artifact@v2
run: ./ci/steps/publish-npm.sh id: download
with:
name: "npm-package"
path: release-npm-package
- name: Publish npm package and tag with "latest"
run: yarn publish:npm
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
NPM_ENVIRONMENT: "production"
homebrew: homebrew:
# The newest version of code-server needs to be available on npm when this runs # The newest version of code-server needs to be available on npm when this runs

View File

@ -5,10 +5,14 @@ on:
branches: branches:
- main - main
paths: paths:
- "installer.sh" - "**.sh"
- "**.bats"
pull_request: pull_request:
branches: branches:
- main - main
paths:
- "**.sh"
- "**.bats"
jobs: jobs:
test: test:

1
.nvmrc
View File

@ -1 +0,0 @@
.node-version

View File

@ -2,3 +2,16 @@ printWidth: 120
semi: false semi: false
trailingComma: all trailingComma: all
arrowParens: always arrowParens: always
singleQuote: false
useTabs: false
overrides:
# Attempt to keep VScode's existing code style intact.
- files: "vendor/modules/code-oss-dev/**/*.ts"
options:
# No limit defined upstream.
printWidth: 10000
semi: true
singleQuote: true
useTabs: true
arrowParens: avoid

View File

@ -50,7 +50,7 @@
{ {
"file": "src/node/heart.ts", "file": "src/node/heart.ts",
"line": 7, "line": 7,
"description": "code-server's heart beats to indicate recent activity.\n\nAlso documented here: [https://github.com/cdr/code-server/blob/master/docs/FAQ.md#heartbeat-file](https://github.com/cdr/code-server/blob/master/docs/FAQ.md#heartbeat-file)" "description": "code-server's heart beats to indicate recent activity.\n\nAlso documented here: [https://github.com/coder/code-server/blob/master/docs/FAQ.md#heartbeat-file](https://github.com/coder/code-server/blob/master/docs/FAQ.md#heartbeat-file)"
}, },
{ {
"file": "src/node/socket.ts", "file": "src/node/socket.ts",
@ -80,12 +80,12 @@
{ {
"file": "src/node/routes/domainProxy.ts", "file": "src/node/routes/domainProxy.ts",
"line": 18, "line": 18,
"description": "code-server provides a built-in proxy to help in developing web-based applications. This is the code for the domain-based proxy.\n\nAlso documented here: [https://github.com/cdr/code-server/blob/master/docs/FAQ.md#how-do-i-securely-access-web-services](https://github.com/cdr/code-server/blob/master/docs/FAQ.md#how-do-i-securely-access-web-services)" "description": "code-server provides a built-in proxy to help in developing web-based applications. This is the code for the domain-based proxy.\n\nAlso documented here: [https://github.com/coder/code-server/blob/master/docs/FAQ.md#how-do-i-securely-access-web-services](https://github.com/coder/code-server/blob/master/docs/FAQ.md#how-do-i-securely-access-web-services)"
}, },
{ {
"file": "src/node/routes/pathProxy.ts", "file": "src/node/routes/pathProxy.ts",
"line": 19, "line": 19,
"description": "Here is the path-based version of the proxy.\n\nAlso documented here: [https://github.com/cdr/code-server/blob/master/docs/FAQ.md#how-do-i-securely-access-web-services](https://github.com/cdr/code-server/blob/master/docs/FAQ.md#how-do-i-securely-access-web-services)" "description": "Here is the path-based version of the proxy.\n\nAlso documented here: [https://github.com/coder/code-server/blob/master/docs/FAQ.md#how-do-i-securely-access-web-services](https://github.com/coder/code-server/blob/master/docs/FAQ.md#how-do-i-securely-access-web-services)"
}, },
{ {
"file": "src/node/proxy.ts", "file": "src/node/proxy.ts",
@ -95,7 +95,7 @@
{ {
"file": "src/node/routes/health.ts", "file": "src/node/routes/health.ts",
"line": 5, "line": 5,
"description": "A simple endpoint that lets you see if code-server is up.\n\nAlso documented here: [https://github.com/cdr/code-server/blob/master/docs/FAQ.md#healthz-endpoint](https://github.com/cdr/code-server/blob/master/docs/FAQ.md#healthz-endpoint)" "description": "A simple endpoint that lets you see if code-server is up.\n\nAlso documented here: [https://github.com/coder/code-server/blob/master/docs/FAQ.md#healthz-endpoint](https://github.com/coder/code-server/blob/master/docs/FAQ.md#healthz-endpoint)"
}, },
{ {
"file": "src/node/routes/login.ts", "file": "src/node/routes/login.ts",
@ -145,7 +145,7 @@
{ {
"directory": "vendor/modules/code-oss-dev", "directory": "vendor/modules/code-oss-dev",
"line": 1, "line": 1,
"description": "code-server makes use of VS Code's frontend web/remote support. Most of the modifications implement the remote server since that portion of the code is closed source and not released with VS Code.\n\nWe also have a few bug fixes and have added some features (like client-side extensions). See [https://github.com/cdr/code-server/blob/master/docs/CONTRIBUTING.md#modifications-to-vs-code](https://github.com/cdr/code-server/blob/master/docs/CONTRIBUTING.md#modifications-to-vs-code) for a list.\n\nWe make an effort to keep the modifications as few as possible." "description": "code-server makes use of VS Code's frontend web/remote support. Most of the modifications implement the remote server since that portion of the code is closed source and not released with VS Code.\n\nWe also have a few bug fixes and have added some features (like client-side extensions). See [https://github.com/coder/code-server/blob/master/docs/CONTRIBUTING.md#modifications-to-vs-code](https://github.com/coder/code-server/blob/master/docs/CONTRIBUTING.md#modifications-to-vs-code) for a list.\n\nWe make an effort to keep the modifications as few as possible."
} }
] ]
} }

View File

@ -20,7 +20,7 @@
{ {
"file": "src/node/app.ts", "file": "src/node/app.ts",
"line": 62, "line": 62,
"description": "## That's it!\n\n\nThat's all there is to it! When this tour ends, your terminal session may stop, but just use `yarn watch` to start developing from here on out!\n\n\nIf you haven't already, be sure to check out these resources:\n- [Tour: Contributing](command:codetour.startTourByTitle?[\"Contributing\")\n- [Docs: FAQ.md](https://github.com/cdr/code-server/blob/master/docs/FAQ.md)\n- [Docs: CONTRIBUTING.md](https://github.com/cdr/code-server/blob/master/docs/CONTRIBUTING.md)\n- [Community: GitHub Discussions](https://github.com/cdr/code-server/discussions)\n- [Community: Slack](https://community.coder.com)" "description": "## That's it!\n\n\nThat's all there is to it! When this tour ends, your terminal session may stop, but just use `yarn watch` to start developing from here on out!\n\n\nIf you haven't already, be sure to check out these resources:\n- [Tour: Contributing](command:codetour.startTourByTitle?[\"Contributing\")\n- [Docs: FAQ.md](https://github.com/coder/code-server/blob/master/docs/FAQ.md)\n- [Docs: CONTRIBUTING.md](https://github.com/coder/code-server/blob/master/docs/CONTRIBUTING.md)\n- [Community: GitHub Discussions](https://github.com/coder/code-server/discussions)\n- [Community: Slack](https://community.coder.com)"
} }
] ]
} }

View File

@ -1,175 +1,161 @@
# Changelog # Changelog
<!-- All notable changes to this project will be documented in this file.
This should be updated on every PR. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
We copy from here into the release notes. <!-- Example:
--> ## [9.99.999] - 9090-09-09
<!-- VS Code v99.99.999
Add next version above previous version but below this line using the template
## Next Version ### Changed
### Added
VS Code v0.00.0 ### Deprecated
### Removed
### New Features ### Fixed
### Security
- item
### Bug Fixes
- fix(socket): did this thing #321 @githubuser
### Documentation
- item
### Development
- item
--> -->
## Next Version ## [Unreleased](https://github.com/coder/code-server/releases)
VS Code v0.00.0 VS Code v0.00.0
### New Features ### Changed
- item - Add here
### Bug Fixes ## [4.0.2](https://github.com/coder/code-server/releases/tag/v4.0.2) - 2022-01-27
- Fix logout when using a base path (#3608) VS Code v1.63.0
### Documentation ### Fixed
- docs: add Pomerium #3424 @desimone - Unset the `BROWSER` environment variable. This fixes applications that hard
- docs: fix confusing sentence in pull requests section #3460 @shiv-tyagi exit when trying to spawn the helper script `BROWSER` points to because the
- docs: remove toc from changelog @oxy @jsjoeio file is missing. While we do include the script now we are leaving the
- docs(MAINTAINING): add information about CHANGELOG #3467 @jsjoeio variable omitted because the script does not work yet.
- docs: move release process to MAINTAINING.md #3441 @oxy @Prashant168
- docs: format 'Caddy' from guide.md @PisecesPeng
### Development ## [4.0.1](https://github.com/coder/code-server/releases/tag/v4.0.1) - 2022-01-04
- chore: cross-compile docker images with buildx #3166 @oxy VS Code v1.63.0
- chore: update node to v14 #3458 @oxy
- chore: update .gitignore #3557 @cuining
- fix: use sufficient computational effort for password hash #3422 @jsjoeio
- docs(CONTRIBUTING): add section on testing #3629 @jsjoeio
### Development code-server has been rebased on upstream's newly open-sourced server
implementation (#4414).
- fix(publish): update cdrci fork in brew-bump.sh #3468 @jsjoeio ### Changed
- chore(dev): migrate away from parcel #3578 @jsjoeio
## 3.10.2 - Web socket compression has been made the default (when supported). This means
the `--enable` flag will no longer take `permessage-deflate` as an option.
- The static endpoint can no longer reach outside code-server. However the
vscode-remote-resource endpoint still can.
- OpenVSX has been made the default marketplace.
- The last opened folder/workspace is no longer stored separately in the
settings file (we rely on the already-existing query object instead).
- The marketplace override environment variables `SERVICE_URL` and `ITEM_URL`
have been replaced with a single `EXTENSIONS_GALLERY` variable that
corresponds to `extensionsGallery` in VS Code's `product.json`.
### Added
- `VSCODE_PROXY_URI` env var for use in the terminal and extensions.
### Removed
- Extra extension directories have been removed. The `--extra-extensions-dir`
and `--extra-builtin-extensions-dir` flags will no longer be accepted.
- The `--install-source` flag has been removed.
### Deprecated
- `--link` is now deprecated (#4562).
### Security
- We fixed a XSS vulnerability by escaping HTML from messages in the error page (#4430).
## [3.12.0](https://github.com/coder/code-server/releases/tag/v3.12.0) - 2021-09-15
VS Code v1.60.0
### Changed
- Upgrade VS Code to 1.60.0.
### Fixed
- Fix logout when using a base path (#3608).
## [3.11.1](https://github.com/coder/code-server/releases/tag/v3.11.1) - 2021-08-06
Undocumented (see releases page).
## [3.11.0](https://github.com/coder/code-server/releases/tag/v3.11.0) - 2021-06-14
Undocumented (see releases page).
## [3.10.2](https://github.com/coder/code-server/releases/tag/v3.10.2) - 2021-05-21
VS Code v1.56.1 VS Code v1.56.1
### New Features ### Added
- feat: support `extraInitContainers` in helm chart values #3393 @strowk - Support `extraInitContainers` in helm chart values (#3393).
- feat: change `extraContainers` to support templating in helm chart #3393 @strowk
### Bug Fixes ### Changed
- fix: use correct command to Open Folder on Welcome page #3437 @jsjoeio - Change `extraContainers` to support templating in helm chart (#3393).
### Development ### Fixed
- fix(ci): update brew-bump.sh to update remote first #3438 @jsjoeio - Fix "Open Folder" on welcome page (#3437).
## 3.10.1 ## [3.10.1](https://github.com/coder/code-server/releases/tag/v3.10.1) - 2021-05-17
VS Code v1.56.1 VS Code v1.56.1
### Bug Fixes ### Fixed
- fix: Check the logged user instead of $USER #3330 @videlanicolas - Check the logged user instead of $USER (#3330).
- fix: Fix broken node_modules.asar symlink in npm package #3355 @code-asher - Fix broken node_modules.asar symlink in npm package (#3355).
- fix: Update cloud agent to fix version issue #3342 @oxy - Update cloud agent to fix version issue (#3342).
### Documentation ### Changed
- docs(install): add raspberry pi section #3376 @jsjoeio - Use xdgBasedir.runtime instead of tmp (#3304).
- docs(maintaining): add pull requests section #3378 @jsjoeio
- docs(maintaining): add merge strategies section #3379 @jsjoeio
- refactor: move default PR template #3375 @jsjoeio
- docs(contributing): add commits section #3377 @jsjoeio
### Development ## [3.10.0](https://github.com/coder/code-server/releases/tag/v3.10.0) - 2021-05-10
- chore: ignore updates to microsoft/playwright-github-action
- fix(socket): use xdgBasedir.runtime instead of tmp #3304 @jsjoeio
- fix(ci): re-enable trivy-scan-repo #3368 @jsjoeio
## 3.10.0
VS Code v1.56.0 VS Code v1.56.0
### New Features ### Changed
- feat: minor connections refactor #3178 @code-asher - Update to VS Code 1.56.0 (#3269).
- feat(security): add code-scanning with CodeQL #3229 @jsjoeio - Minor connections refactor (#3178). Improves connection stability.
- feat(ci): add trivy job for security #3261 @jsjoeio - Use ptyHostService (#3308). This brings us closer to upstream VS Code.
- feat(vscode): update to version 1.56.0 #3269 @oxy
- feat: use ptyHostService #3308 @code-asher
### Bug Fixes ### Added
- fix(socket): did this thing #321 @githubuser - Add flag for toggling permessage-deflate (#3286). The default is off so
- fix(login): rate limiter shouldn't count successful logins #3141 @jsjoeio compression will no longer be used by default. Use the --enable flag to
- chore(lib/vscode): update netmask #3187 @oxy toggle it back on.
- chore(deps): update dependencies with CVEs #3223 @oxy
- fix: refactor logout #3277 @code-asher
- fix: add flag for toggling permessage-deflate #3286 @code-asher
- fix: make sure directories exist #3309 @code-asher
### Documentation ### Fixed
- docs(FAQ): add mention of sysbox #3087 @bpmct - Make rate limiter not count against successful logins (#3141).
- docs: add security policy #3148 @jsjoeio - Refactor logout (#3277). This fixes logging out in some scenarios.
- docs(guide.md): add `caddy` example for serving from sub-path #3217 @catthehacker - Make sure directories exist (#3309). This fixes some errors on startup.
- docs: revamp debugging section #3224 @code-asher
- docs(readme): refactor to use codecov shield #3227 @jsjoeio
- docs(maintaining): use milestones over boards #3228 @jsjoeio
- docs(faq): add entry for accessing OSX folders #3247 @bpmct
- docs(termux): add workaround for Android backspace issue #3251 @jsjoeio
- docs(maintaining): add triage to workflow #3284 @jsjoeio
- docs(security): add section for tools #3287 @jsjoeio
- docs(maintaining): add versioning #3288 @jsjoeio
- docs: add changelog #3337 @jsjoeio
### Development ### Security
- fix(update-vscode): add check/docs for git-subtree #3129 @oxy - Update dependencies with CVEs (#3223).
- refactor(testing): migrate to playwright-test from jest-playwright #3133 @jsjoeio
- refactor(ci): remove unmaintained CI images and update release workflow #3147 @oxy
- chore(ci): migrate from hub to gh #3168 @oxy
- feat(testing): add e2e tests for code-server and terminal #3169 @jsjoeio
- chore(ranger): fix syntax for extension-request #3172 @oxy
- feat(testing): add codecov to generate test coverage reports #3194 @jsjoeio
- feat(testing): add tests for registerServiceWorker #3200 @jsjoeio
- refactor(testing): fix flaky terminal test #3230 @jsjoeio
- chore: ignore 15.x @types/node updates #3244 @jsjoeio
- chore(build): compile vscode+extensions in parallel #3250 @oxy
- fix(deps): remove eslint-plugin-jest-playwright #3260 @jsjoeio
- fix(testing): reduce flakiness of terminal.test.ts and use 1 worker for e2e tests #3263 @jsjoeio
- feat(testing): add isConnected check #3271 @jsjoeio
- feat(testing): add test for src/node/constants.ts #3290 @jsjoeio
- feat: test static route #3297 @code-asher
- refactor(ci): split audit from prebuild #3298 @oxy
- chore(lib/vscode): cleanup/update build deps #3314 @oxy
- fix(build): download correct cloud-agent for arch #3331 @oxy
- fix: xmldom and underscore #3332 @oxy
## Previous versions ## Previous versions
This was added with `3.10.0`, which means any previous versions are not documented in the changelog. This was added with `3.10.0`, which means any previous versions are not
documented in the changelog.
To see those, please visit the [Releases page](https://github.com/cdr/code-server/releases). To see those, please visit the [Releases page](https://github.com/coder/code-server/releases).

View File

@ -15,23 +15,21 @@ main() {
chmod +x out/node/entry.js chmod +x out/node/entry.js
fi fi
# for arch; we do not use OS from lib.sh and get our own.
# lib.sh normalizes macos to darwin - but cloud-agent's binaries do not
source ./ci/lib.sh
OS="$(uname | tr '[:upper:]' '[:lower:]')"
mkdir -p ./lib
if ! [ -f ./lib/coder-cloud-agent ]; then if ! [ -f ./lib/coder-cloud-agent ]; then
echo "Downloading the cloud agent..." echo "Downloading the cloud agent..."
# for arch; we do not use OS from lib.sh and get our own.
# lib.sh normalizes macos to darwin - but cloud-agent's binaries do not
source ./ci/lib.sh
OS="$(uname | tr '[:upper:]' '[:lower:]')"
set +e set +e
curl -fsSL "https://github.com/cdr/cloud-agent/releases/latest/download/cloud-agent-$OS-$ARCH" -o ./lib/coder-cloud-agent curl -fsSL "https://github.com/coder/cloud-agent/releases/latest/download/cloud-agent-$OS-$ARCH" -o ./lib/coder-cloud-agent
chmod +x ./lib/coder-cloud-agent chmod +x ./lib/coder-cloud-agent
set -e set -e
fi fi
yarn browserify out/browser/register.js -o out/browser/register.browserified.js
yarn browserify out/browser/pages/login.js -o out/browser/pages/login.browserified.js
yarn browserify out/browser/pages/vscode.js -o out/browser/pages/vscode.browserified.js
} }
main "$@" main "$@"

View File

@ -67,7 +67,7 @@ EOF
bundle_vscode() { bundle_vscode() {
mkdir -p "$VSCODE_OUT_PATH" mkdir -p "$VSCODE_OUT_PATH"
rsync "$VSCODE_SRC_PATH/yarn.lock" "$VSCODE_OUT_PATH" rsync "$VSCODE_SRC_PATH/yarn.lock" "$VSCODE_OUT_PATH"
rsync "$VSCODE_SRC_PATH/out-vscode${MINIFY:+-min}/" "$VSCODE_OUT_PATH/out" rsync "$VSCODE_SRC_PATH/out-vscode-reh-web${MINIFY:+-min}/" "$VSCODE_OUT_PATH/out"
rsync "$VSCODE_SRC_PATH/.build/extensions/" "$VSCODE_OUT_PATH/extensions" rsync "$VSCODE_SRC_PATH/.build/extensions/" "$VSCODE_OUT_PATH/extensions"
if [ "$KEEP_MODULES" = 0 ]; then if [ "$KEEP_MODULES" = 0 ]; then
@ -79,9 +79,15 @@ bundle_vscode() {
rsync "$VSCODE_SRC_PATH/extensions/yarn.lock" "$VSCODE_OUT_PATH/extensions" rsync "$VSCODE_SRC_PATH/extensions/yarn.lock" "$VSCODE_OUT_PATH/extensions"
rsync "$VSCODE_SRC_PATH/extensions/postinstall.js" "$VSCODE_OUT_PATH/extensions" rsync "$VSCODE_SRC_PATH/extensions/postinstall.js" "$VSCODE_OUT_PATH/extensions"
mkdir -p "$VSCODE_OUT_PATH/resources/"{linux,web} mkdir -p "$VSCODE_OUT_PATH/resources/"
rsync "$VSCODE_SRC_PATH/resources/linux/code.png" "$VSCODE_OUT_PATH/resources/linux/code.png" rsync "$VSCODE_SRC_PATH/resources/" "$VSCODE_OUT_PATH/resources/"
rsync "$VSCODE_SRC_PATH/resources/web/callback.html" "$VSCODE_OUT_PATH/resources/web/callback.html"
# TODO: We should look into using VS Code's packaging task (see
# gulpfile.reh.js). For now copy this directory into the right spot (for some
# reason VS Code uses a different path in production).
mkdir -p "$VSCODE_OUT_PATH/bin/helpers"
rsync "$VSCODE_SRC_PATH/resources/server/bin/helpers/" "$VSCODE_OUT_PATH/bin/helpers"
chmod +x "$VSCODE_OUT_PATH/bin/helpers/browser.sh"
# Add the commit and date and enable telemetry. This just makes telemetry # Add the commit and date and enable telemetry. This just makes telemetry
# available; telemetry can still be disabled by flag or setting. # available; telemetry can still be disabled by flag or setting.
@ -89,8 +95,10 @@ bundle_vscode() {
cat << EOF cat << EOF
{ {
"enableTelemetry": true, "enableTelemetry": true,
"commit": "$(git rev-parse HEAD)", "commit": "$(cd "$VSCODE_SRC_PATH" && git rev-parse HEAD)",
"date": $(jq -n 'now | todate') "quality": "stable",
"date": $(jq -n 'now | todate'),
"codeServerVersion": "$VERSION"
} }
EOF EOF
) > "$VSCODE_OUT_PATH/product.json" ) > "$VSCODE_OUT_PATH/product.json"

View File

@ -11,11 +11,8 @@ main() {
cd vendor/modules/code-oss-dev cd vendor/modules/code-oss-dev
yarn gulp compile-build compile-extensions-build compile-extension-media # Any platform works since we have our own packaging step (for now).
yarn gulp optimize --gulpfile ./coder.js yarn gulp "vscode-reh-web-linux-x64${MINIFY:+-min}"
if [[ $MINIFY ]]; then
yarn gulp minify --gulpfile ./coder.js
fi
} }
main "$@" main "$@"

View File

@ -5,7 +5,7 @@ set -eu
# Runs code-server with the bundled node binary. # Runs code-server with the bundled node binary.
_realpath() { _realpath() {
# See https://github.com/cdr/code-server/issues/1537 on why no realpath or readlink -f. # See https://github.com/coder/code-server/issues/1537 on why no realpath or readlink -f.
script="$1" script="$1"
cd "$(dirname "$script")" cd "$(dirname "$script")"
@ -16,7 +16,7 @@ _realpath() {
&& cat package.json | grep -q '^ "name": "code-server",$'; then && cat package.json | grep -q '^ "name": "code-server",$'; then
echo "***** Please use the script in bin/code-server instead!" >&2 echo "***** Please use the script in bin/code-server instead!" >&2
echo "***** This script will soon be removed!" >&2 echo "***** This script will soon be removed!" >&2
echo "***** See the release notes at https://github.com/cdr/code-server/releases/tag/v3.4.0" >&2 echo "***** See the release notes at https://github.com/coder/code-server/releases/tag/v3.4.0" >&2
fi fi
script="$(readlink "$(basename "$script")")" script="$(readlink "$(basename "$script")")"

View File

@ -8,7 +8,7 @@ maintainer: "Anmol Sethi <hi@nhooyr.io>"
description: | description: |
Run VS Code in the browser. Run VS Code in the browser.
vendor: "Coder" vendor: "Coder"
homepage: "https://github.com/cdr/code-server" homepage: "https://github.com/coder/code-server"
license: "MIT" license: "MIT"
contents: contents:

View File

@ -57,7 +57,10 @@ main() {
esac esac
OS="$(uname | tr '[:upper:]' '[:lower:]')" OS="$(uname | tr '[:upper:]' '[:lower:]')"
if curl -fsSL "https://github.com/cdr/cloud-agent/releases/latest/download/cloud-agent-$OS-$ARCH" -o ./lib/coder-cloud-agent; then
mkdir -p ./lib
if curl -fsSL "https://github.com/coder/cloud-agent/releases/latest/download/cloud-agent-$OS-$ARCH" -o ./lib/coder-cloud-agent; then
chmod +x ./lib/coder-cloud-agent chmod +x ./lib/coder-cloud-agent
else else
echo "Failed to download cloud agent; --link will not work" echo "Failed to download cloud agent; --link will not work"
@ -65,7 +68,7 @@ main() {
if ! vscode_yarn; then if ! vscode_yarn; then
echo "You may not have the required dependencies to build the native modules." echo "You may not have the required dependencies to build the native modules."
echo "Please see https://github.com/cdr/code-server/blob/master/docs/npm.md" echo "Please see https://github.com/coder/code-server/blob/master/docs/npm.md"
exit 1 exit 1
fi fi

View File

@ -13,7 +13,7 @@ main() {
download_artifact release-packages ./release-packages download_artifact release-packages ./release-packages
local assets=(./release-packages/code-server*"$VERSION"*{.tar.gz,.deb,.rpm}) local assets=(./release-packages/code-server*"$VERSION"*{.tar.gz,.deb,.rpm})
EDITOR=true gh release upload "v$VERSION" "${assets[@]}" EDITOR=true gh release upload "v$VERSION" "${assets[@]}" --clobber
} }
main "$@" main "$@"

View File

@ -83,14 +83,14 @@ main() {
echo -e "Great! We'll prep a PR for updating to $CODE_SERVER_VERSION_TO_UPDATE\n" echo -e "Great! We'll prep a PR for updating to $CODE_SERVER_VERSION_TO_UPDATE\n"
$CMD rg -g '!yarn.lock' -g '!*.svg' -g '!CHANGELOG.md' --files-with-matches --fixed-strings "${CODE_SERVER_CURRENT_VERSION}" | $CMD xargs sd "$CODE_SERVER_CURRENT_VERSION" "$CODE_SERVER_VERSION_TO_UPDATE" $CMD rg -g '!yarn.lock' -g '!*.svg' -g '!CHANGELOG.md' --files-with-matches --fixed-strings "${CODE_SERVER_CURRENT_VERSION}" | $CMD xargs sd "$CODE_SERVER_CURRENT_VERSION" "$CODE_SERVER_VERSION_TO_UPDATE"
$CMD git commit -am "chore(release): bump version to $CODE_SERVER_VERSION_TO_UPDATE" $CMD git commit --no-verify -am "chore(release): bump version to $CODE_SERVER_VERSION_TO_UPDATE"
# This runs from the root so that's why we use this path vs. ../../ # This runs from the root so that's why we use this path vs. ../../
RELEASE_TEMPLATE_STRING=$(cat ./.github/PULL_REQUEST_TEMPLATE/release_template.md) RELEASE_TEMPLATE_STRING=$(cat ./.github/PULL_REQUEST_TEMPLATE/release_template.md)
echo -e "\nOpening a draft PR on GitHub" echo -e "\nOpening a draft PR on GitHub"
# To read about these flags, visit the docs: https://cli.github.com/manual/gh_pr_create # To read about these flags, visit the docs: https://cli.github.com/manual/gh_pr_create
$CMD gh pr create --base main --title "release: $CODE_SERVER_VERSION_TO_UPDATE" --body "$RELEASE_TEMPLATE_STRING" --reviewer @cdr/code-server-reviewers --repo cdr/code-server --draft --assignee "@me" $CMD gh pr create --base main --title "release: $CODE_SERVER_VERSION_TO_UPDATE" --body "$RELEASE_TEMPLATE_STRING" --reviewer @coder/code-server-reviewers --repo coder/code-server --draft --assignee "@me"
# Open PR in browser # Open PR in browser
$CMD gh pr view --web $CMD gh pr view --web

View File

@ -3,35 +3,48 @@ set -euo pipefail
main() { main() {
cd "$(dirname "$0")/../.." cd "$(dirname "$0")/../.."
source ./ci/lib.sh
echo "Installing code-server test dependencies..." pushd test
echo "Installing dependencies for $PWD"
cd test
yarn install yarn install
cd .. popd
cd vendor
echo "Installing vendor dependencies..."
# * We install in 'modules' instead of 'node_modules' because VS Code's extensions
# use a webpack config which cannot differentiate between its own node_modules
# and itself being in a directory with the same name.
#
# * We ignore scripts because NPM/Yarn's default behavior is to assume that
# devDependencies are not needed, and that even git repo based packages are
# assumed to be compiled. Because the default behavior for VS Code's `postinstall`
# assumes we're also compiled, this needs to be ignored.
local args=(install --modules-folder modules --ignore-scripts)
local args=(install)
if [[ ${CI-} ]]; then if [[ ${CI-} ]]; then
args+=("--frozen-lockfile") args+=(--frozen-lockfile)
fi fi
pushd test
echo "Installing dependencies for $PWD"
yarn "${args[@]}"
popd
pushd test/e2e/extensions/test-extension
echo "Installing dependencies for $PWD"
yarn "${args[@]}"
popd
pushd vendor
echo "Installing dependencies for $PWD"
# We install in 'modules' instead of 'node_modules' because VS Code's
# extensions use a webpack config which cannot differentiate between its own
# node_modules and itself being in a directory with the same name.
args+=(--modules-folder modules)
# We ignore scripts because NPM/Yarn's default behavior is to assume that
# devDependencies are not needed, and that even git repo based packages are
# assumed to be compiled. Because the default behavior for VS Code's
# `postinstall` assumes we're also compiled, this needs to be ignored.
args+=(--ignore-scripts)
yarn "${args[@]}" yarn "${args[@]}"
# Finally, run the vendor `postinstall` # Finally, run the vendor `postinstall`
yarn run postinstall yarn run postinstall
popd
} }
main "$@" main "$@"

View File

@ -1,11 +1,23 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -euo pipefail set -euo pipefail
help() {
echo >&2 " You can build with 'yarn watch' or you can build a release"
echo >&2 " For example: 'yarn build && yarn build:vscode && KEEP_MODULES=1 yarn release'"
echo >&2 " Then 'CODE_SERVER_TEST_ENTRY=./release yarn test:e2e'"
echo >&2 " You can manually run that release with 'node ./release'"
}
main() { main() {
cd "$(dirname "$0")/../.." cd "$(dirname "$0")/../.."
source ./ci/lib.sh source ./ci/lib.sh
pushd test/e2e/extensions/test-extension
echo "Building test extension"
yarn build
popd
local dir="$PWD" local dir="$PWD"
if [[ ! ${CODE_SERVER_TEST_ENTRY-} ]]; then if [[ ! ${CODE_SERVER_TEST_ENTRY-} ]]; then
echo "Set CODE_SERVER_TEST_ENTRY to test another build of code-server" echo "Set CODE_SERVER_TEST_ENTRY to test another build of code-server"
@ -21,13 +33,13 @@ main() {
# wrong (native modules version issues, incomplete build, etc). # wrong (native modules version issues, incomplete build, etc).
if [[ ! -d $dir/out ]]; then if [[ ! -d $dir/out ]]; then
echo >&2 "No code-server build detected" echo >&2 "No code-server build detected"
echo >&2 "You can build it with 'yarn build' or 'yarn watch'" help
exit 1 exit 1
fi fi
if [[ ! -d $dir/vendor/modules/code-oss-dev/out ]]; then if [[ ! -d $dir/vendor/modules/code-oss-dev/out ]]; then
echo >&2 "No VS Code build detected" echo >&2 "No VS Code build detected"
echo >&2 "You can build it with 'yarn build:vscode' or 'yarn watch'" help
exit 1 exit 1
fi fi

View File

@ -3,12 +3,27 @@ set -euo pipefail
main() { main() {
cd "$(dirname "$0")/../.." cd "$(dirname "$0")/../.."
cd test/unit/node/test-plugin
source ./ci/lib.sh
echo "Building test plugin"
pushd test/unit/node/test-plugin
make -s out/index.js make -s out/index.js
popd
# Our code imports from `out` in order to work during development but if you
# have only built for production you will have not have this directory. In
# that case symlink `out` to a production build directory.
local vscode="vendor/modules/code-oss-dev"
local link="$vscode/out"
local target="out-build"
if [[ ! -e $link ]] && [[ -d $vscode/$target ]]; then
ln -s "$target" "$link"
fi
# We must keep jest in a sub-directory. See ../../test/package.json for more # We must keep jest in a sub-directory. See ../../test/package.json for more
# information. We must also run it from the root otherwise coverage will not # information. We must also run it from the root otherwise coverage will not
# include our source files. # include our source files.
cd "$OLDPWD"
CS_DISABLE_PLUGINS=true ./test/node_modules/.bin/jest "$@" CS_DISABLE_PLUGINS=true ./test/node_modules/.bin/jest "$@"
} }

View File

@ -1,157 +1,140 @@
import browserify from "browserify" import { spawn, fork, ChildProcess } from "child_process"
import * as cp from "child_process"
import * as fs from "fs"
import * as path from "path" import * as path from "path"
import { onLine } from "../../src/node/util" import { onLine, OnLineCallback } from "../../src/node/util"
interface DevelopmentCompilers {
[key: string]: ChildProcess | undefined
vscode: ChildProcess
vscodeWebExtensions: ChildProcess
codeServer: ChildProcess
plugins: ChildProcess | undefined
}
class Watcher {
private rootPath = path.resolve(process.cwd())
private readonly paths = {
/** Path to uncompiled VS Code source. */
vscodeDir: path.join(this.rootPath, "vendor", "modules", "code-oss-dev"),
pluginDir: process.env.PLUGIN_DIR,
}
//#region Web Server
/** Development web server. */
private webServer: ChildProcess | undefined
private reloadWebServer = (): void => {
if (this.webServer) {
this.webServer.kill()
}
// Pass CLI args, save for `node` and the initial script name.
const args = process.argv.slice(2)
this.webServer = fork(path.join(this.rootPath, "out/node/entry.js"), args)
const { pid } = this.webServer
this.webServer.on("exit", () => console.log("[Code Server]", `Web process ${pid} exited`))
console.log("\n[Code Server]", `Spawned web server process ${pid}`)
}
//#endregion
//#region Compilers
private readonly compilers: DevelopmentCompilers = {
codeServer: spawn("tsc", ["--watch", "--pretty", "--preserveWatchOutput"], { cwd: this.rootPath }),
vscode: spawn("yarn", ["watch"], { cwd: this.paths.vscodeDir }),
vscodeWebExtensions: spawn("yarn", ["watch-web"], { cwd: this.paths.vscodeDir }),
plugins: this.paths.pluginDir ? spawn("yarn", ["build", "--watch"], { cwd: this.paths.pluginDir }) : undefined,
}
public async initialize(): Promise<void> {
for (const event of ["SIGINT", "SIGTERM"]) {
process.on(event, () => this.dispose(0))
}
for (const [processName, devProcess] of Object.entries(this.compilers)) {
if (!devProcess) continue
devProcess.on("exit", (code) => {
console.log(`[${processName}]`, "Terminated unexpectedly")
this.dispose(code)
})
if (devProcess.stderr) {
devProcess.stderr.on("data", (d: string | Uint8Array) => process.stderr.write(d))
}
}
onLine(this.compilers.vscode, this.parseVSCodeLine)
onLine(this.compilers.codeServer, this.parseCodeServerLine)
if (this.compilers.plugins) {
onLine(this.compilers.plugins, this.parsePluginLine)
}
}
//#endregion
//#region Line Parsers
private parseVSCodeLine: OnLineCallback = (strippedLine, originalLine) => {
if (!strippedLine.length) return
console.log("[VS Code]", originalLine)
if (strippedLine.includes("Finished compilation with")) {
console.log("[VS Code] ✨ Finished compiling! ✨", "(Refresh your web browser ♻️)")
this.reloadWebServer()
}
}
private parseCodeServerLine: OnLineCallback = (strippedLine, originalLine) => {
if (!strippedLine.length) return
console.log("[Compiler][Code Server]", originalLine)
if (strippedLine.includes("Watching for file changes")) {
console.log("[Compiler][Code Server]", "Finished compiling!", "(Refresh your web browser ♻️)")
this.reloadWebServer()
}
}
private parsePluginLine: OnLineCallback = (strippedLine, originalLine) => {
if (!strippedLine.length) return
console.log("[Compiler][Plugin]", originalLine)
if (strippedLine.includes("Watching for file changes...")) {
this.reloadWebServer()
}
}
//#endregion
//#region Utilities
private dispose(code: number | null): void {
for (const [processName, devProcess] of Object.entries(this.compilers)) {
console.log(`[${processName}]`, "Killing...\n")
devProcess?.removeAllListeners()
devProcess?.kill()
}
process.exit(typeof code === "number" ? code : 0)
}
//#endregion
}
async function main(): Promise<void> { async function main(): Promise<void> {
try { try {
const watcher = new Watcher() const watcher = new Watcher()
await watcher.watch() await watcher.initialize()
} catch (error) { } catch (error: any) {
console.error(error.message) console.error(error.message)
process.exit(1) process.exit(1)
} }
} }
class Watcher {
private readonly rootPath = path.resolve(__dirname, "../..")
private readonly vscodeSourcePath = path.join(this.rootPath, "vendor/modules/code-oss-dev")
private static log(message: string, skipNewline = false): void {
process.stdout.write(message)
if (!skipNewline) {
process.stdout.write("\n")
}
}
public async watch(): Promise<void> {
let server: cp.ChildProcess | undefined
const restartServer = (): void => {
if (server) {
server.kill()
}
const s = cp.fork(path.join(this.rootPath, "out/node/entry.js"), process.argv.slice(2))
console.log(`[server] spawned process ${s.pid}`)
s.on("exit", () => console.log(`[server] process ${s.pid} exited`))
server = s
}
const vscode = cp.spawn("yarn", ["watch"], { cwd: this.vscodeSourcePath })
const tsc = cp.spawn("tsc", ["--watch", "--pretty", "--preserveWatchOutput"], { cwd: this.rootPath })
const plugin = process.env.PLUGIN_DIR
? cp.spawn("yarn", ["build", "--watch"], { cwd: process.env.PLUGIN_DIR })
: undefined
const cleanup = (code?: number | null): void => {
Watcher.log("killing vs code watcher")
vscode.removeAllListeners()
vscode.kill()
Watcher.log("killing tsc")
tsc.removeAllListeners()
tsc.kill()
if (plugin) {
Watcher.log("killing plugin")
plugin.removeAllListeners()
plugin.kill()
}
if (server) {
Watcher.log("killing server")
server.removeAllListeners()
server.kill()
}
Watcher.log("killing watch")
process.exit(code || 0)
}
process.on("SIGINT", () => cleanup())
process.on("SIGTERM", () => cleanup())
vscode.on("exit", (code) => {
Watcher.log("vs code watcher terminated unexpectedly")
cleanup(code)
})
tsc.on("exit", (code) => {
Watcher.log("tsc terminated unexpectedly")
cleanup(code)
})
if (plugin) {
plugin.on("exit", (code) => {
Watcher.log("plugin terminated unexpectedly")
cleanup(code)
})
}
vscode.stderr.on("data", (d) => process.stderr.write(d))
tsc.stderr.on("data", (d) => process.stderr.write(d))
if (plugin) {
plugin.stderr.on("data", (d) => process.stderr.write(d))
}
const browserFiles = [
path.join(this.rootPath, "out/browser/register.js"),
path.join(this.rootPath, "out/browser/pages/login.js"),
path.join(this.rootPath, "out/browser/pages/vscode.js"),
]
let startingVscode = false
let startedVscode = false
onLine(vscode, (line, original) => {
console.log("[vscode]", original)
// Wait for watch-client since "Finished compilation" will appear multiple
// times before the client starts building.
if (!startingVscode && line.includes("Starting watch-client")) {
startingVscode = true
} else if (startingVscode && line.includes("Finished compilation")) {
if (startedVscode) {
restartServer()
}
startedVscode = true
}
})
onLine(tsc, (line, original) => {
// tsc outputs blank lines; skip them.
if (line !== "") {
console.log("[tsc]", original)
}
if (line.includes("Watching for file changes")) {
bundleBrowserCode(browserFiles)
restartServer()
}
})
if (plugin) {
onLine(plugin, (line, original) => {
// tsc outputs blank lines; skip them.
if (line !== "") {
console.log("[plugin]", original)
}
if (line.includes("Watching for file changes")) {
restartServer()
}
})
}
}
}
function bundleBrowserCode(inputFiles: string[]) {
console.log(`[browser] bundling...`)
inputFiles.forEach(async (path: string) => {
const outputPath = path.replace(".js", ".browserified.js")
browserify()
.add(path)
.bundle()
.on("error", function (error: Error) {
console.error(error.toString())
})
.pipe(fs.createWriteStream(outputPath))
})
console.log(`[browser] done bundling`)
}
main() main()

View File

@ -1,6 +1,6 @@
apiVersion: v2 apiVersion: v2
name: code-server name: code-server
description: A Helm chart for cdr/code-server description: A Helm chart for coder/code-server
# A chart can be either an 'application' or a 'library' chart. # A chart can be either an 'application' or a 'library' chart.
# #
@ -15,9 +15,9 @@ type: application
# This is the chart version. This version number should be incremented each time you make changes # 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. # to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/) # Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 1.0.3 version: 2.1.0
# This is the version number of the application being deployed. This version number should be # 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 # 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. # follow Semantic Versioning. They should reflect the version the application is using.
appVersion: 3.12.0 appVersion: 4.0.2

View File

@ -15,9 +15,8 @@
export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "code-server.fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}') export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "code-server.fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
echo http://$SERVICE_IP:{{ .Values.service.port }} echo http://$SERVICE_IP:{{ .Values.service.port }}
{{- else if contains "ClusterIP" .Values.service.type }} {{- else if contains "ClusterIP" .Values.service.type }}
export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "code-server.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
echo "Visit http://127.0.0.1:8080 to use your application" echo "Visit http://127.0.0.1:8080 to use your application"
kubectl port-forward $POD_NAME 8080:80 kubectl port-forward --namespace {{ .Release.Namespace }} service/{{ include "code-server.fullname" . }} 8080:http
{{- end }} {{- end }}
Administrator credentials: Administrator credentials:

View File

@ -21,6 +21,7 @@ spec:
app.kubernetes.io/name: {{ include "code-server.name" . }} app.kubernetes.io/name: {{ include "code-server.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }} app.kubernetes.io/instance: {{ .Release.Name }}
spec: spec:
imagePullSecrets: {{- toYaml .Values.imagePullSecrets | nindent 8 }}
{{- if .Values.hostnameOverride }} {{- if .Values.hostnameOverride }}
hostname: {{ .Values.hostnameOverride }} hostname: {{ .Values.hostnameOverride }}
{{- end }} {{- end }}
@ -142,6 +143,12 @@ spec:
secretName: {{ .secretName }} secretName: {{ .secretName }}
defaultMode: {{ .defaultMode }} defaultMode: {{ .defaultMode }}
{{- end }} {{- end }}
{{- range .Values.extraConfigmapMounts }}
- name: {{ .name }}
configMap:
name: {{ .configMap }}
defaultMode: {{ .defaultMode }}
{{- end }}
{{- range .Values.extraVolumeMounts }} {{- range .Values.extraVolumeMounts }}
- name: {{ .name }} - name: {{ .name }}
{{- if .existingClaim }} {{- if .existingClaim }}

View File

@ -18,6 +18,9 @@ metadata:
{{- toYaml . | nindent 4 }} {{- toYaml . | nindent 4 }}
{{- end }} {{- end }}
spec: spec:
{{- if .Values.ingress.ingressClassName }}
ingressClassName: {{ .Values.ingress.ingressClassName }}
{{- end }}
{{- if .Values.ingress.tls }} {{- if .Values.ingress.tls }}
tls: tls:
{{- range .Values.ingress.tls }} {{- range .Values.ingress.tls }}

View File

@ -6,10 +6,15 @@ replicaCount: 1
image: image:
repository: codercom/code-server repository: codercom/code-server
tag: '3.12.0' tag: '4.0.2'
pullPolicy: Always pullPolicy: Always
# Specifies one or more secrets to be used when pulling images from a
# private container repository
# https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry
imagePullSecrets: [] imagePullSecrets: []
# - name: registry-creds
nameOverride: "" nameOverride: ""
fullnameOverride: "" fullnameOverride: ""
hostnameOverride: "" hostnameOverride: ""
@ -28,14 +33,6 @@ podAnnotations: {}
podSecurityContext: {} podSecurityContext: {}
# fsGroup: 2000 # fsGroup: 2000
securityContext: {}
# capabilities:
# drop:
# - ALL
# readOnlyRootFilesystem: true
# runAsNonRoot: true
# runAsUser: 1000
service: service:
type: ClusterIP type: ClusterIP
port: 8080 port: 8080
@ -43,13 +40,12 @@ service:
ingress: ingress:
enabled: false enabled: false
#annotations: #annotations:
# kubernetes.io/ingress.class: nginx
# kubernetes.io/tls-acme: "true" # kubernetes.io/tls-acme: "true"
#hosts: #hosts:
# - host: code-server.example.loc # - host: code-server.example.loc
# paths: # paths:
# - / # - /
ingressClassName: ""
#tls: #tls:
# - secretName: code-server # - secretName: code-server
# hosts: # hosts:
@ -127,10 +123,6 @@ persistence:
# existingClaim: "" # existingClaim: ""
# hostPath: /data # hostPath: /data
serviceAccount:
create: true
name:
## Enable an Specify container in extraContainers. ## Enable an Specify container in extraContainers.
## This is meant to allow adding code-server dependencies, like docker-dind. ## This is meant to allow adding code-server dependencies, like docker-dind.
extraContainers: | extraContainers: |

View File

@ -10,11 +10,13 @@ RUN apt-get update \
man \ man \
nano \ nano \
git \ git \
git-lfs \
procps \ procps \
openssh-client \ openssh-client \
sudo \ sudo \
vim.tiny \ vim.tiny \
lsb-release \ lsb-release \
&& git lfs install \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
# https://wiki.debian.org/Locale#Manually # https://wiki.debian.org/Locale#Manually

View File

@ -18,4 +18,4 @@ if [ "${DOCKER_USER-}" ]; then
fi fi
fi fi
dumb-init /usr/bin/code-server "$@" exec dumb-init /usr/bin/code-server "$@"

View File

@ -10,23 +10,23 @@ main() {
echo "Checking environment variables" echo "Checking environment variables"
# We need VERSION to bump the brew formula # We need VERSION to bump the brew formula
if is_env_var_set "VERSION"; then if ! is_env_var_set "VERSION"; then
echo "VERSION is not set" echo "VERSION is not set"
exit 1 exit 1
fi fi
# We need HOMEBREW_GITHUB_API_TOKEN to push up commits # We need HOMEBREW_GITHUB_API_TOKEN to push up commits
if is_env_var_set "HOMEBREW_GITHUB_API_TOKEN"; then if ! is_env_var_set "HOMEBREW_GITHUB_API_TOKEN"; then
echo "HOMEBREW_GITHUB_API_TOKEN is not set" echo "HOMEBREW_GITHUB_API_TOKEN is not set"
exit 1 exit 1
fi fi
# NOTE: we need to make sure cdrci/homebrew-core # NOTE: we need to make sure coderci/homebrew-core
# is up-to-date # is up-to-date
# otherwise, brew bump-formula-pr will use an # otherwise, brew bump-formula-pr will use an
# outdated base # outdated base
echo "Cloning cdrci/homebrew-core" echo "Cloning coderci/homebrew-core"
git clone https://github.com/cdrci/homebrew-core.git git clone https://github.com/coderci/homebrew-core.git
# Make sure the git clone step is successful # Make sure the git clone step is successful
if directory_exists "homebrew-core"; then if directory_exists "homebrew-core"; then
@ -57,7 +57,7 @@ main() {
echo "Merging in latest Homebrew/homebrew-core changes" echo "Merging in latest Homebrew/homebrew-core changes"
git merge upstream/master git merge upstream/master
echo "Pushing changes to cdrci/homebrew-core fork on GitHub" echo "Pushing changes to coderci/homebrew-core fork on GitHub"
# GIT_ASKPASS lets us use the password when pushing without revealing it in the process list # GIT_ASKPASS lets us use the password when pushing without revealing it in the process list
# See: https://serverfault.com/a/912788 # See: https://serverfault.com/a/912788
@ -86,7 +86,7 @@ main() {
# Export the variables so git sees them # Export the variables so git sees them
export HOMEBREW_GITHUB_API_TOKEN="$HOMEBREW_GITHUB_API_TOKEN" export HOMEBREW_GITHUB_API_TOKEN="$HOMEBREW_GITHUB_API_TOKEN"
export GIT_ASKPASS="$PATH_TO_ASKPASS" export GIT_ASKPASS="$PATH_TO_ASKPASS"
git push https://cdr-oss@github.com/cdr-oss/homebrew-core.git --all git push https://coder-oss@github.com/coder-oss/homebrew-core.git --all
# Find the docs for bump-formula-pr here # Find the docs for bump-formula-pr here
# https://github.com/Homebrew/brew/blob/master/Library/Homebrew/dev-cmd/bump-formula-pr.rb#L18 # https://github.com/Homebrew/brew/blob/master/Library/Homebrew/dev-cmd/bump-formula-pr.rb#L18

View File

@ -1,36 +1,15 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -euo pipefail set -euo pipefail
# See if this version already exists on Docker Hub.
function version_exists() {
local output
output=$(curl --silent "https://index.docker.io/v1/repositories/codercom/code-server/tags/$VERSION")
if [[ $output == "Tag not found" ]]; then
return 1
else
return 0
fi
}
main() { main() {
cd "$(dirname "$0")/../.." cd "$(dirname "$0")/../.."
# ci/lib.sh sets VERSION and provides download_artifact here # ci/lib.sh sets VERSION and provides download_artifact here
source ./ci/lib.sh source ./ci/lib.sh
if version_exists; then
echo "$VERSION is already pushed"
return
fi
# Download the release-packages artifact # Download the release-packages artifact
download_artifact release-packages ./release-packages download_artifact release-packages ./release-packages
# Login to Docker
if [[ ${CI-} ]]; then
echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin
fi
docker buildx bake -f ci/release-image/docker-bake.hcl --push docker buildx bake -f ci/release-image/docker-bake.hcl --push
} }

View File

@ -4,27 +4,144 @@ set -euo pipefail
main() { main() {
cd "$(dirname "$0")/../.." cd "$(dirname "$0")/../.."
source ./ci/lib.sh source ./ci/lib.sh
source ./ci/steps/steps-lib.sh
# npm view won't exit with non-zero so we have to check the output. ## Authentication tokens
local hasVersion # Needed to publish on NPM
hasVersion=$(npm view "code-server@$VERSION" version) if ! is_env_var_set "NPM_TOKEN"; then
if [[ $hasVersion == "$VERSION" ]]; then echo "NPM_TOKEN is not set. Cannot publish to npm without credentials."
echo "$VERSION is already published" exit 1
return
fi fi
# NOTE@jsjoeio - only needed if we use the download_artifact
# because we talk to the GitHub API.
# Needed to use GitHub API
if ! is_env_var_set "GITHUB_TOKEN"; then
echo "GITHUB_TOKEN is not set. Cannot download npm release artifact without GitHub credentials."
exit 1
fi
## Publishing Information
# All the variables below are used to determine how we should publish
# the npm package. We also use this information for bumping the version.
# This is because npm won't publish your package unless it's a new version.
# i.e. for development, we bump the version to <current version>-<pr number>-<commit sha>
# example: "version": "4.0.1-4769-ad7b23cfe6ffd72914e34781ef7721b129a23040"
# We need the current package.json VERSION
if ! is_env_var_set "VERSION"; then
echo "VERSION is not set. Cannot publish to npm without VERSION."
exit 1
fi
# We use this to grab the PR_NUMBER
if ! is_env_var_set "GITHUB_REF"; then
echo "GITHUB_REF is not set. Are you running this locally? We rely on values provided by GitHub."
exit 1
fi
# We use this when setting NPM_VERSION
if ! is_env_var_set "GITHUB_SHA"; then
echo "GITHUB_SHA is not set. Are you running this locally? We rely on values provided by GitHub."
exit 1
fi
# We use this to determine the NPM_ENVIRONMENT
if ! is_env_var_set "GITHUB_EVENT_NAME"; then
echo "GITHUB_EVENT_NAME is not set. Are you running this locally? We rely on values provided by GitHub."
exit 1
fi
# This allows us to publish to npm in CI workflows
if [[ ${CI-} ]]; then if [[ ${CI-} ]]; then
echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > ~/.npmrc echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > ~/.npmrc
fi fi
download_artifact npm-package ./release-npm-package ## Environment
# This string is used to determine how we should tag the npm release.
# Environment can be one of three choices:
# "development" - this means we tag with the PR number, allowing
# a developer to install this version with `yarn add code-server@<pr-number>`
# "staging" - this means we tag with `beta`, allowing
# a developer to install this version with `yarn add code-server@beta`
# "production" - this means we tag with `latest` (default), allowing
# a developer to install this version with `yarn add code-server@latest`
if ! is_env_var_set "NPM_ENVIRONMENT"; then
echo "NPM_ENVIRONMENT is not set. Determining in script based on GITHUB environment variables."
if [[ "$GITHUB_EVENT_NAME" == 'push' && "$GITHUB_REF" == 'refs/heads/main' ]]; then
NPM_ENVIRONMENT="staging"
else
NPM_ENVIRONMENT="development"
fi
echo "Using npm environment: $NPM_ENVIRONMENT"
fi
# NOTE@jsjoeio - this script assumes we have the artifact downloaded on disk
# That happens in CI as a step before we run this.
# https://github.com/actions/upload-artifact/issues/38 # https://github.com/actions/upload-artifact/issues/38
tar -xzf release-npm-package/package.tar.gz tar -xzf release-npm-package/package.tar.gz
# Ignore symlink when publishing npm package # Ignore symlink when publishing npm package
# See: https://github.com/cdr/code-server/pull/3935 # See: https://github.com/coder/code-server/pull/3935
echo "node_modules.asar" > release/.npmignore echo "node_modules.asar" > release/.npmignore
yarn publish --non-interactive release
# NOTES:@jsjoeio
# We only need to run npm version for "development" and "staging".
# This is because our release:prep script automatically bumps the version
# in the package.json and we commit it as part of the release PR.
if [[ "$NPM_ENVIRONMENT" == "production" ]]; then
NPM_VERSION="$VERSION"
# This means the npm version will be published as "stable"
# and installed when a user runs `yarn install code-server`
NPM_TAG="latest"
else
COMMIT_SHA="$GITHUB_SHA"
echo "Not a production environment"
echo "Found environment: $NPM_ENVIRONMENT"
echo "Manually bumping npm version..."
if [[ "$NPM_ENVIRONMENT" == "staging" ]]; then
NPM_VERSION="$VERSION-beta-$COMMIT_SHA"
# This means the npm version will be tagged with "beta"
# and installed when a user runs `yarn install code-server@beta`
NPM_TAG="beta"
fi
if [[ "$NPM_ENVIRONMENT" == "development" ]]; then
# Source: https://github.com/actions/checkout/issues/58#issuecomment-614041550
PR_NUMBER=$(echo "$GITHUB_REF" | awk 'BEGIN { FS = "/" } ; { print $3 }')
NPM_VERSION="$VERSION-$PR_NUMBER-$COMMIT_SHA"
# This means the npm version will be tagged with "<pr number>"
# and installed when a user runs `yarn install code-server@<pr number>`
NPM_TAG="$PR_NUMBER"
fi
echo "using tag: $NPM_TAG"
# We modify the version in the package.json
# to be the current version + the PR number + commit SHA
# or we use current version + beta + commit SHA
# Example: "version": "4.0.1-4769-ad7b23cfe6ffd72914e34781ef7721b129a23040"
# Example: "version": "4.0.1-beta-ad7b23cfe6ffd72914e34781ef7721b129a23040"
pushd release
# NOTE:@jsjoeio
# I originally tried to use `yarn version` but ran into issues and abandoned it.
npm version "$NPM_VERSION"
popd
fi
# We need to make sure we haven't already published the version.
# This is because npm view won't exit with non-zero so we have
# to check the output.
local hasVersion
hasVersion=$(npm view "code-server@$NPM_VERSION" version)
if [[ $hasVersion == "$NPM_VERSION" ]]; then
echo "$NPM_VERSION is already published"
return
fi
yarn publish --non-interactive release --tag "$NPM_TAG"
} }
main "$@" main "$@"

View File

@ -3,13 +3,16 @@
# Contributing # Contributing
- [Requirements](#requirements) - [Requirements](#requirements)
- [Linux-specific requirements](#linux-specific-requirements)
- [Creating pull requests](#creating-pull-requests) - [Creating pull requests](#creating-pull-requests)
- [Commits and commit history](#commits-and-commit-history) - [Commits and commit history](#commits-and-commit-history)
- [Development workflow](#development-workflow) - [Development workflow](#development-workflow)
- [Updates to VS Code](#updates-to-vs-code) - [Updates to VS Code](#updates-to-vs-code)
- [Build](#build) - [Build](#build)
- [Test](#test) - [Help](#help)
- [Test](#test)
- [Unit tests](#unit-tests) - [Unit tests](#unit-tests)
- [Script tests](#script-tests)
- [Integration tests](#integration-tests) - [Integration tests](#integration-tests)
- [End-to-end tests](#end-to-end-tests) - [End-to-end tests](#end-to-end-tests)
- [Structure](#structure) - [Structure](#structure)
@ -32,7 +35,7 @@ Here is what is needed:
- [`git-lfs`](https://git-lfs.github.com) - [`git-lfs`](https://git-lfs.github.com)
- [`yarn`](https://classic.yarnpkg.com/en/) - [`yarn`](https://classic.yarnpkg.com/en/)
- Used to install JS packages and run scripts - Used to install JS packages and run scripts
- [`nfpm`](https://classic.yarnpkg.com/en/) - [`nfpm`](https://nfpm.goreleaser.com/)
- Used to build `.deb` and `.rpm` packages - Used to build `.deb` and `.rpm` packages
- [`jq`](https://stedolan.github.io/jq/) - [`jq`](https://stedolan.github.io/jq/)
- Used to build code-server releases - Used to build code-server releases
@ -41,16 +44,24 @@ Here is what is needed:
signature signature
verification](https://docs.github.com/en/github/authenticating-to-github/managing-commit-signature-verification) verification](https://docs.github.com/en/github/authenticating-to-github/managing-commit-signature-verification)
or follow [this tutorial](https://joeprevite.com/verify-commits-on-github) or follow [this tutorial](https://joeprevite.com/verify-commits-on-github)
- `build-essential` (Linux only - used by VS Code)
- Get this by running `apt-get install -y build-essential`
- `rsync` and `unzip` - `rsync` and `unzip`
- Used for code-server releases - Used for code-server releases
- `bats` - `bats`
- Used to run script unit tests - Used to run script unit tests
### Linux-specific requirements
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
```
These are required by VS Code. See [their Wiki](https://github.com/microsoft/vscode/wiki/How-to-Contribute#prerequisites) for more information.
## Creating pull requests ## Creating pull requests
Please create a [GitHub Issue](https://github.com/cdr/code-server/issues) that Please create a [GitHub Issue](https://github.com/coder/code-server/issues) that
includes context for issues that you see. You can skip this if the proposed fix includes context for issues that you see. You can skip this if the proposed fix
is minor. is minor.
@ -67,31 +78,37 @@ we'll guide you.
## Development workflow ## Development workflow
```shell The current development workflow is a bit tricky because we have this repo and we use our `coder/vscode` fork inside it with [`yarn link`](https://classic.yarnpkg.com/lang/en/docs/cli/link/).
yarn
yarn watch
# Visit http://localhost:8080 once the build is completed.
```
`yarn watch` will live reload changes to the source. Here are these steps you should follow to get your dev environment setup:
1. `git clone https://github.com/coder/code-server.git` - Clone `code-server`
2. `git clone https://github.com/coder/vscode.git` - Clone `vscode`
3. `cd vscode && yarn install` - install the dependencies in the `vscode` repo
4. `cd code-server && yarn install` - install the dependencies in the `code-server` repo
5. `cd vscode && yarn link` - use `yarn` to create a symlink to the `vscode` repo (`code-oss-dev` package)
6. `cd code-server && yarn link code-oss-dev --modules-folder vendor/modules` - links your local `vscode` repo (`code-oss-dev` package) inside your local version of code-server
7. `cd code-server && yarn watch` - this will spin up code-server on localhost:8080 which you can start developing. It will live reload changes to the source.
### Updates to VS Code ### Updates to VS Code
If changes are made and merged into `main` in the [`coder/vscode`](https://github.com/coder/vscode) repo, then you'll need to update the version in the `code-server` repo by following these steps:
1. Update the package tag listed in `vendor/package.json`: 1. Update the package tag listed in `vendor/package.json`:
```json ```json
{ {
"devDependencies": { "devDependencies": {
"vscode": "cdr/vscode#X.XX.X-code-server" "vscode": "coder/vscode#<latest-commit-sha>"
} }
} }
``` ```
2. From the code-server **project root**, run `yarn install`. 2. From the code-server **project root**, run `yarn install`.
Then, test code-server locally to make sure everything works. Then, test code-server locally to make sure everything works.
1. Check the Node.js version that's used by Electron (which is shipped with VS 3. Check the Node.js version that's used by Electron (which is shipped with VS
Code. If necessary, update your version of Node.js to match. Code. If necessary, update your version of Node.js to match.
1. Open a PR 4. Open a PR
> Watch for updates to > Watch for updates to
> `vendor/modules/code-oss-dev/src/vs/code/browser/workbench/workbench.html`. You may need to > `vendor/modules/code-oss-dev/src/vs/code/browser/workbench/workbench.html`. You may need to
@ -129,13 +146,18 @@ yarn package
> If you need your builds to support older distros, run the build commands > If you need your builds to support older distros, run the build commands
> inside a Docker container with all the build requirements installed. > inside a Docker container with all the build requirements installed.
### Test ### Help
There are three kinds of tests in code-server: If you get stuck or need help, you can always start a new GitHub Discussion [here](https://github.com/coder/code-server/discussions). One of the maintainers will respond and help you out.
## Test
There are four kinds of tests in code-server:
1. Unit tests 1. Unit tests
2. Integration tests 2. Script tests
3. End-to-end tests 3. Integration tests
4. End-to-end tests
### Unit tests ### Unit tests
@ -146,6 +168,14 @@ These live under [test/unit](../test/unit).
We use unit tests for functions and things that can be tested in isolation. The file structure is modeled closely after `/src` so it's easy for people to know where test files should live. We use unit tests for functions and things that can be tested in isolation. The file structure is modeled closely after `/src` so it's easy for people to know where test files should live.
### Script tests
Our script tests are written in bash and run using [bats](https://github.com/bats-core/bats-core).
These tests live under `test/scripts`.
We use these to test anything related to our scripts (most of which live under `ci`).
### Integration tests ### Integration tests
These are a work in progress. We build code-server and run a script called These are a work in progress. We build code-server and run a script called

View File

@ -26,7 +26,8 @@
- [Can I use Docker in a code-server container?](#can-i-use-docker-in-a-code-server-container) - [Can I use Docker in a code-server container?](#can-i-use-docker-in-a-code-server-container)
- [How do I disable telemetry?](#how-do-i-disable-telemetry) - [How do I disable telemetry?](#how-do-i-disable-telemetry)
- [What's the difference between code-server and Theia?](#whats-the-difference-between-code-server-and-theia) - [What's the difference between code-server and Theia?](#whats-the-difference-between-code-server-and-theia)
- [What's the difference between code-server and VS Code Codespaces?](#whats-the-difference-between-code-server-and-vs-code-codespaces) - [What's the difference between code-server and OpenVSCode-Server?](#whats-the-difference-between-code-server-and-openvscode-server)
- [What's the difference between code-server and GitHub Codespaces?](#whats-the-difference-between-code-server-and-github-codespaces)
- [Does code-server have any security login validation?](#does-code-server-have-any-security-login-validation) - [Does code-server have any security login validation?](#does-code-server-have-any-security-login-validation)
- [Are there community projects involving code-server?](#are-there-community-projects-involving-code-server) - [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 change the port?](#how-do-i-change-the-port)
@ -36,7 +37,7 @@
## Questions? ## Questions?
Please file all questions and support requests at Please file all questions and support requests at
<https://github.com/cdr/code-server/discussions>. <https://github.com/coder/code-server/discussions>.
## How should I expose code-server to the internet? ## How should I expose code-server to the internet?
@ -100,32 +101,28 @@ Service](https://cdn.vsassets.io/v/M146_20190123.39/_content/Microsoft-Visual-St
> Visual Studio Products and Services. > Visual Studio Products and Services.
Because of this, we can't offer any extensions on Microsoft's marketplace. Because of this, we can't offer any extensions on Microsoft's marketplace.
Instead, we've created a marketplace offering open-source extensions. The Instead, we use the [Open-VSX extension gallery](https://open-vsx.org), which is also used by various other forks.
marketplace works by scraping GitHub for VS Code extensions and building them. It isn't perfect, but its getting better by the day with more and more extensions.
We also offer our own marketplace for open source extensions, but plan to
deprecate it at a future date and completely migrate to Open-VSX.
These are the closed-source extensions that are presently unavailable: These are the closed-source extensions that are presently unavailable:
1. [Live Share](https://visualstudio.microsoft.com/services/live-share). We may 1. [Live Share](https://visualstudio.microsoft.com/services/live-share). We may
implement something similar (see implement something similar (see
[#33](https://github.com/cdr/code-server/issues/33)) [#33](https://github.com/coder/code-server/issues/33))
1. [Remote Extensions (SSH, Containers, 1. [Remote Extensions (SSH, Containers,
WSL)](https://github.com/microsoft/vscode-remote-release). We may implement WSL)](https://github.com/microsoft/vscode-remote-release). We may implement
these again at some point, see these again at some point, see
([#1315](https://github.com/cdr/code-server/issues/1315)). ([#1315](https://github.com/coder/code-server/issues/1315)).
For more about the closed source portions of VS Code, see [vscodium/vscodium](https://github.com/VSCodium/vscodium#why-does-this-exist). For more about the closed source portions of VS Code, see [vscodium/vscodium](https://github.com/VSCodium/vscodium#why-does-this-exist).
## How can I request an extension that's missing from the marketplace? ## How can I request an extension that's missing from the marketplace?
We are in the process of transitioning to [Open VSX](https://open-vsx.org/). To add an extension to Open-VSX, please see [open-vsx/publish-extensions](https://github.com/open-vsx/publish-extensions).
Once we've [implemented Open We no longer plan to add new extensions to our legacy extension gallery.
VSX](https://github.com/eclipse/openvsx/issues/249), we can finalize this
transition. As such, we are not currently accepting new extension requests.
In the meantime, we suggest:
- [Switching to Open VSX](#how-do-i-configure-the-marketplace-url) now
- Downloading and [installing the extension manually](#installing-an-extension-manually)
## How do I install an extension? ## How do I install an extension?
@ -158,20 +155,19 @@ You can also download extensions using the command line. For instance,
downloading from OpenVSX can be done like this: downloading from OpenVSX can be done like this:
```shell ```shell
SERVICE_URL=https://open-vsx.org/vscode/gallery ITEM_URL=https://open-vsx.org/vscode/item code-server --install-extension <extension id> code-server --install-extension <extension id>
``` ```
## How do I use my own extensions marketplace? ## How do I use my own extensions marketplace?
If you own a marketplace that implements the VS Code Extension Gallery API, you If you own a marketplace that implements the VS Code Extension Gallery API, you
can point code-server to it by setting `$SERVICE_URL` and `$ITEM_URL`. These correspond directly can point code-server to it by setting `$EXTENSIONS_GALLERY`.
to `serviceUrl` and `itemUrl` in VS Code's `product.json`. This corresponds directly with the `extensionsGallery` entry in in VS Code's `product.json`.
For example, to use [open-vsx.org](https://open-vsx.org), run: For example, to use the legacy Coder extensions marketplace:
```bash ```bash
export SERVICE_URL=https://open-vsx.org/vscode/gallery export EXTENSIONS_GALLERY='{"serviceUrl": "https://extensions.coder.com/api"}'
export ITEM_URL=https://open-vsx.org/vscode/item
``` ```
Though you can technically use Microsoft's marketplace in this manner, we Though you can technically use Microsoft's marketplace in this manner, we
@ -226,7 +222,7 @@ You may have to give Node.js full disk access, since it doesn't implement any of
Privacy** > **Privacy** > **Full Disk Access**. Then, click the 🔒 to unlock, Privacy** > **Privacy** > **Full Disk Access**. Then, click the 🔒 to unlock,
click **+**, and select the Node.js binary you located in the previous step. click **+**, and select the Node.js binary you located in the previous step.
See [#2794](https://github.com/cdr/code-server/issues/2794) for additional context. See [#2794](https://github.com/coder/code-server/issues/2794) for additional context.
## How do I direct server-side requests through a proxy? ## How do I direct server-side requests through a proxy?
@ -306,7 +302,7 @@ after a predetermined amount of time, you can do so by checking continuously for
the last modified time on the heartbeat file. If it is older than X minutes (or the last modified time on the heartbeat file. If it is older than X minutes (or
whatever amount of time you'd like), you can kill code-server. whatever amount of time you'd like), you can kill code-server.
Eventually, [#1636](https://github.com/cdr/code-server/issues/1636) will make Eventually, [#1636](https://github.com/coder/code-server/issues/1636) will make
this process better. this process better.
## How do I change the password? ## How do I change the password?
@ -377,18 +373,31 @@ for extensions.
Theia doesn't allow you to reuse your existing VS Code config. Theia doesn't allow you to reuse your existing VS Code config.
## What's the difference between code-server and VS Code Codespaces? ## What's the difference between code-server and OpenVSCode-Server?
Both code-server and VS Code Codespaces allow you to access VS Code via a code-server and OpenVSCode-Server both allow you to access VS Code via a
browser. browser. The two projects also use their own [forks of VS Code](https://github.com/coder/vscode) to
leverage modern VS Code APIs and stay up to date with the upsteam version.
VS Code Codespaces, however, is a closed-source, paid service offered by However, OpenVSCode-Server is scoped at only making VS Code available in the web browser.
Microsoft. While you can self-host environments with VS Code Codespaces, you code-server includes some other features:
still need an Azure billing account, and you must access VS Code via the
Codespaces web dashboard instead of connecting directly to it.
On the other hand, code-server is free, open-source, and can be run on any - password auth
machine with few limitations. - proxy web ports
- certificate support
- plugin API
- settings sync (coming soon)
For more details, see [this discussion post](https://github.com/coder/code-server/discussions/4267#discussioncomment-1411583).
## What's the difference between code-server and GitHub Codespaces?
Both code-server and GitHub Codespaces allow you to access VS Code via a
browser. GitHub Codespaces, however, is a closed-source, paid service offered by
GitHub and Microsoft.
On the other hand, code-server is self-hosted, free, open-source, and
can be run on any machine with few limitations.
## Does code-server have any security login validation? ## Does code-server have any security login validation?
@ -397,7 +406,7 @@ minute plus an additional twelve per hour.
## Are there community projects involving code-server? ## Are there community projects involving code-server?
Visit the [awesome-code-server](https://github.com/cdr/awesome-code-server) Visit the [awesome-code-server](https://github.com/coder/awesome-code-server)
repository to view community projects and guides with code-server! Feel free to repository to view community projects and guides with code-server! Feel free to
add your own! add your own!

View File

@ -19,6 +19,8 @@
- [Docker](#docker) - [Docker](#docker)
- [Homebrew](#homebrew) - [Homebrew](#homebrew)
- [npm](#npm) - [npm](#npm)
- [Syncing with Upstream VS Code](#syncing-with-upstream-vs-code)
- [Testing](#testing)
- [Documentation](#documentation) - [Documentation](#documentation)
- [Troubleshooting](#troubleshooting) - [Troubleshooting](#troubleshooting)
@ -35,23 +37,23 @@ Current maintainers:
- @TeffenEllis - @TeffenEllis
- @jsjoeio - @jsjoeio
Occassionally, other Coder employees may step in time to time to assist with code-server. Occasionally, other Coder employees may step in time to time to assist with code-server.
### Onboarding ### Onboarding
To onboard a new maintainer to the project, please make sure to do the following: To onboard a new maintainer to the project, please make sure to do the following:
- [ ] Add to [cdr/code-server-reviewers](https://github.com/orgs/cdr/teams/code-server-reviewers) - [ ] Add to [coder/code-server-reviewers](https://github.com/orgs/coder/teams/code-server-reviewers)
- [ ] Add as Admin under [Repository Settings > Access](https://github.com/cdr/code-server/settings/access) - [ ] Add as Admin under [Repository Settings > Access](https://github.com/coder/code-server/settings/access)
- [ ] Add to [npm Coder org](https://www.npmjs.com/org/coder) - [ ] Add to [npm Coder org](https://www.npmjs.com/org/coder)
- [ ] Add as [AUR maintainer](https://aur.archlinux.org/packages/code-server/) (talk to Colin) - [ ] Add as [AUR maintainer](https://aur.archlinux.org/packages/code-server/) (talk to Colin)
- [ ] Introduce to community via Discussion (see [example](https://github.com/cdr/code-server/discussions/3955)) - [ ] Introduce to community via Discussion (see [example](https://github.com/coder/code-server/discussions/3955))
### Offboarding ### Offboarding
Very similar to Onboarding but Remove maintainer from all teams and revoke access. Please also do the following: Very similar to Onboarding but Remove maintainer from all teams and revoke access. Please also do the following:
- [ ] Write farewell post via Discussion (see [example](https://github.com/cdr/code-server/discussions/3933)) - [ ] Write farewell post via Discussion (see [example](https://github.com/coder/code-server/discussions/3933))
## Workflow ## Workflow
@ -62,7 +64,7 @@ contributing on day one.
### Milestones ### Milestones
We operate mainly using We operate mainly using
[milestones](https://github.com/cdr/code-server/milestones). This was heavily [milestones](https://github.com/coder/code-server/milestones). This was heavily
inspired by our friends over at [vscode](https://github.com/microsoft/vscode). inspired by our friends over at [vscode](https://github.com/microsoft/vscode).
Here are the milestones we use and how we use them: Here are the milestones we use and how we use them:
@ -125,8 +127,7 @@ the issue.
### Merge strategies ### Merge strategies
For most things, we recommend the **squash and merge** strategy. If you're For most things, we recommend the **squash and merge** strategy. There
updating `lib/vscode`, we suggest using the **rebase and merge** strategy. There
may be times where **creating a merge commit** makes sense as well. Use your may be times where **creating a merge commit** makes sense as well. Use your
best judgment. If you're unsure, you can always discuss in the PR with the team. best judgment. If you're unsure, you can always discuss in the PR with the team.
@ -184,12 +185,12 @@ If you're the current release manager, follow these steps:
artifacts, publish the NPM package from `npm-package`, and publish the Docker artifacts, publish the NPM package from `npm-package`, and publish the Docker
Hub image from `release-images`. Hub image from `release-images`.
1. Update the AUR package. Instructions for updating the AUR package are at 1. Update the AUR package. Instructions for updating the AUR package are at
[cdr/code-server-aur](https://github.com/cdr/code-server-aur). [coder/code-server-aur](https://github.com/coder/code-server-aur).
1. Wait for the npm package to be published. 1. Wait for the npm package to be published.
#### AUR #### AUR
We publish to AUR as a package [here](https://aur.archlinux.org/packages/code-server/). This process is manual and can be done by following the steps in [this repo](https://github.com/cdr/code-server-aur). We publish to AUR as a package [here](https://aur.archlinux.org/packages/code-server/). This process is manual and can be done by following the steps in [this repo](https://github.com/coder/code-server-aur).
#### Docker #### Docker
@ -201,7 +202,7 @@ This is currently automated with the release process.
We publish code-server on Homebrew [here](https://github.com/Homebrew/homebrew-core/blob/master/Formula/code-server.rb). We publish code-server on Homebrew [here](https://github.com/Homebrew/homebrew-core/blob/master/Formula/code-server.rb).
This is currently automated with the release process (but may fail occassionally). If it does, run this locally: This is currently automated with the release process (but may fail occasionally). If it does, run this locally:
```shell ```shell
# Replace VERSION with version # Replace VERSION with version
@ -214,13 +215,40 @@ We publish code-server as a npm package [here](https://www.npmjs.com/package/cod
This is currently automated with the release process. This is currently automated with the release process.
## Syncing with Upstream VS Code
The VS Code portion of code-server lives under [`coder/vscode`](https://github.com/coder/vscode). To update VS Code for code-server, follow these steps:
1. `git checkout -b vscode-update` - Create a new branch locally based off `main`
2. `git fetch upstream` - Fetch upstream (VS Code)'s latest branches
3. `git merge upstream/release/1.64` - Merge it locally
1. replace `1.64` with the version you're upgrading to
1. If there are merge conflicts, commit first, then fix them locally.
4. Open a PR merging your branch (`vscode-update`) into `main` and add the code-server review team
Ideally, our fork stays as close to upstream as possible. See the differences between our fork and upstream [here](https://github.com/microsoft/vscode/compare/main...coder:main).
## Testing
Our testing structure is laid out under our [Contributing docs](https://coder.com/docs/code-server/latest/CONTRIBUTING#test).
We hope to eventually hit 100% test coverage with our unit tests, and maybe one day our scripts (coverage not tracked currently).
If you're ever looking to add more tests, here are a few ways to get started:
- run `yarn test:unit` and look at the coverage chart. You'll see all the uncovered lines. This is a good place to start.
- look at `test/scripts` to see which scripts are tested. We can always use more tests there.
- look at `test/e2e`. We can always use more end-to-end tests.
Otherwise, talk to a current maintainer and ask which part of the codebase is lacking most when it comes to tests.
## Documentation ## Documentation
### Troubleshooting ### Troubleshooting
Our docs are hosted on [Vercel](https://vercel.com/). Vercel only shows logs in realtime, which means you need to have the logs open in one tab and reproduce your error in another tab. Since our logs are private to Coder the organization, you can only follow these steps if you're a Coder employee. Ask a maintainer for help if you need it. Our docs are hosted on [Vercel](https://vercel.com/). Vercel only shows logs in realtime, which means you need to have the logs open in one tab and reproduce your error in another tab. Since our logs are private to Coder the organization, you can only follow these steps if you're a Coder employee. Ask a maintainer for help if you need it.
Taking a real scenario, let's say you wanted to troubleshoot [this docs change](https://github.com/cdr/code-server/pull/4042). Here is how you would do it: Taking a real scenario, let's say you wanted to troubleshoot [this docs change](https://github.com/coder/code-server/pull/4042). Here is how you would do it:
1. Go to https://vercel.com/codercom/codercom 1. Go to https://vercel.com/codercom/codercom
2. Click "View Function Logs" 2. Click "View Function Logs"

View File

@ -1,6 +1,6 @@
# code-server # code-server
[!["GitHub Discussions"](https://img.shields.io/badge/%20GitHub-%20Discussions-gray.svg?longCache=true&logo=github&colorB=purple)](https://github.com/cdr/code-server/discussions) [!["Join us on Slack"](https://img.shields.io/badge/join-us%20on%20slack-gray.svg?longCache=true&logo=slack&colorB=brightgreen)](https://cdr.co/join-community) [![Twitter Follow](https://img.shields.io/twitter/follow/CoderHQ?label=%40CoderHQ&style=social)](https://twitter.com/coderhq) [![codecov](https://codecov.io/gh/cdr/code-server/branch/main/graph/badge.svg?token=5iM9farjnC)](https://codecov.io/gh/cdr/code-server) [![See v3.12.0 docs](https://img.shields.io/static/v1?label=Docs&message=see%20v3.12.0%20&color=blue)](https://github.com/cdr/code-server/tree/v3.12.0/docs) [!["GitHub Discussions"](https://img.shields.io/badge/%20GitHub-%20Discussions-gray.svg?longCache=true&logo=github&colorB=purple)](https://github.com/coder/code-server/discussions) [!["Join us on Slack"](https://img.shields.io/badge/join-us%20on%20slack-gray.svg?longCache=true&logo=slack&colorB=brightgreen)](https://coder.com/community) [![Twitter Follow](https://img.shields.io/twitter/follow/CoderHQ?label=%40CoderHQ&style=social)](https://twitter.com/coderhq) [![codecov](https://codecov.io/gh/coder/code-server/branch/main/graph/badge.svg?token=5iM9farjnC)](https://codecov.io/gh/coder/code-server) [![See v4.0.2 docs](https://img.shields.io/static/v1?label=Docs&message=see%20v4.0.2%20&color=blue)](https://github.com/coder/code-server/tree/v4.0.2/docs)
Run [VS Code](https://github.com/Microsoft/vscode) on any machine anywhere and Run [VS Code](https://github.com/Microsoft/vscode) on any machine anywhere and
access it in the browser. access it in the browser.
@ -14,6 +14,9 @@ access it in the browser.
- Preserve battery life when you're on the go; all intensive tasks run on your - Preserve battery life when you're on the go; all intensive tasks run on your
server server
| 🔔 code-server is a free browser-based IDE while [Coder](https://coder.com/) is our enterprise developer workspace platform. For more information, visit [Coder.com](https://coder.com/docs/comparison)
| ---
## Requirements ## Requirements
See [requirements](requirements.md) for minimum specs, as well as instructions See [requirements](requirements.md) for minimum specs, as well as instructions
@ -26,13 +29,13 @@ on how to set up a Google VM on which you can install code-server.
There are three ways to get started: There are three ways to get started:
1. Using the [install 1. Using the [install
script](https://github.com/cdr/code-server/blob/main/install.sh), which script](https://github.com/coder/code-server/blob/main/install.sh), which
automates most of the process. The script uses the system package manager if automates most of the process. The script uses the system package manager if
possible. possible.
2. Manually [installing 2. Manually [installing
code-server](https://coder.com/docs/code-server/latest/install) code-server](https://coder.com/docs/code-server/latest/install)
3. Using our one-click buttons and guides to [deploy code-server to a cloud 3. Using our one-click buttons and guides to [deploy code-server to a cloud
provider](https://github.com/cdr/deploy-code-server) ⚡ provider](https://github.com/coder/deploy-code-server) ⚡
If you use the install script, you can preview what occurs during the install If you use the install script, you can preview what occurs during the install
process: process:
@ -53,10 +56,6 @@ code-server.
We also have an in-depth [setup and We also have an in-depth [setup and
configuration](https://coder.com/docs/code-server/latest/guide) guide. configuration](https://coder.com/docs/code-server/latest/guide) guide.
## TLS and authentication (beta)
To add TLS and authentication out of the box, use [code-server --link](https://coder.com/docs/code-server/latest/link).
## Questions? ## Questions?
See answers to [frequently asked See answers to [frequently asked

View File

@ -26,9 +26,9 @@ We use the following tools to help us stay on top of vulnerability mitigation.
Coder sponsors the development and maintenance of the code-server project. We will fix security issues within 90 days of receiving a report and publish the fix in a subsequent release. The code-server project does not provide backports or patch releases for security issues at this time. Coder sponsors the development and maintenance of the code-server project. We will fix security issues within 90 days of receiving a report and publish the fix in a subsequent release. The code-server project does not provide backports or patch releases for security issues at this time.
| Version | Supported | | Version | Supported |
| ----------------------------------------------------- | ------------------ | | ------------------------------------------------------- | ------------------ |
| [Latest](https://github.com/cdr/code-server/releases) | :white_check_mark: | | [Latest](https://github.com/coder/code-server/releases) | :white_check_mark: |
## Reporting a Vulnerability ## Reporting a Vulnerability

23
docs/android.md Normal file
View File

@ -0,0 +1,23 @@
# Running code-server using UserLAnd
1. Install UserLAnd from [Google Play](https://play.google.com/store/apps/details?id=tech.ula&hl=en_US&gl=US)
2. Install an Ubuntu VM
3. Start app
4. Install Node.js, `curl` and `yarn` using `sudo apt install nodejs npm yarn curl -y`
5. Install `nvm`:
```shell
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 14:
```shell
nvm install 14
nvm use 14
```
8. Install code-server globally on device with: `npm i -g code-server`
9. Run code-server with `code-server`
10. Access on localhost:8080 in your browser

View File

@ -60,6 +60,6 @@ As `code-server` is based on VS Code, you can follow the steps described on Duck
code-server --enable-proposed-api genuitecllc.codetogether code-server --enable-proposed-api genuitecllc.codetogether
``` ```
Another option would be to add a value in code-server's [config file](https://coder.com/docs/code-server/v3.12.0/FAQ#how-does-the-config-file-work). Another option would be to add a value in code-server's [config file](https://coder.com/docs/code-server/v4.0.2/FAQ#how-does-the-config-file-work).
3. Refresh code-server and navigate to the CodeTogether icon in the sidebar to host or join a coding session. 3. Refresh code-server and navigate to the CodeTogether icon in the sidebar to host or join a coding session.

View File

@ -1,396 +1,5 @@
<!-- START doctoc generated TOC please keep comment here to allow auto update --> <!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --> <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
# Setup Guide
- [Expose code-server](#expose-code-server)
- [Port forwarding via SSH](#port-forwarding-via-ssh)
- [Using Let's Encrypt with Caddy](#using-lets-encrypt-with-caddy)
- [Using Let's Encrypt with NGINX](#using-lets-encrypt-with-nginx)
- [Using a self-signed certificate](#using-a-self-signed-certificate)
- [External authentication](#external-authentication)
- [HTTPS and self-signed certificates](#https-and-self-signed-certificates)
- [Accessing web services](#accessing-web-services)
- [Using a subdomain](#using-a-subdomain)
- [Using a subpath](#using-a-subpath)
- [Stripping `/proxy/<port>` from the request path](#stripping-proxyport-from-the-request-path)
- [Proxying to create a React app](#proxying-to-create-a-react-app)
- [Proxying to a Vue app](#proxying-to-a-vue-app)
- [Setup Guide](#setup-guide)
- [Expose code-server](#expose-code-server-1)
- [Port forwarding via SSH](#port-forwarding-via-ssh-1)
- [Using Let's Encrypt with Caddy](#using-lets-encrypt-with-caddy-1)
- [Using Let's Encrypt with NGINX](#using-lets-encrypt-with-nginx-1)
- [Using a self-signed certificate](#using-a-self-signed-certificate-1)
- [External authentication](#external-authentication-1)
- [HTTPS and self-signed certificates](#https-and-self-signed-certificates-1)
- [Accessing web services](#accessing-web-services-1)
- [Using a subdomain](#using-a-subdomain-1)
- [Using a subpath](#using-a-subpath-1)
- [Stripping `/proxy/<port>` from the request path](#stripping-proxyport-from-the-request-path-1)
- [Proxying to create a React app](#proxying-to-create-a-react-app-1)
- [Proxying to a Vue app](#proxying-to-a-vue-app-1)
- [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)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
This article will walk you through exposing code-server securely once you've
completed the [installation process](install.md).
## Expose code-server
**Never** expose code-server directly to the internet without some form of
authentication and encryption, otherwise someone can take over your machine via
the terminal.
By default, code-server uses password authentication. As such, you must copy the
password from code-server's config file to log in. To avoid exposing itself
unnecessarily, code-server listens on `localhost`; this practice is fine for
testing, but it doesn't work if you want to access code-server from a different
machine.
> **Rate limits:** code-server rate limits password authentication attempts to
> two per minute plus an additional twelve per hour.
There are several approaches to operating and exposing code-server securely:
- Port forwarding via SSH
- Using Let's Encrypt with Caddy
- Using Let's Encrypt with NGINX
- Using a self-signed certificate
### Port forwarding via SSH
We highly recommend using [port forwarding via
SSH](https://help.ubuntu.com/community/SSH/OpenSSH/PortForwarding) to access
code-server. If you have an SSH server on your remote machine, this approach
doesn't required additional setup.
The downside to SSH forwarding, however, is that you can't access code-server
when using machines without SSH clients (such as iPads). If this applies to you,
we recommend using another method, such as [Let's Encrypt](#let-encrypt) instead.
> To work properly, your environment should have WebSockets enabled, which
> code-server uses to communicate between the browser and server.
1. SSH into your instance and edit the code-server config file to disable
password authentication:
```console
# Replaces "auth: password" with "auth: none" in the code-server config.
sed -i.bak 's/auth: password/auth: none/' ~/.config/code-server/config.yaml
```
2. Restart code-server:
```console
sudo systemctl restart code-server@$USER
```
3. Forward local port `8080` to `127.0.0.1:8080` on the remote instance by running the following command on your local machine:
```console
# -N disables executing a remote shell
ssh -N -L 8080:127.0.0.1:8080 [user]@<instance-ip>
```
4. At this point, you can access code-server by pointing your web browser to `http://127.0.0.1:8080`.
5. If you'd like to make the port forwarding via SSH persistent, we recommend
using [mutagen](https://mutagen.io/documentation/introduction/installation)
to do so. Once you've installed mutagen, you can port forward as follows:
```console
# This is the same as the above SSH command, but it runs in the background
# continuously. Be sure to add `mutagen daemon start` to your ~/.bashrc to
# start the mutagen daemon when you open a shell.
mutagen forward create --name=code-server tcp:127.0.0.1:8080 < instance-ip > :tcp:127.0.0.1:8080
```
6. Optional, but highly recommended: add the following to `~/.ssh/config` so
that you can detect bricked SSH connections:
```bash
Host *
ServerAliveInterval 5
ExitOnForwardFailure yes
```
> You can [forward your
> SSH](https://developer.github.com/v3/guides/using-ssh-agent-forwarding/) and
> [GPG agent](https://wiki.gnupg.org/AgentForwarding) to the instance to
> securely access GitHub and sign commits without having to copy your keys.
### Using Let's Encrypt with Caddy
Using [Let's Encrypt](https://letsencrypt.org) is an option if you want to
access code-server on an iPad or do not want to use SSH port forwarding.
1. This option requires that the remote machine be exposed to the internet. Make sure that your instance allows HTTP/HTTP traffic.
1. You'll need a domain name (if you don't have one, you can purchase one from
[Google Domains](https://domains.google.com) or the domain service of your
choice)). Once you have a domain name, add an A record to your domain that contains your
instance's IP address.
1. Install [Caddy](https://caddyserver.com/docs/download#debian-ubuntu-raspbian):
```console
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/cfg/gpg/gpg.155B6D79CA56EA34.key' | sudo apt-key add -
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/cfg/setup/config.deb.txt?distro=debian&version=any-version' | sudo tee -a /etc/apt/sources.list.d/caddy-stable.list
sudo apt update
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
```
If you want to serve code-server from a sub-path, you can do so as follows:
```text
mydomain.com/code/* {
uri strip_prefix /code
reverse_proxy 127.0.0.1:8080
}
```
Remember to replace `mydomain.com` with your domain name!
1. Reload Caddy:
```console
sudo systemctl reload caddy
```
At this point, you should be able to access code-server via
`https://mydomain.com`.
### Using Let's Encrypt with NGINX
1. This option requires that the remote machine be exposed to the internet. Make sure that your instance allows HTTP/HTTP traffic.
1. You'll need a domain name (if you don't have one, you can purchase one from
[Google Domains](https://domains.google.com) or the domain service of your
choice)). Once you have a domain name, add an A record to your domain that contains your
instance's IP address.
1. Install NGINX:
```bash
sudo apt update
sudo apt install -y nginx certbot python3-certbot-nginx
```
1. Update `/etc/nginx/sites-available/code-server` using sudo with the following
configuration:
```text
server {
listen 80;
listen [::]:80;
server_name mydomain.com;
location / {
proxy_pass http://localhost:8080/;
proxy_set_header Host $host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection upgrade;
proxy_set_header Accept-Encoding gzip;
}
}
```
Be sure to replace `mydomain.com` with your domain name!
1. Enable the config:
```console
sudo ln -s ../sites-available/code-server /etc/nginx/sites-enabled/code-server
sudo certbot --non-interactive --redirect --agree-tos --nginx -d mydomain.com -m me@example.com
```
Be sure to replace `me@example.com` with your actual email.
At this point, you should be able to access code-server via
`https://mydomain.com`.
### Using a self-signed certificate
> Self signed certificates do not work with iPad; see [./ipad.md](./ipad.md) for
> more information.
Before proceeding, we recommend familiarizing yourself with the [risks of
self-signing a certificate for
SSL](https://security.stackexchange.com/questions/8110).
We recommend self-signed certificates as a last resort, since self-signed
certificates do not work with iPads and may cause unexpected issues with
code-server. You should only proceed with this option if:
- You do not want to buy a domain or you cannot expose the remote machine to
the internet
- You do not want to use port forwarding via SSH
To use a self-signed certificate:
1. This option requires that the remote machine be exposed to the internet. Make
sure that your instance allows HTTP/HTTP traffic.
1. SSH into your instance and edit your code-server config file to use a
randomly generated self-signed certificate:
```console
# Replaces "cert: false" with "cert: true" in the code-server config.
sed -i.bak 's/cert: false/cert: true/' ~/.config/code-server/config.yaml
# Replaces "bind-addr: 127.0.0.1:8080" with "bind-addr: 0.0.0.0:443" in the code-server config.
sed -i.bak 's/bind-addr: 127.0.0.1:8080/bind-addr: 0.0.0.0:443/' ~/.config/code-server/config.yaml
# Allows code-server to listen on port 443.
sudo setcap cap_net_bind_service=+ep /usr/lib/code-server/lib/node
```
1. Restart code-server:
```console
sudo systemctl restart code-server@$USER
```
At this point, you should be able to access code-server via
`https://<your-instance-ip>`.
If you'd like to avoid the warnings displayed by code-server when using a
self-signed certificate, you can use [mkcert](https://mkcert.dev) to create a
self-signed certificate that's trusted by your operating system, then pass the
certificate to code-server via the `cert` and `cert-key` config fields.
## External authentication
If you want to use external authentication mechanism (e.g., Sign in with
Google), you can do this with a reverse proxy such as:
- [Pomerium](https://www.pomerium.io/guides/code-server.html)
- [oauth2_proxy](https://github.com/pusher/oauth2_proxy)
- [Cloudflare Access](https://teams.cloudflare.com/access)
## HTTPS and self-signed certificates
For HTTPS, you can use a self-signed certificate by:
- Passing in `--cert`
- Passing in an existing certificate by providing the path to `--cert` and the
path to the key with `--cert-key`
The self signed certificate will be generated to
`~/.local/share/code-server/self-signed.crt`.
If you pass a certificate to code-server, it will respond to HTTPS requests and
redirect all HTTP requests to HTTPS.
> You can use [Let's Encrypt](https://letsencrypt.org/) to get a TLS certificate
> for free.
Note: if you set `proxy_set_header Host $host;` in your reverse proxy config, it will change the address displayed in the green section of code-server in the bottom left to show the correct address.
## Accessing web services
If you're working on web services and want to access it locally, code-server
can proxy to any port using either a subdomain or a subpath, allowing you to
securely access these services using code-server's built-in authentication.
### Using a subdomain
You will need a DNS entry that points to your server for each port you want to
access. You can either set up a wildcard DNS entry for `*.<domain>` if your
domain name registrar supports it, or you can create one for every port you want
to access (`3000.<domain>`, `8080.<domain>`, etc).
You should also set up TLS certificates for these subdomains, either using a
wildcard certificate for `*.<domain>` or individual certificates for each port.
To set your domain, start code-server with the `--proxy-domain` flag:
```console
code-server --proxy-domain <domain>
```
Now you can browse to `<port>.<domain>`. Note that this uses the host header, so
ensure your reverse proxy (if you're using one) forwards that information.
### Using a subpath
Simply browse to `/proxy/<port>/`.
### Stripping `/proxy/<port>` from the request path
You may notice that the code-server proxy strips `/proxy/<port>` from the
request path.
HTTP servers should use relative URLs to avoid the need to be coupled to the
absolute path at which they are served. This means you must [use trailing
slashes on all paths with
subpaths](https://blog.cdivilly.com/2019/02/28/uri-trailing-slashes).
This reasoning is why the default behavior is to strip `/proxy/<port>` from the
base path. If your application uses relative URLs and does not assume the
absolute path at which it is being served, it will just work no matter what port
you decide to serve it off or if you put it in behind code-server or any other
proxy.
However, some prefer the cleaner aesthetic of no trailing slashes. Omitting the
trailing slashes couples you to the base path, since you cannot use relative
redirects correctly anymore. If you're okay with this tradeoff, use `/absproxy`
instead and the path will be passed as is (e.g., `/absproxy/3000/my-app-path`).
### Proxying to create a React app
You must use `/absproxy/<port>` with `create-react-app` (see
[#2565](https://github.com/cdr/code-server/issues/2565) and
[#2222](https://github.com/cdr/code-server/issues/2222) for more information).
You will need to inform `create-react-app` of the path at which you are serving
via `$PUBLIC_URL` and webpack via `$WDS_SOCKET_PATH`:
```sh
PUBLIC_URL=/absproxy/3000 \
WDS_SOCKET_PATH=$PUBLIC_URL/sockjs-node \
BROWSER=none yarn start
```
You should then be able to visit `https://my-code-server-address.io/absproxy/3000` to see your app exposed through
code-server!
> We highly recommend using the subdomain approach instead to avoid this class of issue.
### Proxying to a Vue app
Similar to the situation with React apps, you have to make a few modifications to proxy a Vue app.
1. add `vue.config.js`
2. update the values to match this (you can use any free port):
```js
module.exports = {
devServer: {
port: 3454,
sockPath: "sockjs-node",
},
publicPath: "/absproxy/3454",
}
```
3. access app at `<code-server-root>/absproxy/3454` e.g. `http://localhost:8080/absproxy/3454`
Read more about `publicPath` in the [Vue.js docs](https://cli.vuejs.org/config/#publicpath)
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
# Setup Guide # Setup Guide
- [Expose code-server](#expose-code-server) - [Expose code-server](#expose-code-server)
@ -398,6 +7,7 @@ Read more about `publicPath` in the [Vue.js docs](https://cli.vuejs.org/config/#
- [Using Let's Encrypt with Caddy](#using-lets-encrypt-with-caddy) - [Using Let's Encrypt with Caddy](#using-lets-encrypt-with-caddy)
- [Using Let's Encrypt with NGINX](#using-lets-encrypt-with-nginx) - [Using Let's Encrypt with NGINX](#using-lets-encrypt-with-nginx)
- [Using a self-signed certificate](#using-a-self-signed-certificate) - [Using a self-signed certificate](#using-a-self-signed-certificate)
- [TLS 1.3 and Safari](#tls-13-and-safari)
- [External authentication](#external-authentication) - [External authentication](#external-authentication)
- [HTTPS and self-signed certificates](#https-and-self-signed-certificates) - [HTTPS and self-signed certificates](#https-and-self-signed-certificates)
- [Accessing web services](#accessing-web-services) - [Accessing web services](#accessing-web-services)
@ -505,7 +115,7 @@ we recommend using another method, such as [Let's Encrypt](#let-encrypt) instead
Using [Let's Encrypt](https://letsencrypt.org) is an option if you want to Using [Let's Encrypt](https://letsencrypt.org) is an option if you want to
access code-server on an iPad or do not want to use SSH port forwarding. access code-server on an iPad or do not want to use SSH port forwarding.
1. This option requires that the remote machine be exposed to the internet. Make sure that your instance allows HTTP/HTTP traffic. 1. This option requires that the remote machine be exposed to the internet. Make sure that your instance allows HTTP/HTTPS traffic.
1. You'll need a domain name (if you don't have one, you can purchase one from 1. You'll need a domain name (if you don't have one, you can purchase one from
[Google Domains](https://domains.google.com) or the domain service of your [Google Domains](https://domains.google.com) or the domain service of your
@ -536,7 +146,7 @@ sudo apt install caddy
mydomain.com/code/* { mydomain.com/code/* {
uri strip_prefix /code uri strip_prefix /code
reverse_proxy 127.0.0.1:8080 reverse_proxy 127.0.0.1:8080
} }
``` ```
Remember to replace `mydomain.com` with your domain name! Remember to replace `mydomain.com` with your domain name!
@ -552,7 +162,7 @@ At this point, you should be able to access code-server via
### Using Let's Encrypt with NGINX ### Using Let's Encrypt with NGINX
1. This option requires that the remote machine be exposed to the internet. Make sure that your instance allows HTTP/HTTP traffic. 1. This option requires that the remote machine be exposed to the internet. Make sure that your instance allows HTTP/HTTPS traffic.
1. You'll need a domain name (if you don't have one, you can purchase one from 1. You'll need a domain name (if you don't have one, you can purchase one from
[Google Domains](https://domains.google.com) or the domain service of your [Google Domains](https://domains.google.com) or the domain service of your
@ -619,7 +229,7 @@ code-server. You should only proceed with this option if:
To use a self-signed certificate: To use a self-signed certificate:
1. This option requires that the remote machine be exposed to the internet. Make 1. This option requires that the remote machine be exposed to the internet. Make
sure that your instance allows HTTP/HTTP traffic. sure that your instance allows HTTP/HTTPS traffic.
1. SSH into your instance and edit your code-server config file to use a 1. SSH into your instance and edit your code-server config file to use a
randomly generated self-signed certificate: randomly generated self-signed certificate:
@ -647,6 +257,13 @@ self-signed certificate, you can use [mkcert](https://mkcert.dev) to create a
self-signed certificate that's trusted by your operating system, then pass the self-signed certificate that's trusted by your operating system, then pass the
certificate to code-server via the `cert` and `cert-key` config fields. certificate to code-server via the `cert` and `cert-key` config fields.
### TLS 1.3 and Safari
If you will be using Safari and your configuration does not allow anything less
than TLS 1.3 you will need to add support for TLS 1.2 since Safari does not
support TLS 1.3 for web sockets at the time of writing. If this is the case you
should see OSSStatus: 9836 in the browser console.
## External authentication ## External authentication
If you want to use external authentication mechanism (e.g., Sign in with If you want to use external authentication mechanism (e.g., Sign in with
@ -677,7 +294,7 @@ Note: if you set `proxy_set_header Host $host;` in your reverse proxy config, it
## Accessing web services ## Accessing web services
If you're working on web services and want to access it locally, code-server If you're working on web services and want to access them locally, code-server
can proxy to any port using either a subdomain or a subpath, allowing you to can proxy to any port using either a subdomain or a subpath, allowing you to
securely access these services using code-server's built-in authentication. securely access these services using code-server's built-in authentication.
@ -728,8 +345,8 @@ instead and the path will be passed as is (e.g., `/absproxy/3000/my-app-path`).
### Proxying to create a React app ### Proxying to create a React app
You must use `/absproxy/<port>` with `create-react-app` (see You must use `/absproxy/<port>` with `create-react-app` (see
[#2565](https://github.com/cdr/code-server/issues/2565) and [#2565](https://github.com/coder/code-server/issues/2565) and
[#2222](https://github.com/cdr/code-server/issues/2222) for more information). [#2222](https://github.com/coder/code-server/issues/2222) for more information).
You will need to inform `create-react-app` of the path at which you are serving You will need to inform `create-react-app` of the path at which you are serving
via `$PUBLIC_URL` and webpack via `$WDS_SOCKET_PATH`: via `$PUBLIC_URL` and webpack via `$WDS_SOCKET_PATH`:
@ -779,7 +396,7 @@ sudo apt update
sudo apt install wget unzip openssh-server sudo apt install wget unzip openssh-server
``` ```
2. Start the SSH server and set the password for your user, if you haven't already. If you use [deploy-code-server](https://github.com/cdr/deploy-code-server), 2. Start the SSH server and set the password for your user, if you haven't already. If you use [deploy-code-server](https://github.com/coder/deploy-code-server),
```bash ```bash
sudo service ssh start sudo service ssh start

View File

@ -1,8 +1,8 @@
# code-server Helm Chart # code-server Helm Chart
[![Version: 1.0.0](https://img.shields.io/badge/Version-1.0.0-informational?style=flat-square)](https://img.shields.io/badge/Version-1.0.0-informational?style=flat-square) [![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square)](https://img.shields.io/badge/Type-application-informational?style=flat-square) [![AppVersion: 3.12.0](https://img.shields.io/badge/AppVersion-3.12.0-informational?style=flat-square)](https://img.shields.io/badge/AppVersion-3.12.0-informational?style=flat-square) [![Version: 1.0.0](https://img.shields.io/badge/Version-1.0.0-informational?style=flat-square)](https://img.shields.io/badge/Version-1.0.0-informational?style=flat-square) [![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square)](https://img.shields.io/badge/Type-application-informational?style=flat-square) [![AppVersion: 4.0.2](https://img.shields.io/badge/AppVersion-4.0.2-informational?style=flat-square)](https://img.shields.io/badge/AppVersion-4.0.2-informational?style=flat-square)
[code-server](https://github.com/cdr/code-server) code-server is VS Code running [code-server](https://github.com/coder/code-server) code-server is VS Code running
on a remote server, accessible through the browser. on a remote server, accessible through the browser.
This chart is community maintained by [@Matthew-Beckett](https://github.com/Matthew-Beckett) and [@alexgorbatchev](https://github.com/alexgorbatchev) This chart is community maintained by [@Matthew-Beckett](https://github.com/Matthew-Beckett) and [@alexgorbatchev](https://github.com/alexgorbatchev)
@ -10,7 +10,7 @@ This chart is community maintained by [@Matthew-Beckett](https://github.com/Matt
## Quickstart ## Quickstart
```console ```console
$ git clone https://github.com/cdr/code-server $ git clone https://github.com/coder/code-server
$ cd code-server $ cd code-server
$ helm upgrade --install code-server ci/helm-chart $ helm upgrade --install code-server ci/helm-chart
``` ```
@ -30,7 +30,7 @@ package manager.
To install the chart with the release name `code-server`: To install the chart with the release name `code-server`:
```console ```console
$ git clone https://github.com/cdr/code-server $ git clone https://github.com/coder/code-server
$ cd code-server $ cd code-server
$ helm upgrade --install code-server ci/helm-chart $ helm upgrade --install code-server ci/helm-chart
``` ```
@ -73,7 +73,7 @@ and their default values.
| hostnameOverride | string | `""` | | hostnameOverride | string | `""` |
| image.pullPolicy | string | `"Always"` | | image.pullPolicy | string | `"Always"` |
| image.repository | string | `"codercom/code-server"` | | image.repository | string | `"codercom/code-server"` |
| image.tag | string | `"3.12.0"` | | image.tag | string | `"4.0.2"` |
| imagePullSecrets | list | `[]` | | imagePullSecrets | list | `[]` |
| ingress.enabled | bool | `false` | | ingress.enabled | bool | `false` |
| nameOverride | string | `""` | | nameOverride | string | `""` |

View File

@ -30,7 +30,7 @@ operating systems.
## install.sh ## install.sh
The easiest way to install code-server is to use our [install The easiest way to install code-server is to use our [install
script](../install.sh) for Linux, macOS and FreeBSD. The install script script](https://github.com/coder/code-server/blob/main/install.sh) for Linux, macOS and FreeBSD. The install script
[attempts to use the system package manager](#detection-reference) if possible. [attempts to use the system package manager](#detection-reference) if possible.
You can preview what occurs during the install process: You can preview what occurs during the install process:
@ -67,7 +67,7 @@ code-server.
If you prefer to install code-server manually, despite the [detection If you prefer to install code-server manually, despite the [detection
references](#detection-reference) and `--dry-run` feature, then continue on for references](#detection-reference) and `--dry-run` feature, then continue on for
information on how to do this. The [`install.sh`](../install.sh) script runs the information on how to do this. The [`install.sh`](https://github.com/coder/code-server/blob/main/install.sh) script runs the
_exact_ same commands presented in the rest of this document. _exact_ same commands presented in the rest of this document.
### Detection reference ### Detection reference
@ -104,7 +104,7 @@ We recommend installing with `yarn` or `npm` when:
1. You're on Linux with `glibc` < v2.17, `glibcxx` < v3.4.18 on `amd64`, `glibc` 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`. < v2.23, or `glibcxx` < v3.4.21 on `arm64`.
1. You're running Alpine Linux or are using a non-glibc libc. See 1. You're running Alpine Linux or are using a non-glibc libc. See
[#1430](https://github.com/cdr/code-server/issues/1430#issuecomment-629883198) [#1430](https://github.com/coder/code-server/issues/1430#issuecomment-629883198)
for more information. for more information.
Installing code-server with `yarn` or `npm` builds native modules on install. Installing code-server with `yarn` or `npm` builds native modules on install.
@ -114,7 +114,7 @@ This process requires C dependencies; see our guide on [installing with yarn and
## Standalone releases ## Standalone releases
We publish self-contained `.tar.gz` archives for every release on We publish self-contained `.tar.gz` archives for every release on
[GitHub](https://github.com/cdr/code-server/releases). The archives bundle the [GitHub](https://github.com/coder/code-server/releases). The archives bundle the
node binary and node modules. node binary and node modules.
We create the standalone releases using the [npm package](#yarn-npm), and we We create the standalone releases using the [npm package](#yarn-npm), and we
@ -127,7 +127,7 @@ requirement).
To use a standalone release: To use a standalone release:
1. Download the latest release archive for your system from 1. Download the latest release archive for your system from
[GitHub](https://github.com/cdr/code-server/releases). [GitHub](https://github.com/coder/code-server/releases).
2. Unpack the release. 2. Unpack the release.
3. Run code-server by executing `./bin/code-server`. 3. Run code-server by executing `./bin/code-server`.
@ -139,7 +139,7 @@ release on Linux:
```bash ```bash
mkdir -p ~/.local/lib ~/.local/bin mkdir -p ~/.local/lib ~/.local/bin
curl -fL https://github.com/cdr/code-server/releases/download/v$VERSION/code-server-$VERSION-linux-amd64.tar.gz \ curl -fL https://github.com/coder/code-server/releases/download/v$VERSION/code-server-$VERSION-linux-amd64.tar.gz \
| tar -C ~/.local/lib -xz | tar -C ~/.local/lib -xz
mv ~/.local/lib/code-server-$VERSION-linux-amd64 ~/.local/lib/code-server-$VERSION mv ~/.local/lib/code-server-$VERSION-linux-amd64 ~/.local/lib/code-server-$VERSION
ln -s ~/.local/lib/code-server-$VERSION/bin/code-server ~/.local/bin/code-server ln -s ~/.local/lib/code-server-$VERSION/bin/code-server ~/.local/bin/code-server
@ -154,7 +154,7 @@ code-server
> upgrade or [build with yarn](#yarn-npm). > upgrade or [build with yarn](#yarn-npm).
```bash ```bash
curl -fOL https://github.com/cdr/code-server/releases/download/v$VERSION/code-server_$VERSION_amd64.deb curl -fOL https://github.com/coder/code-server/releases/download/v$VERSION/code-server_$VERSION_amd64.deb
sudo dpkg -i code-server_$VERSION_amd64.deb sudo dpkg -i code-server_$VERSION_amd64.deb
sudo systemctl enable --now code-server@$USER sudo systemctl enable --now code-server@$USER
# Now visit http://127.0.0.1:8080. Your password is in ~/.config/code-server/config.yaml # Now visit http://127.0.0.1:8080. Your password is in ~/.config/code-server/config.yaml
@ -166,7 +166,7 @@ sudo systemctl enable --now code-server@$USER
> with yarn](#yarn-npm). > with yarn](#yarn-npm).
```bash ```bash
curl -fOL https://github.com/cdr/code-server/releases/download/v$VERSION/code-server-$VERSION-amd64.rpm curl -fOL https://github.com/coder/code-server/releases/download/v$VERSION/code-server-$VERSION-amd64.rpm
sudo rpm -i code-server-$VERSION-amd64.rpm sudo rpm -i code-server-$VERSION-amd64.rpm
sudo systemctl enable --now code-server@$USER sudo systemctl enable --now code-server@$USER
# Now visit http://127.0.0.1:8080. Your password is in ~/.config/code-server/config.yaml # Now visit http://127.0.0.1:8080. Your password is in ~/.config/code-server/config.yaml
@ -228,9 +228,9 @@ You can install code-server using the [Helm package manager](https://coder.com/d
## Windows ## Windows
We currently [do not publish Windows releases](https://github.com/cdr/code-server/issues/1397). We recommend installing code-server onto Windows with [`yarn` or `npm`](#yarn-npm). We currently [do not publish Windows releases](https://github.com/coder/code-server/issues/1397). We recommend installing code-server onto Windows with [`yarn` or `npm`](#yarn-npm).
> Note: You will also need to [build cdr/cloud-agent manually](https://github.com/cdr/cloud-agent/issues/17) if you would like to use `code-server --link` on Windows. > Note: You will also need to [build coder/cloud-agent manually](https://github.com/coder/cloud-agent/issues/17) if you would like to use `code-server --link` on Windows.
## Raspberry Pi ## Raspberry Pi
@ -245,7 +245,7 @@ information.
## Cloud providers ## Cloud providers
We maintain [one-click apps and install scripts for cloud We maintain [one-click apps and install scripts for cloud
providers](https://github.com/cdr/deploy-code-server) such as DigitalOcean, providers](https://github.com/coder/deploy-code-server) such as DigitalOcean,
Railway, Heroku, and Azure. Railway, Heroku, and Azure.
## Uninstall ## Uninstall

7
docs/ios.md Normal file
View File

@ -0,0 +1,7 @@
# Using code-server on iOS with iSH
1. Install iSH from the [App Store](https://apps.apple.com/us/app/ish-shell/id1436902243)
2. Install `curl` with `apk add curl`
3. Install code-server with `curl -fsSL https://code-server.dev/install.sh | sh`
4. Run code-server with `code-server`
5. Access on localhost:8080 in your browser

View File

@ -104,7 +104,7 @@ and tricks helpful:
process](#access-code-server-with-a-self-signed-certificate-on-an-ipad) process](#access-code-server-with-a-self-signed-certificate-on-an-ipad)
- Keyboard issues: - Keyboard issues:
- The keyboard disappear sometimes - The keyboard disappear sometimes
[#979](https://github.com/cdr/code-server/issues/979) [#979](https://github.com/coder/code-server/issues/979)
- Some expectations regarding shortcuts may not be met: - Some expectations regarding shortcuts may not be met:
- `cmd + n` opens new browser window instead of new file, and it's difficult - `cmd + n` opens new browser window instead of new file, and it's difficult
to set alternative as a workaround to set alternative as a workaround
@ -112,18 +112,18 @@ and tricks helpful:
- There's no escape key by default on the Magic Keyboard, so most users set - There's no escape key by default on the Magic Keyboard, so most users set
the globe key to be an escape key the globe key to be an escape key
- Trackpad scrolling does not work on iPadOS < 14.5 - Trackpad scrolling does not work on iPadOS < 14.5
([#1455](https://github.com/cdr/code-server/issues/1455)) ([#1455](https://github.com/coder/code-server/issues/1455))
- [WebKit fix](https://bugs.webkit.org/show_bug.cgi?id=210071#c13) - [WebKit fix](https://bugs.webkit.org/show_bug.cgi?id=210071#c13)
- Keyboard may lose focus in Safari / split view [#4182](https://github.com/cdr/code-server/issues/4182) - Keyboard may lose focus in Safari / split view [#4182](https://github.com/coder/code-server/issues/4182)
- Terminal text does not appear by default [#3824](https://github.com/cdr/code-server/issues/3824) - Terminal text does not appear by default [#3824](https://github.com/coder/code-server/issues/3824)
- Copy & paste in terminal does not work well with keyboard shortcuts [#3491](https://github.com/cdr/code-server/issues/3491) - Copy & paste in terminal does not work well with keyboard shortcuts [#3491](https://github.com/coder/code-server/issues/3491)
- `ctrl+c` does not stop a long-running process in the browser - `ctrl+c` does not stop a long-running process in the browser
- Tracking upstream issue here: - Tracking upstream issue here:
[#114009](https://github.com/microsoft/vscode/issues/114009) [#114009](https://github.com/microsoft/vscode/issues/114009)
- See [workaround](#ctrl-c-workaround) - See [workaround](#ctrl-c-workaround)
Additionally, see [issues in the code-server repo that are tagged with the iPad Additionally, see [issues in the code-server repo that are tagged with the `os-ios`
label](https://github.com/cdr/code-server/issues?q=is%3Aopen+is%3Aissue+label%3AiPad) label](https://github.com/coder/code-server/issues?q=is%3Aopen+is%3Aissue+label%3Aos-ios)
for more information. for more information.
### Workaround for issue with `ctrl+c` not stopping a running process in the terminal ### Workaround for issue with `ctrl+c` not stopping a running process in the terminal

View File

@ -1,9 +1,11 @@
# code-server --link # code-server --link
Run code-server with the beta flag `--link` and you'll get TLS, authentication, and a dedicated URL > Note: This feature is no longer recommended due to instability. Stay tuned for a revised version.
Run code-server with the flag `--link` and you'll get TLS, authentication, and a dedicated URL
for accessing your IDE out of the box. for accessing your IDE out of the box.
```console ```console
$ code-server --link $ code-server --link
Proxying code-server, you can access your IDE at https://example.cdr.co Proxying code-server, you can access your IDE at https://example.coder.co
``` ```

View File

@ -1,5 +1,5 @@
{ {
"versions": ["v3.12.0"], "versions": ["v4.0.2"],
"routes": [ "routes": [
{ {
"title": "Home", "title": "Home",
@ -51,6 +51,16 @@
"title": "Termux", "title": "Termux",
"description": "How to install Termux to run code-server on an Android device.", "description": "How to install Termux to run code-server on an Android device.",
"path": "./termux.md" "path": "./termux.md"
},
{
"title": "iOS",
"description": "How to use code-server on iOS with iSH.",
"path": "./ios.md"
},
{
"title": "Android",
"description": "How to run code-server on an Android device using UserLAnd.",
"path": "./android.md"
} }
] ]
}, },
@ -63,7 +73,7 @@
{ {
"title": "Upgrade", "title": "Upgrade",
"description": "How to upgrade code-server.", "description": "How to upgrade code-server.",
"icon": "<svg width=\"20\" height=\"20\" viewBox=\"0 0 20 20\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M17.8049 2.19795C17.7385 2.1311 17.6587 2.07899 17.5708 2.04504C17.4829 2.01108 17.3889 1.99604 17.2948 2.00089C7.89216 2.49153 4.4188 10.8673 4.38528 10.9517C4.33624 11.0736 4.32406 11.2071 4.35028 11.3358C4.3765 11.4645 4.43995 11.5827 4.53274 11.6756L8.32449 15.4674C8.41787 15.5606 8.53669 15.6242 8.66606 15.6502C8.79543 15.6762 8.92959 15.6634 9.05174 15.6135C9.13552 15.5793 17.4664 12.0671 17.9986 2.7087C18.0039 2.61474 17.9895 2.5207 17.9561 2.4327C17.9227 2.3447 17.8712 2.26471 17.8049 2.19795ZM12.3314 9.56427C12.1439 9.75179 11.9051 9.87951 11.645 9.93126C11.385 9.98302 11.1154 9.9565 10.8704 9.85505C10.6254 9.7536 10.4161 9.58178 10.2687 9.36131C10.1214 9.14085 10.0428 8.88166 10.0428 8.6165C10.0428 8.35135 10.1214 8.09215 10.2687 7.87169C10.4161 7.65123 10.6254 7.47941 10.8704 7.37796C11.1154 7.27651 11.385 7.24998 11.645 7.30174C11.9051 7.3535 12.1439 7.48121 12.3314 7.66873C12.5827 7.92012 12.7239 8.26104 12.7239 8.6165C12.7239 8.97197 12.5827 9.31288 12.3314 9.56427Z\"/><path d=\"M2.74602 14.5444C2.92281 14.3664 3.133 14.2251 3.36454 14.1285C3.59608 14.0319 3.8444 13.9819 4.09529 13.9815C4.34617 13.9811 4.59466 14.0302 4.82653 14.126C5.05839 14.2218 5.26907 14.3624 5.44647 14.5398C5.62386 14.7172 5.7645 14.9279 5.86031 15.1598C5.95612 15.3916 6.00522 15.6401 6.00479 15.891C6.00437 16.1419 5.95442 16.3902 5.85782 16.6218C5.76122 16.8533 5.61987 17.0635 5.44186 17.2403C4.69719 17.985 2 18.0004 2 18.0004C2 18.0004 2 15.2884 2.74602 14.5444Z\"/><path d=\"M8.9416 3.48269C7.99688 3.31826 7.02645 3.38371 6.11237 3.67352C5.19828 3.96332 4.36741 4.46894 3.68999 5.14765C3.33153 5.50944 3.01988 5.91477 2.76233 6.35415C2.68692 6.4822 2.6562 6.63169 2.67501 6.77911C2.69381 6.92652 2.76108 7.06351 2.86623 7.16853L4.1994 8.50238C5.43822 6.53634 7.04911 4.83119 8.9416 3.48269Z\"/><path d=\"M16.5181 11.0585C16.6825 12.0033 16.6171 12.9737 16.3273 13.8878C16.0375 14.8019 15.5318 15.6327 14.8531 16.3101C14.4914 16.6686 14.086 16.9803 13.6466 17.2378C13.5186 17.3132 13.3691 17.3439 13.2217 17.3251C13.0743 17.3063 12.9373 17.2391 12.8323 17.1339L11.4984 15.8007C13.4645 14.5619 15.1696 12.951 16.5181 11.0585Z\"/></svg>", "icon": "<svg width=\"20\" height=\"20\" viewBox=\"0 0 20 20\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M17.8049 2.19795C17.7385 2.1311 17.6587 2.07899 17.5708 2.04504C17.4829 2.01108 17.3889 1.99604 17.2948 2.00089C7.89216 2.49153 4.4188 10.8673 4.38528 10.9517C4.33624 11.0736 4.32406 11.2071 4.35028 11.3358C4.3765 11.4645 4.43995 11.5827 4.53274 11.6756L8.32449 15.4674C8.41787 15.5606 8.53669 15.6242 8.66606 15.6502C8.79543 15.6762 8.92959 15.6634 9.05174 15.6135C9.13552 15.5793 17.4664 12.0671 17.9986 2.7087C18.0039 2.61474 17.9895 2.5207 17.9561 2.4327C17.9227 2.3447 17.8712 2.26471 17.8049 2.19795ZM12.3314 9.56427C12.1439 9.75179 11.9051 9.87951 11.645 9.93126C11.385 9.98302 11.1154 9.9565 10.8704 9.85505C10.6254 9.7536 10.4161 9.58178 10.2687 9.36131C10.1214 9.14085 10.0428 8.88166 10.0428 8.6165C10.0428 8.35135 10.1214 8.09215 10.2687 7.87169C10.4161 7.65123 10.6254 7.47941 10.8704 7.37796C11.1154 7.27651 11.385 7.24998 11.645 7.30174C11.9051 7.3535 12.1439 7.48121 12.3314 7.66873C12.5827 7.92012 12.7239 8.26104 12.7239 8.6165C12.7239 8.97197 12.5827 9.31288 12.3314 9.56427Z\"/><path d=\"M2.74602 14.5444C2.92281 14.3664 3.133 14.2251 3.36454 14.1285C3.59608 14.0319 3.8444 13.9819 4.09529 13.9815C4.34617 13.9811 4.59466 14.0.12 4.82653 14.126C5.05839 14.2218 5.26907 14.3624 5.44647 14.5398C5.62386 14.7172 5.7645 14.9279 5.86031 15.1598C5.95612 15.3916 6.00522 15.6401 6.00479 15.891C6.00437 16.1419 5.95442 16.3902 5.85782 16.6218C5.76122 16.8533 5.61987 17.0635 5.44186 17.2403C4.69719 17.985 2 18.0004 2 18.0004C2 18.0004 2 15.2884 2.74602 14.5444Z\"/><path d=\"M8.9416 3.48269C7.99688 3.31826 7.02645 3.38371 6.11237 3.67352C5.19828 3.96332 4.36741 4.46894 3.68999 5.14765C3.33153 5.50944 3.01988 5.91477 2.76233 6.35415C2.68692 6.4822 2.6562 6.63169 2.67501 6.77911C2.69381 6.92652 2.76108 7.06351 2.86623 7.16853L4.1994 8.50238C5.43822 6.53634 7.04911 4.83119 8.9416 3.48269Z\"/><path d=\"M16.5181 11.0585C16.6825 12.0033 16.6171 12.9737 16.3273 13.8878C16.0375 14.8019 15.5318 15.6327 14.8531 16.3101C14.4914 16.6686 14.086 16.9803 13.6466 17.2378C13.5186 17.3132 13.3691 17.3439 13.2217 17.3251C13.0743 17.3063 12.9373 17.2391 12.8323 17.1339L11.4984 15.8007C13.4645 14.5619 15.1696 12.951 16.5181 11.0585Z\"/></svg>",
"path": "./upgrade.md" "path": "./upgrade.md"
}, },
{ {

View File

@ -27,7 +27,7 @@ which is currently `14.x`. VS Code also [lists Node.js
requirements](https://github.com/microsoft/vscode/wiki/How-to-Contribute#prerequisites). requirements](https://github.com/microsoft/vscode/wiki/How-to-Contribute#prerequisites).
Using other versions of Node.js [may lead to unexpected Using other versions of Node.js [may lead to unexpected
behavior](https://github.com/cdr/code-server/issues/1633). behavior](https://github.com/coder/code-server/issues/1633).
## Ubuntu, Debian ## Ubuntu, Debian
@ -100,7 +100,7 @@ yarn global bin
# Or: npm config get prefix # Or: npm config get prefix
``` ```
For help and additional troubleshooting, see [#1397](https://github.com/cdr/code-server/issues/1397). For help and additional troubleshooting, see [#1397](https://github.com/coder/code-server/issues/1397).
## Installing ## Installing
@ -116,7 +116,7 @@ code-server
## Troubleshooting ## Troubleshooting
If you need further assistance, post on our [GitHub Discussions If you need further assistance, post on our [GitHub Discussions
page](https://github.com/cdr/code-server/discussions). page](https://github.com/coder/code-server/discussions).
### Issues with Node.js after version upgrades ### Issues with Node.js after version upgrades

View File

@ -33,7 +33,7 @@ new Compute Engine VM instance:
4. Choose the **region** that's closest to you based on [GCP 4. Choose the **region** that's closest to you based on [GCP
ping](https://gcping.com/). ping](https://gcping.com/).
5. Choose a **zone** (any option is fine). 5. Choose a **zone** (any option is fine).
6. We recommend choose an **E2 series instance** from the [general-purpose 6. We recommend choosing an **E2 series instance** from the [general-purpose
family](https://cloud.google.com/compute/docs/machine-types#general_purpose). family](https://cloud.google.com/compute/docs/machine-types#general_purpose).
7. Change the instance type to **custom** and set at least **2 cores** and **2 7. Change the instance type to **custom** and set at least **2 cores** and **2
GB of RAM**. You can add more resources if desired, though you can also edit GB of RAM**. You can add more resources if desired, though you can also edit

View File

@ -5,67 +5,144 @@
- [Install](#install) - [Install](#install)
- [Upgrade](#upgrade) - [Upgrade](#upgrade)
- [Known Issues](#known-issues) - [Known Issues](#known-issues)
- [Search doesn't work](#search-doesnt-work) - [Git won't work in `/sdcard`](#git-wont-work-in-sdcard)
- [Backspace doesn't work](#backspace-doesnt-work) - [Extra](#extra)
- [Create a new user](#create-a-new-user)
- [Install Go](#install-go)
- [Install Python](#install-python)
<!-- END doctoc generated TOC please keep comment here to allow auto update --> <!-- END doctoc generated TOC please keep comment here to allow auto update -->
Termux is a terminal application and Linux environment that you can also use to
run code-server from your Android phone.
## Install ## Install
1. Install Termux from [F-Droid](https://f-droid.org/en/packages/com.termux/). 1. Get [Termux](https://f-droid.org/en/packages/com.termux/) from **F-Droid**.
1. Make sure it's up-to-date: `apt update && apt upgrade` 2. Install Debian by running the following.
1. Install required packages: `apt install build-essential python git nodejs yarn` - Run `termux-setup-storage` to allow storage access, or else code-server won't be able to read from `/sdcard`.\
1. Install code-server: `yarn global add code-server` If you used the Andronix command then you may have to edit the `start-debian.sh` script to mount `/sdcard` just as simple as uncommenting the `command+=" -b /sdcard"` line.
1. Run code-server: `code-server` and navigate to localhost:8080 in your browser > The following command was extracted from [Andronix](https://andronix.app/) you can also use [proot-distro](https://github.com/termux/proot-distro).
> After Debian is installed the `~ $` will change to `root@localhost`.
```bash
pkg update -y && pkg install wget curl proot tar -y && wget https://raw.githubusercontent.com/AndronixApp/AndronixOrigin/master/Installer/Debian/debian.sh -O debian.sh && chmod +x debian.sh && bash debian.sh
```
3. Run the following commands to setup Debian.
```bash
apt update
apt upgrade -y
apt-get install nano vim sudo curl wget git -y
```
4. Install [NVM](https://github.com/nvm-sh/nvm) by following the install guide in the README, just a curl/wget command.
5. Set up NVM for multi-user. After installing NVM it automatically adds the necessary commands for it to work, but it will only work if you are logged in as root;
- Copy the lines NVM asks you to run after running the install script.
- Run `nano /root/.bashrc` and comment out those lines by adding a `#` at the start.
- Run `nano /etc/profile` and paste those lines at the end and make sure to replace `$HOME` with `/root`
- Now run `exit` and start Debain again.
6. After following the instructions and setting up NVM you can now install the [required node version](https://coder.com/docs/code-server/latest/npm#nodejs-version) using `nvm install version_here`.
7. To install `code-server` run the following.
> To check the install process (Will not actually install code-server)
> If it all looks good, you can install code-server by running the second command
```bash
curl -fsSL https://code-server.dev/install.sh | sh -s -- --dry-run
```
```bash
curl -fsSL https://code-server.dev/install.sh | sh
```
8. You can now start code server by simply running `code-server`.
> Consider using a new user instead of root, read [here](https://www.howtogeek.com/124950/htg-explains-why-you-shouldnt-log-into-your-linux-system-as-root/) why using root is not recommended.\
> Learn how to add a user [here](#create-a-new-user).
## Upgrade ## Upgrade
To upgrade run: `yarn global upgrade code-server --latest` 1. Remove all previous installs `rm -rf ~/.local/lib/code-server-*`
2. Run the install script again `curl -fsSL https://code-server.dev/install.sh | sh`
## Known Issues ## Known Issues
The following details known issues and suggested workarounds for using ### Git won't work in `/sdcard`
code-server with Termux.
### Search doesn't work Issue : Using git in the `/sdcard` directory will fail during cloning/commit/staging/etc...\
Fix : None\
Potential Workaround :
There is a known issue with search not working on Android because it's missing 1. Create a soft-link from the debian-fs to your folder in `/sdcard`
`bin/rg` ([context](https://github.com/cdr/code-server/issues/1730#issuecomment-721515979)). To fix this: 2. Use git from termux (preferred)
1. Install `ripgrep` with `pkg` ## Extra
```sh ### Create a new user
pkg install ripgrep
```
1. Make a soft link using `ln -s` To create a new user follow these simple steps -
```sh 1. Create a new user by running `useradd username -m`.
# run this command inside the code-server directory 2. Change the password by running `passwd username`.
ln -s $PREFIX/bin/rg ./vendor/modules/code-oss-dev/vscode-ripgrep/bin/rg 3. Give your new user sudo access by runnning `visudo`, scroll down to `User privilege specification` and add the following line after root `username ALL=(ALL:ALL) ALL`.
``` 4. Now edit the `/etc/passwd` file with your commadline editor of choice and at the end of the line that specifies your user change `/bin/sh` to `/bin/bash`.
5. Now switch users, by running `su - username`
### Backspace doesn't work - Remember the `-` betweeen `su` and username is required to execute `/etc/profile`,\
since `/etc/profile` may have some necessary things to be executed you should always add a `-`.
When using Android's on-screen keyboard, the backspace key doesn't work ### Install Go
properly. This is a known upstream issue:
- [Issues with backspace in Codespaces on Android (Surface Duo)](https://github.com/microsoft/vscode/issues/107602) > From https://golang.org/doc/install
- [Support mobile platforms](https://github.com/xtermjs/xterm.js/issues/1101)
There are two workarounds. 1. Go to https://golang.org/dl/ and copy the download link for `linux arm` and run the following.
**Option 1:** Modify keyboard dispatch settings ```bash
wget download_link
```
1. Open the Command Palette 2. Extract the downloaded archive. (This step will erase all previous GO installs, make sure to create a backup if you have previously installed GO)
2. Search for **Preferences: Open Settings (JSON)**
3. Add `"keyboard.dispatch": "keyCode"`
The backspace button should work at this point. ```bash
rm -rf /usr/local/go && tar -C /usr/local -xzf archive_name
```
_Thanks to @Nefomemes for the [suggestion](https://github.com/cdr/code-server/issues/1141#issuecomment-789463707)!_ 3. Run `nano /etc/profile` and add the following line `export PATH=$PATH:/usr/local/go/bin`.
4. Now run `exit` (depending on if you have switched users or not, you may have to run `exit` multiple times to get to normal termux shell) and start Debian again.
5. Check if your install was successful by running `go version`
**Option 2:** Use a Bluetooth keyboard. ### Install Python
> Run these commands as root
1. Run the following command to install required packages to build python.
```bash
sudo apt-get update
sudo apt-get install make build-essential libssl-dev zlib1g-dev \
libbz2-dev libreadline-dev libsqlite3-dev wget curl llvm \
libncursesw5-dev xz-utils tk-dev libxml2-dev libxmlsec1-dev libffi-dev liblzma-dev
```
2. Install [pyenv](https://github.com/pyenv/pyenv/) from [pyenv-installer](https://github.com/pyenv/pyenv-installer) by running.
```bash
curl -L https://github.com/pyenv/pyenv-installer/raw/master/bin/pyenv-installer | bash
```
3. Run `nano /etc/profile` and add the following
```bash
export PYENV_ROOT="/root/.pyenv"
export PATH="/root/.pyenv/bin:$PATH"
eval "$(pyenv init --path)"
eval "$(pyenv virtualenv-init -)"
```
4. Exit start Debian again.
5. Run `pyenv versions` to list all installable versions.
6. Run `pyenv install version` to install the desired python version.
> The build process may take some time (an hour or 2 depending on your device).
7. Run `touch /root/.pyenv/version && echo "your_version_here" > /root/.pyenv/version`
8. (You may have to start Debian again) Run `python3 -V` to verify if PATH works or not.
> If `python3` doesn't work but pyenv says that the install was successful in step 6 then try running `$PYENV_ROOT/versions/your_version/bin/python3`.

View File

@ -28,8 +28,8 @@ This will show issues that:
1. If more information is required, please ask the submitter and tag as 1. If more information is required, please ask the submitter and tag as
`waiting-for-info` and wait. `waiting-for-info` and wait.
1. Finally, the issue should be moved into the 1. Finally, the issue should be moved into the
[code-server](https://github.com/cdr/code-server/projects/1) project where we [code-server](https://github.com/coder/code-server/projects/1) project where we
pick out issues to fix and track their progress. pick out issues to fix and track their progress.
We also use [milestones](https://github.com/cdr/code-server/milestones) to track We also use [milestones](https://github.com/coder/code-server/milestones) to track
what issues are planned/or were closed for what release. what issues are planned/or were closed for what release.

View File

@ -23,7 +23,7 @@ The remote host must have internet access.
${not_curl_usage-} ${not_curl_usage-}
Usage: Usage:
$arg0 [--dry-run] [--version X.X.X] [--method detect] \ $arg0 [--dry-run] [--version X.X.X] [--edge] [--method detect] \
[--prefix ~/.local] [--rsh ssh] [user@host] [--prefix ~/.local] [--rsh ssh] [user@host]
--dry-run --dry-run
@ -32,6 +32,9 @@ Usage:
--version X.X.X --version X.X.X
Install a specific version instead of the latest. Install a specific version instead of the latest.
--edge
Install the latest edge version instead of the latest stable version.
--method [detect | standalone] --method [detect | standalone]
Choose the installation method. Defaults to detect. Choose the installation method. Defaults to detect.
- detect detects the system package manager and tries to use it. - detect detects the system package manager and tries to use it.
@ -71,9 +74,13 @@ EOF
} }
echo_latest_version() { echo_latest_version() {
# https://gist.github.com/lukechilds/a83e1d7127b78fef38c2914c4ececc3c#gistcomment-2758860 if [ "${EDGE-}" ]; then
version="$(curl -fsSLI -o /dev/null -w "%{url_effective}" https://github.com/cdr/code-server/releases/latest)" version="$(curl -fsSL https://api.github.com/repos/coder/code-server/releases | awk 'match($0,/.*"html_url": "(.*\/releases\/tag\/.*)".*/)' | head -n 1 | awk -F '"' '{print $4}')"
version="${version#https://github.com/cdr/code-server/releases/tag/}" else
# https://gist.github.com/lukechilds/a83e1d7127b78fef38c2914c4ececc3c#gistcomment-2758860
version="$(curl -fsSLI -o /dev/null -w "%{url_effective}" https://github.com/coder/code-server/releases/latest)"
fi
version="${version#https://github.com/coder/code-server/releases/tag/}"
version="${version#v}" version="${version#v}"
echo "$version" echo "$version"
} }
@ -135,6 +142,7 @@ main() {
OPTIONAL \ OPTIONAL \
ALL_FLAGS \ ALL_FLAGS \
RSH_ARGS \ RSH_ARGS \
EDGE \
RSH RSH
ALL_FLAGS="" ALL_FLAGS=""
@ -170,6 +178,9 @@ main() {
--version=*) --version=*)
VERSION="$(parse_arg "$@")" VERSION="$(parse_arg "$@")"
;; ;;
--edge)
EDGE=1
;;
--rsh) --rsh)
RSH="$(parse_arg "$@")" RSH="$(parse_arg "$@")"
shift shift
@ -340,7 +351,7 @@ install_deb() {
echoh "Installing v$VERSION of the $ARCH deb package from GitHub." echoh "Installing v$VERSION of the $ARCH deb package from GitHub."
echoh echoh
fetch "https://github.com/cdr/code-server/releases/download/v$VERSION/code-server_${VERSION}_$ARCH.deb" \ fetch "https://github.com/coder/code-server/releases/download/v$VERSION/code-server_${VERSION}_$ARCH.deb" \
"$CACHE_DIR/code-server_${VERSION}_$ARCH.deb" "$CACHE_DIR/code-server_${VERSION}_$ARCH.deb"
sudo_sh_c dpkg -i "$CACHE_DIR/code-server_${VERSION}_$ARCH.deb" sudo_sh_c dpkg -i "$CACHE_DIR/code-server_${VERSION}_$ARCH.deb"
@ -351,7 +362,7 @@ install_rpm() {
echoh "Installing v$VERSION of the $ARCH rpm package from GitHub." echoh "Installing v$VERSION of the $ARCH rpm package from GitHub."
echoh echoh
fetch "https://github.com/cdr/code-server/releases/download/v$VERSION/code-server-$VERSION-$ARCH.rpm" \ fetch "https://github.com/coder/code-server/releases/download/v$VERSION/code-server-$VERSION-$ARCH.rpm" \
"$CACHE_DIR/code-server-$VERSION-$ARCH.rpm" "$CACHE_DIR/code-server-$VERSION-$ARCH.rpm"
sudo_sh_c rpm -i "$CACHE_DIR/code-server-$VERSION-$ARCH.rpm" sudo_sh_c rpm -i "$CACHE_DIR/code-server-$VERSION-$ARCH.rpm"
@ -377,7 +388,7 @@ install_standalone() {
echoh "Installing v$VERSION of the $ARCH release from GitHub." echoh "Installing v$VERSION of the $ARCH release from GitHub."
echoh echoh
fetch "https://github.com/cdr/code-server/releases/download/v$VERSION/code-server-$VERSION-$OS-$ARCH.tar.gz" \ fetch "https://github.com/coder/code-server/releases/download/v$VERSION/code-server-$VERSION-$OS-$ARCH.tar.gz" \
"$CACHE_DIR/code-server-$VERSION-$OS-$ARCH.tar.gz" "$CACHE_DIR/code-server-$VERSION-$OS-$ARCH.tar.gz"
# -w only works if the directory exists so try creating it first. If this # -w only works if the directory exists so try creating it first. If this

View File

@ -1,13 +1,13 @@
{ {
"name": "code-server", "name": "code-server",
"license": "MIT", "license": "MIT",
"version": "3.12.0", "version": "4.0.2",
"description": "Run VS Code on a remote server.", "description": "Run VS Code on a remote server.",
"homepage": "https://github.com/cdr/code-server", "homepage": "https://github.com/coder/code-server",
"bugs": { "bugs": {
"url": "https://github.com/cdr/code-server/issues" "url": "https://github.com/coder/code-server/issues"
}, },
"repository": "https://github.com/cdr/code-server", "repository": "https://github.com/coder/code-server",
"scripts": { "scripts": {
"clean": "./ci/build/clean.sh", "clean": "./ci/build/clean.sh",
"build": "./ci/build/build-code-server.sh", "build": "./ci/build/build-code-server.sh",
@ -17,26 +17,25 @@
"release:github-draft": "./ci/build/release-github-draft.sh", "release:github-draft": "./ci/build/release-github-draft.sh",
"release:github-assets": "./ci/build/release-github-assets.sh", "release:github-assets": "./ci/build/release-github-assets.sh",
"release:prep": "./ci/build/release-prep.sh", "release:prep": "./ci/build/release-prep.sh",
"test:e2e": "./ci/dev/test-e2e.sh", "test:e2e": "VSCODE_IPC_HOOK_CLI= ./ci/dev/test-e2e.sh",
"test:standalone-release": "./ci/build/test-standalone-release.sh", "test:standalone-release": "./ci/build/test-standalone-release.sh",
"test:unit": "./ci/dev/test-unit.sh", "test:unit": "./ci/dev/test-unit.sh --forceExit --detectOpenHandles",
"test:scripts": "./ci/dev/test-scripts.sh", "test:scripts": "./ci/dev/test-scripts.sh",
"package": "./ci/build/build-packages.sh", "package": "./ci/build/build-packages.sh",
"postinstall": "./ci/dev/postinstall.sh", "postinstall": "./ci/dev/postinstall.sh",
"publish:npm": "./ci/steps/publish-npm.sh",
"_audit": "./ci/dev/audit.sh", "_audit": "./ci/dev/audit.sh",
"fmt": "./ci/dev/fmt.sh", "fmt": "./ci/dev/fmt.sh",
"lint": "./ci/dev/lint.sh", "lint": "./ci/dev/lint.sh",
"test": "echo 'Run yarn test:unit or yarn test:e2e' && exit 1", "test": "echo 'Run yarn test:unit or yarn test:e2e' && exit 1",
"ci": "./ci/dev/ci.sh", "ci": "./ci/dev/ci.sh",
"watch": "VSCODE_IPC_HOOK_CLI= NODE_OPTIONS='--max_old_space_size=32384 --trace-warnings' ts-node ./ci/dev/watch.ts", "watch": "VSCODE_DEV=1 VSCODE_IPC_HOOK_CLI= NODE_OPTIONS='--max_old_space_size=32384 --trace-warnings' ts-node ./ci/dev/watch.ts",
"icons": "./ci/dev/gen_icons.sh", "icons": "./ci/dev/gen_icons.sh",
"coverage": "codecov" "coverage": "codecov"
}, },
"main": "out/node/entry.js", "main": "out/node/entry.js",
"devDependencies": { "devDependencies": {
"@schemastore/package": "^0.0.6", "@schemastore/package": "^0.0.6",
"@types/body-parser": "^1.19.0",
"@types/browserify": "^12.0.36",
"@types/compression": "^1.7.0", "@types/compression": "^1.7.0",
"@types/cookie-parser": "^1.4.2", "@types/cookie-parser": "^1.4.2",
"@types/express": "^4.17.8", "@types/express": "^4.17.8",
@ -48,27 +47,25 @@
"@types/safe-compare": "^1.1.0", "@types/safe-compare": "^1.1.0",
"@types/semver": "^7.1.0", "@types/semver": "^7.1.0",
"@types/split2": "^3.2.0", "@types/split2": "^3.2.0",
"@types/tar-fs": "^2.0.0", "@types/trusted-types": "^2.0.2",
"@types/tar-stream": "^2.1.0",
"@types/ws": "^8.0.0", "@types/ws": "^8.0.0",
"@typescript-eslint/eslint-plugin": "^4.7.0", "@typescript-eslint/eslint-plugin": "^5.0.0",
"@typescript-eslint/parser": "^4.7.0", "@typescript-eslint/parser": "^5.0.0",
"audit-ci": "^4.0.0", "audit-ci": "^5.0.0",
"browserify": "^17.0.0",
"codecov": "^3.8.3", "codecov": "^3.8.3",
"doctoc": "^2.0.0", "doctoc": "^2.0.0",
"eslint": "^7.7.0", "eslint": "^7.7.0",
"eslint-config-prettier": "^8.1.0", "eslint-config-prettier": "^8.1.0",
"eslint-import-resolver-alias": "^1.1.2", "eslint-import-resolver-typescript": "^2.5.0",
"eslint-plugin-import": "^2.18.2", "eslint-plugin-import": "^2.18.2",
"eslint-plugin-prettier": "^4.0.0", "eslint-plugin-prettier": "^4.0.0",
"prettier": "^2.2.1", "prettier": "^2.2.1",
"prettier-plugin-sh": "^0.7.1", "prettier-plugin-sh": "^0.8.0",
"shellcheck": "^1.0.0", "shellcheck": "^1.0.0",
"stylelint": "^13.0.0", "stylelint": "^13.0.0",
"stylelint-config-recommended": "^5.0.0", "stylelint-config-recommended": "^5.0.0",
"ts-node": "^10.0.0", "ts-node": "^10.0.0",
"typescript": "^4.1.3" "typescript": "^4.4.0-dev.20210528"
}, },
"resolutions": { "resolutions": {
"ansi-regex": "^5.0.1", "ansi-regex": "^5.0.1",
@ -79,13 +76,16 @@
"browserslist": "^4.16.5", "browserslist": "^4.16.5",
"safe-buffer": "^5.1.1", "safe-buffer": "^5.1.1",
"vfile-message": "^2.0.2", "vfile-message": "^2.0.2",
"argon2/@mapbox/node-pre-gyp/tar": "^6.1.9", "tar": "^6.1.9",
"path-parse": "^1.0.7" "path-parse": "^1.0.7",
"vm2": "^3.9.6",
"follow-redirects": "^1.14.8",
"node-fetch": "^2.6.7",
"nanoid": "^3.1.31"
}, },
"dependencies": { "dependencies": {
"@coder/logger": "1.1.16", "@coder/logger": "1.1.16",
"argon2": "^0.28.0", "argon2": "^0.28.0",
"body-parser": "^1.19.0",
"compression": "^1.7.4", "compression": "^1.7.4",
"cookie-parser": "^1.4.5", "cookie-parser": "^1.4.5",
"env-paths": "^2.2.0", "env-paths": "^2.2.0",
@ -96,14 +96,12 @@
"limiter": "^1.1.5", "limiter": "^1.1.5",
"pem": "^1.14.2", "pem": "^1.14.2",
"proxy-agent": "^5.0.0", "proxy-agent": "^5.0.0",
"proxy-from-env": "^1.1.0", "qs": "6.10.3",
"qs": "6.10.1", "rotating-file-stream": "^3.0.0",
"rotating-file-stream": "^2.1.1",
"safe-buffer": "^5.1.1", "safe-buffer": "^5.1.1",
"safe-compare": "^1.1.4", "safe-compare": "^1.1.4",
"semver": "^7.1.3", "semver": "^7.1.3",
"split2": "^3.2.2", "split2": "^4.0.0",
"tar-fs": "^2.0.0",
"ws": "^8.0.0", "ws": "^8.0.0",
"xdg-basedir": "^4.0.0", "xdg-basedir": "^4.0.0",
"yarn": "^1.22.4" "yarn": "^1.22.4"
@ -159,10 +157,12 @@
"<rootDir>/release-standalone", "<rootDir>/release-standalone",
"<rootDir>/release-npm-package", "<rootDir>/release-npm-package",
"<rootDir>/release-gcp", "<rootDir>/release-gcp",
"<rootDir>/release-images" "<rootDir>/release-images",
"<rootDir>/vendor"
], ],
"moduleNameMapper": { "moduleNameMapper": {
"^.+\\.(css|less)$": "<rootDir>/test/utils/cssStub.ts" "^.+\\.(css|less)$": "<rootDir>/test/utils/cssStub.ts"
} },
"globalSetup": "<rootDir>/test/utils/globalUnitSetup.ts"
} }
} }

View File

@ -1,20 +0,0 @@
{
"name": "code-server",
"short_name": "code-server",
"start_url": "{{BASE}}",
"display": "fullscreen",
"background-color": "#fff",
"description": "Run editors on a remote server.",
"icons": [
{
"src": "{{CS_STATIC_BASE}}/src/browser/media/pwa-icon-192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "{{CS_STATIC_BASE}}/src/browser/media/pwa-icon-512.png",
"type": "image/png",
"sizes": "512x512"
}
]
}

View File

@ -10,10 +10,11 @@
http-equiv="Content-Security-Policy" http-equiv="Content-Security-Policy"
content="style-src 'self'; manifest-src 'self'; img-src 'self' data:; font-src 'self' data:;" content="style-src 'self'; manifest-src 'self'; img-src 'self' data:; font-src 'self' data:;"
/> />
<title>{{ERROR_TITLE}} - code-server</title> <title>{{ERROR_TITLE}} - code-server</title>
<link rel="icon" href="{{CS_STATIC_BASE}}/src/browser/media/favicon-dark-support.svg" /> <link rel="icon" href="{{CS_STATIC_BASE}}/src/browser/media/favicon-dark-support.svg" />
<link rel="alternate icon" href="{{CS_STATIC_BASE}}/src/browser/media/favicon.ico" /> <link rel="alternate icon" href="{{CS_STATIC_BASE}}/src/browser/media/favicon.ico" />
<link rel="manifest" href="{{CS_STATIC_BASE}}/src/browser/media/manifest.json" crossorigin="use-credentials" /> <link rel="manifest" href="/manifest.json" crossorigin="use-credentials" />
<link rel="apple-touch-icon" sizes="192x192" href="{{CS_STATIC_BASE}}/src/browser/media/pwa-icon-192.png" /> <link rel="apple-touch-icon" sizes="192x192" href="{{CS_STATIC_BASE}}/src/browser/media/pwa-icon-192.png" />
<link rel="apple-touch-icon" sizes="512x512" href="{{CS_STATIC_BASE}}/src/browser/media/pwa-icon-512.png" /> <link rel="apple-touch-icon" sizes="512x512" href="{{CS_STATIC_BASE}}/src/browser/media/pwa-icon-512.png" />
<link href="{{CS_STATIC_BASE}}/src/browser/pages/global.css" rel="stylesheet" /> <link href="{{CS_STATIC_BASE}}/src/browser/pages/global.css" rel="stylesheet" />
@ -30,6 +31,5 @@
</div> </div>
</div> </div>
</div> </div>
<script data-cfasync="false" src="{{CS_STATIC_BASE}}/out/browser/register.browserified.js"></script>
</body> </body>
</html> </html>

View File

@ -13,7 +13,7 @@
<title>code-server login</title> <title>code-server login</title>
<link rel="icon" href="{{CS_STATIC_BASE}}/src/browser/media/favicon-dark-support.svg" /> <link rel="icon" href="{{CS_STATIC_BASE}}/src/browser/media/favicon-dark-support.svg" />
<link rel="alternate icon" href="{{CS_STATIC_BASE}}/src/browser/media/favicon.ico" /> <link rel="alternate icon" href="{{CS_STATIC_BASE}}/src/browser/media/favicon.ico" />
<link rel="manifest" href="{{CS_STATIC_BASE}}/src/browser/media/manifest.json" crossorigin="use-credentials" /> <link rel="manifest" href="{{BASE}}/manifest.json" crossorigin="use-credentials" />
<link rel="apple-touch-icon" sizes="192x192" href="{{CS_STATIC_BASE}}/src/browser/media/pwa-icon-192.png" /> <link rel="apple-touch-icon" sizes="192x192" href="{{CS_STATIC_BASE}}/src/browser/media/pwa-icon-192.png" />
<link rel="apple-touch-icon" sizes="512x512" href="{{CS_STATIC_BASE}}/src/browser/media/pwa-icon-512.png" /> <link rel="apple-touch-icon" sizes="512x512" href="{{CS_STATIC_BASE}}/src/browser/media/pwa-icon-512.png" />
<link href="{{CS_STATIC_BASE}}/src/browser/pages/global.css" rel="stylesheet" /> <link href="{{CS_STATIC_BASE}}/src/browser/pages/global.css" rel="stylesheet" />
@ -30,7 +30,8 @@
<div class="content"> <div class="content">
<form class="login-form" method="post"> <form class="login-form" method="post">
<input class="user" type="text" autocomplete="username" /> <input class="user" type="text" autocomplete="username" />
<input id="base" type="hidden" name="base" value="/" /> <input id="base" type="hidden" name="base" value="{{BASE}}" />
<input id="href" type="hidden" name="href" value="" />
<div class="field"> <div class="field">
<input <input
required required
@ -48,6 +49,13 @@
</div> </div>
</div> </div>
</div> </div>
<script>
// Inform the backend about the path since the proxy might have rewritten
// it out of the headers and cookies must be set with absolute paths.
const el = document.getElementById("href")
if (el) {
el.value = location.href
}
</script>
</body> </body>
<script data-cfasync="false" src="{{CS_STATIC_BASE}}/out/browser/pages/login.browserified.js"></script>
</html> </html>

View File

@ -1,8 +0,0 @@
import { getOptions } from "../../common/util"
import "../register"
const options = getOptions()
const el = document.getElementById("base") as HTMLInputElement
if (el) {
el.value = options.base
}

View File

@ -1,54 +0,0 @@
<!-- Copyright (C) Microsoft Corporation. All rights reserved. -->
<!DOCTYPE html>
<html>
<head>
<script>
performance.mark("code/didStartRenderer")
</script>
<meta charset="utf-8" />
<!-- Disable pinch zooming -->
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no"
/>
<!-- Workbench Configuration -->
<meta id="vscode-workbench-web-configuration" data-settings="{{WORKBENCH_WEB_CONFIGURATION}}" />
<!-- Workarounds/Hacks (remote user data uri) -->
<meta id="vscode-remote-user-data-uri" data-settings="{{REMOTE_USER_DATA_URI}}" />
<meta id="vscode-remote-product-configuration" data-settings="{{PRODUCT_CONFIGURATION}}" />
<meta id="vscode-remote-nls-configuration" data-settings="{{NLS_CONFIGURATION}}" />
<!-- Workbench Icon/Manifest/CSS -->
<link rel="icon" href="{{CS_STATIC_BASE}}/src/browser/media/favicon-dark-support.svg" />
<link rel="alternate icon" href="{{CS_STATIC_BASE}}/src/browser/media/favicon.ico" />
<link rel="manifest" href="{{CS_STATIC_BASE}}/src/browser/media/manifest.json" crossorigin="use-credentials" />
<!-- PROD_ONLY
<link data-name="vs/workbench/workbench.web.api" rel="stylesheet" href="{{CS_STATIC_BASE}}/vendor/modules/code-oss-dev/out/vs/workbench/workbench.web.api.css">
END_PROD_ONLY -->
<link rel="apple-touch-icon" sizes="192x192" href="{{CS_STATIC_BASE}}/src/browser/media/pwa-icon-192.png" />
<link rel="apple-touch-icon" sizes="512x512" href="{{CS_STATIC_BASE}}/src/browser/media/pwa-icon-512.png" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta id="coder-options" data-settings="{{OPTIONS}}" />
</head>
<body aria-label=""></body>
<!-- Startup (do not modify order of script tags!) -->
<script data-cfasync="false" src="{{CS_STATIC_BASE}}/out/browser/pages/vscode.browserified.js"></script>
<script data-cfasync="false" src="{{CS_STATIC_BASE}}/vendor/modules/code-oss-dev/out/vs/loader.js"></script>
<script>
performance.mark("code/willLoadWorkbenchMain")
</script>
<!-- PROD_ONLY
<script data-cfasync="false" src="{{CS_STATIC_BASE}}/vendor/modules/code-oss-dev/out/vs/workbench/workbench.web.api.nls.js"></script>
<script data-cfasync="false" src="{{CS_STATIC_BASE}}/vendor/modules/code-oss-dev/out/vs/workbench/workbench.web.api.js"></script>
END_PROD_ONLY -->
<script>
require(["vs/code/browser/workbench/workbench"], function () {})
</script>
</html>

View File

@ -1,253 +0,0 @@
import { getOptions, Options } from "../../common/util"
import "../register"
// TODO@jsjoeio: Add proper types.
type FixMeLater = any
// NOTE@jsjoeio
// This lives here ../../../lib/vscode/src/vs/base/common/platform.ts#L106
export const nlsConfigElementId = "vscode-remote-nls-configuration"
type NlsConfiguration = {
locale: string
availableLanguages: { [key: string]: string } | {}
_languagePackId?: string
_translationsConfigFile?: string
_cacheRoot?: string
_resolvedLanguagePackCoreLocation?: string
_corruptedFile?: string
_languagePackSupport?: boolean
loadBundle?: FixMeLater
}
/**
* Helper function to create the path to the bundle
* for getNlsConfiguration.
*/
export function createBundlePath(_resolvedLanguagePackCoreLocation: string | undefined, bundle: string) {
// NOTE@jsjoeio - this comment was here before me
// Refers to operating systems that use a different path separator.
// Probably just Windows but we're not sure if "/" breaks on Windows
// so we'll leave it alone for now.
// FIXME: Only works if path separators are /.
return (_resolvedLanguagePackCoreLocation || "") + "/" + bundle.replace(/\//g, "!") + ".nls.json"
}
/**
* A helper function to get the NLS Configuration settings.
*
* This is used by VSCode for localizations (i.e. changing
* the display language).
*
* Make sure to wrap this in a try/catch block when you call it.
**/
export function getNlsConfiguration(_document: Document, base: string) {
const errorMsgPrefix = "[vscode]"
const nlsConfigElement = _document?.getElementById(nlsConfigElementId)
const dataSettings = nlsConfigElement?.getAttribute("data-settings")
if (!nlsConfigElement) {
throw new Error(
`${errorMsgPrefix} Could not parse NLS configuration. Could not find nlsConfigElement with id: ${nlsConfigElementId}`,
)
}
if (!dataSettings) {
throw new Error(
`${errorMsgPrefix} Could not parse NLS configuration. Found nlsConfigElement but missing data-settings attribute.`,
)
}
const nlsConfig = JSON.parse(dataSettings) as NlsConfiguration
if (nlsConfig._resolvedLanguagePackCoreLocation) {
// NOTE@jsjoeio
// Not sure why we use Object.create(null) instead of {}
// They are not the same
// See: https://stackoverflow.com/a/15518712/3015595
// We copied this from ../../../lib/vscode/src/bootstrap.js#L143
const bundles: {
[key: string]: string
} = Object.create(null)
type LoadBundleCallback = (_: undefined, result?: string) => void
nlsConfig.loadBundle = async (bundle: string, _language: string, cb: LoadBundleCallback): Promise<void> => {
const result = bundles[bundle]
if (result) {
return cb(undefined, result)
}
try {
const path = createBundlePath(nlsConfig._resolvedLanguagePackCoreLocation, bundle)
const response = await fetch(`${base}/vscode/resource/?path=${encodeURIComponent(path)}`)
const json = await response.json()
bundles[bundle] = json
return cb(undefined, json)
} catch (error) {
return cb(error)
}
}
}
return nlsConfig
}
type GetLoaderParams = {
nlsConfig: NlsConfiguration
options: Options
_window: Window
}
/**
* Link to types in the loader source repo
* https://github.com/microsoft/vscode-loader/blob/main/src/loader.d.ts#L280
*/
type Loader = {
baseUrl: string
recordStats: boolean
// TODO@jsjoeio: There don't appear to be any types for trustedTypes yet.
trustedTypesPolicy: FixMeLater
paths: {
[key: string]: string
}
"vs/nls": NlsConfiguration
}
/**
* A helper function which creates a script url if the value
* is valid.
*
* Extracted into a function to make it easier to test
*/
export function _createScriptURL(value: string, origin: string): string {
if (value.startsWith(origin)) {
return value
}
throw new Error(`Invalid script url: ${value}`)
}
/**
* A helper function to get the require loader
*
* This used by VSCode/code-server
* to load files.
*
* We extracted the logic into a function so that
* it's easier to test.
**/
export function getConfigurationForLoader({ nlsConfig, options, _window }: GetLoaderParams) {
const loader: Loader = {
// Without the full URL VS Code will try to load file://.
baseUrl: `${window.location.origin}${options.csStaticBase}/vendor/modules/code-oss-dev/out`,
recordStats: true,
trustedTypesPolicy: (_window as FixMeLater).trustedTypes?.createPolicy("amdLoader", {
createScriptURL(value: string): string {
return _createScriptURL(value, window.location.origin)
},
}),
paths: {
"vscode-textmate": `../node_modules/vscode-textmate/release/main`,
"vscode-oniguruma": `../node_modules/vscode-oniguruma/release/main`,
xterm: `../node_modules/xterm/lib/xterm.js`,
"xterm-addon-search": `../node_modules/xterm-addon-search/lib/xterm-addon-search.js`,
"xterm-addon-unicode11": `../node_modules/xterm-addon-unicode11/lib/xterm-addon-unicode11.js`,
"xterm-addon-webgl": `../node_modules/xterm-addon-webgl/lib/xterm-addon-webgl.js`,
"tas-client-umd": `../node_modules/tas-client-umd/lib/tas-client-umd.js`,
"iconv-lite-umd": `../node_modules/iconv-lite-umd/lib/iconv-lite-umd.js`,
jschardet: `../node_modules/jschardet/dist/jschardet.min.js`,
},
"vs/nls": nlsConfig,
}
return loader
}
/**
* Sets the body background color to match the theme.
*/
export function setBodyBackgroundToThemeBackgroundColor(_document: Document, _localStorage: Storage) {
const errorMsgPrefix = "[vscode]"
const colorThemeData = _localStorage.getItem("colorThemeData")
if (!colorThemeData) {
throw new Error(
`${errorMsgPrefix} Could not set body background to theme background color. Could not find colorThemeData in localStorage.`,
)
}
let _colorThemeData
try {
// We wrap this JSON.parse logic in a try/catch
// because it can throw if the JSON is invalid.
// and instead of throwing a random error
// we can throw our own error, which will be more helpful
// to the end user.
_colorThemeData = JSON.parse(colorThemeData)
} catch {
throw new Error(
`${errorMsgPrefix} Could not set body background to theme background color. Could not parse colorThemeData from localStorage.`,
)
}
const hasColorMapProperty = Object.prototype.hasOwnProperty.call(_colorThemeData, "colorMap")
if (!hasColorMapProperty) {
throw new Error(
`${errorMsgPrefix} Could not set body background to theme background color. colorThemeData is missing colorMap.`,
)
}
const editorBgColor = _colorThemeData.colorMap["editor.background"]
if (!editorBgColor) {
throw new Error(
`${errorMsgPrefix} Could not set body background to theme background color. colorThemeData.colorMap["editor.background"] is undefined.`,
)
}
_document.body.style.background = editorBgColor
return null
}
/**
* A helper function to encapsulate all the
* logic used in this file.
*
* We purposely include all of this in a single function
* so that it's easier to test.
*/
export function main(_document: Document | undefined, _window: Window | undefined, _localStorage: Storage | undefined) {
if (!_document) {
throw new Error(`document is undefined.`)
}
if (!_window) {
throw new Error(`window is undefined.`)
}
if (!_localStorage) {
throw new Error(`localStorage is undefined.`)
}
const options = getOptions()
const nlsConfig = getNlsConfiguration(_document, options.base)
const loader = getConfigurationForLoader({
nlsConfig,
options,
_window,
})
;(self.require as unknown as Loader) = loader
setBodyBackgroundToThemeBackgroundColor(_document, _localStorage)
}
try {
main(document, window, localStorage)
} catch (error) {
console.error("[vscode] failed to initialize VS Code")
console.error(error)
}

View File

@ -1,23 +0,0 @@
import { logger } from "@coder/logger"
import { getOptions, normalize, logError } from "../common/util"
export async function registerServiceWorker(): Promise<void> {
const options = getOptions()
logger.level = options.logLevel
const path = normalize(`${options.csStaticBase}/out/browser/serviceWorker.js`)
try {
await navigator.serviceWorker.register(path, {
scope: options.base + "/",
})
logger.info(`[Service Worker] registered`)
} catch (error) {
logError(logger, `[Service Worker] registration`, error)
}
}
if (typeof navigator !== "undefined" && "serviceWorker" in navigator) {
registerServiceWorker()
} else {
logger.error(`[Service Worker] navigator is undefined`)
}

View File

@ -1,14 +0,0 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
self.addEventListener("install", () => {
console.log("[Service Worker] installed")
})
self.addEventListener("activate", (event: any) => {
event.waitUntil((self as any).clients.claim())
console.log("[Service Worker] activated")
})
self.addEventListener("fetch", () => {
// Without this event handler we won't be recognized as a PWA.
})

View File

@ -7,7 +7,7 @@ import { logger } from "@coder/logger"
export type Callback<T, R = void | Promise<void>> = (t: T, p: Promise<void>) => R export type Callback<T, R = void | Promise<void>> = (t: T, p: Promise<void>) => R
export interface Disposable { export interface Disposable {
dispose(): void dispose(): void | Promise<void>
} }
export interface Event<T> { export interface Event<T> {
@ -46,7 +46,7 @@ export class Emitter<T> {
this.listeners.map(async (cb) => { this.listeners.map(async (cb) => {
try { try {
await cb(value, promise) await cb(value, promise)
} catch (error) { } catch (error: any) {
logger.error(error.message) logger.error(error.message)
} }
}), }),

View File

@ -13,8 +13,12 @@ export enum HttpCode {
* used in the HTTP response. * used in the HTTP response.
*/ */
export class HttpError extends Error { export class HttpError extends Error {
public constructor(message: string, public readonly status: HttpCode, public readonly details?: object) { public constructor(message: string, public readonly statusCode: HttpCode, public readonly details?: object) {
super(message) super(message)
this.name = this.constructor.name this.name = this.constructor.name
} }
} }
export enum CookieKeys {
Session = "code-server-session",
}

View File

@ -1,19 +1,3 @@
/*
* This file exists in two locations:
* - src/common/util.ts
* - lib/vscode/src/vs/server/common/util.ts
* The second is a symlink to the first.
*/
/**
* Base options included on every page.
*/
export interface Options {
base: string
csStaticBase: string
logLevel: number
}
/** /**
* Split a string up to the delimiter. If the delimiter doesn't exist the first * Split a string up to the delimiter. If the delimiter doesn't exist the first
* item will have all the text and the second item will be an empty string. * item will have all the text and the second item will be an empty string.
@ -39,6 +23,12 @@ export const generateUuid = (length = 24): string => {
/** /**
* Remove extra slashes in a URL. * Remove extra slashes in a URL.
*
* This is meant to fill the job of `path.join` so you can concatenate paths and
* then normalize out any extra slashes.
*
* If you are using `path.join` you do not need this but note that `path` is for
* file system paths, not URLs.
*/ */
export const normalize = (url: string, keepTrailing = false): string => { export const normalize = (url: string, keepTrailing = false): string => {
return url.replace(/\/\/+/g, "/").replace(/\/+$/, keepTrailing ? "/" : "") return url.replace(/\/\/+/g, "/").replace(/\/+$/, keepTrailing ? "/" : "")
@ -51,50 +41,6 @@ export const trimSlashes = (url: string): string => {
return url.replace(/^\/+|\/+$/g, "") return url.replace(/^\/+|\/+$/g, "")
} }
/**
* Resolve a relative base against the window location. This is used for
* anything that doesn't work with a relative path.
*/
export const resolveBase = (base?: string): string => {
// After resolving the base will either start with / or be an empty string.
if (!base || base.startsWith("/")) {
return base ?? ""
}
const parts = location.pathname.split("/")
parts[parts.length - 1] = base
const url = new URL(location.origin + "/" + parts.join("/"))
return normalize(url.pathname)
}
/**
* Get options embedded in the HTML or query params.
*/
export const getOptions = <T extends Options>(): T => {
let options: T
try {
options = JSON.parse(document.getElementById("coder-options")!.getAttribute("data-settings")!)
} catch (error) {
options = {} as T
}
// You can also pass options in stringified form to the options query
// variable. Options provided here will override the ones in the options
// element.
const params = new URLSearchParams(location.search)
const queryOpts = params.get("options")
if (queryOpts) {
options = {
...options,
...JSON.parse(queryOpts),
}
}
options.base = resolveBase(options.base)
options.csStaticBase = resolveBase(options.csStaticBase)
return options
}
/** /**
* Wrap the value in an array if it's not already an array. If the value is * Wrap the value in an array if it's not already an array. If the value is
* undefined return an empty array. * undefined return an empty array.
@ -109,19 +55,8 @@ export const arrayify = <T>(value?: T | T[]): T[] => {
return [value] return [value]
} }
/**
* Get the first string. If there's no string return undefined.
*/
export const getFirstString = (value: string | string[] | object | undefined): string | undefined => {
if (Array.isArray(value)) {
return value[0]
}
return typeof value === "string" ? value : undefined
}
// TODO: Might make sense to add Error handling to the logger itself. // TODO: Might make sense to add Error handling to the logger itself.
export function logError(logger: { error: (msg: string) => void }, prefix: string, err: Error | string): void { export function logError(logger: { error: (msg: string) => void }, prefix: string, err: unknown): void {
if (err instanceof Error) { if (err instanceof Error) {
logger.error(`${prefix}: ${err.message} ${err.stack}`) logger.error(`${prefix}: ${err.message} ${err.stack}`)
} else { } else {

View File

@ -4,18 +4,57 @@ import express, { Express } from "express"
import { promises as fs } from "fs" import { promises as fs } from "fs"
import http from "http" import http from "http"
import * as httpolyglot from "httpolyglot" import * as httpolyglot from "httpolyglot"
import { Disposable } from "../common/emitter"
import * as util from "../common/util" import * as util from "../common/util"
import { DefaultedArgs } from "./cli" import { DefaultedArgs } from "./cli"
import { disposer } from "./http"
import { isNodeJSErrnoException } from "./util" import { isNodeJSErrnoException } from "./util"
import { handleUpgrade } from "./wsRouter" import { handleUpgrade } from "./wsRouter"
type ListenOptions = Pick<DefaultedArgs, "socket" | "port" | "host">
export interface App extends Disposable {
/** Handles regular HTTP requests. */
router: Express
/** Handles websocket requests. */
wsRouter: Express
/** The underlying HTTP server. */
server: http.Server
}
const listen = (server: http.Server, { host, port, socket }: ListenOptions) => {
return new Promise<void>(async (resolve, reject) => {
server.on("error", reject)
const onListen = () => {
// Promise resolved earlier so this is an unrelated error.
server.off("error", reject)
server.on("error", (err) => util.logError(logger, "http server error", err))
resolve()
}
if (socket) {
try {
await fs.unlink(socket)
} catch (error: any) {
handleArgsSocketCatchError(error)
}
server.listen(socket, onListen)
} else {
// [] is the correct format when using :: but Node errors with them.
server.listen(port, host.replace(/^\[|\]$/g, ""), onListen)
}
})
}
/** /**
* Create an Express app and an HTTP/S server to serve it. * Create an Express app and an HTTP/S server to serve it.
*/ */
export const createApp = async (args: DefaultedArgs): Promise<[Express, Express, http.Server]> => { export const createApp = async (args: DefaultedArgs): Promise<App> => {
const app = express() const router = express()
router.use(compression())
app.use(compression())
const server = args.cert const server = args.cert
? httpolyglot.createServer( ? httpolyglot.createServer(
@ -23,51 +62,38 @@ export const createApp = async (args: DefaultedArgs): Promise<[Express, Express,
cert: args.cert && (await fs.readFile(args.cert.value)), cert: args.cert && (await fs.readFile(args.cert.value)),
key: args["cert-key"] && (await fs.readFile(args["cert-key"])), key: args["cert-key"] && (await fs.readFile(args["cert-key"])),
}, },
app, router,
) )
: http.createServer(app) : http.createServer(router)
let resolved = false const dispose = disposer(server)
await new Promise<void>(async (resolve2, reject) => {
const resolve = () => {
resolved = true
resolve2()
}
server.on("error", (err) => {
handleServerError(resolved, err, reject)
})
if (args.socket) { await listen(server, args)
try {
await fs.unlink(args.socket)
} catch (error: any) {
handleArgsSocketCatchError(error)
}
server.listen(args.socket, resolve)
} else {
// [] is the correct format when using :: but Node errors with them.
server.listen(args.port, args.host.replace(/^\[|\]$/g, ""), resolve)
}
})
const wsApp = express() const wsRouter = express()
handleUpgrade(wsApp, server) handleUpgrade(wsRouter, server)
return [app, wsApp, server] return { router, wsRouter, server, dispose }
} }
/** /**
* Get the address of a server as a string (protocol *is* included) while * Get the address of a server as a string (protocol *is* included) while
* ensuring there is one (will throw if there isn't). * ensuring there is one (will throw if there isn't).
*
* The address might be a URL or it might be a pipe or socket path.
*/ */
export const ensureAddress = (server: http.Server): string => { export const ensureAddress = (server: http.Server, protocol: string): URL | string => {
const addr = server.address() const addr = server.address()
if (!addr) { if (!addr) {
throw new Error("server has no address") throw new Error("Server has no address")
} }
if (typeof addr !== "string") { if (typeof addr !== "string") {
return `http://${addr.address}:${addr.port}` return new URL(`${protocol}://${addr.address}:${addr.port}`)
} }
// If this is a string then it is a pipe or Unix socket.
return addr return addr
} }

View File

@ -3,12 +3,21 @@ import { promises as fs } from "fs"
import yaml from "js-yaml" import yaml from "js-yaml"
import * as os from "os" import * as os from "os"
import * as path from "path" import * as path from "path"
import { Args as VsArgs } from "../../typings/ipc" import {
import { canConnect, generateCertificate, generatePassword, humanPath, paths } from "./util" canConnect,
generateCertificate,
generatePassword,
humanPath,
paths,
isNodeJSErrnoException,
isFile,
} from "./util"
const DEFAULT_SOCKET_PATH = path.join(os.tmpdir(), "vscode-ipc")
export enum Feature { export enum Feature {
/** Web socket compression. */ // No current experimental features!
PermessageDeflate = "permessage-deflate", Placeholder = "placeholder",
} }
export enum AuthType { export enum AuthType {
@ -30,7 +39,13 @@ export enum LogLevel {
export class OptionalString extends Optional<string> {} export class OptionalString extends Optional<string> {}
export interface Args extends VsArgs { /**
* Arguments that the user explicitly provided on the command line. All
* arguments must be optional.
*
* For arguments with defaults see DefaultedArgs.
*/
export interface UserProvidedArgs {
config?: string config?: string
auth?: AuthType auth?: AuthType
password?: string password?: string
@ -38,30 +53,40 @@ export interface Args extends VsArgs {
cert?: OptionalString cert?: OptionalString
"cert-host"?: string "cert-host"?: string
"cert-key"?: string "cert-key"?: string
"disable-telemetry"?: boolean
"disable-update-check"?: boolean "disable-update-check"?: boolean
enable?: string[] enable?: string[]
help?: boolean help?: boolean
host?: string host?: string
locale?: string
port?: number
json?: boolean json?: boolean
log?: LogLevel log?: LogLevel
open?: boolean open?: boolean
port?: number
"bind-addr"?: string "bind-addr"?: string
socket?: string socket?: string
version?: boolean version?: boolean
force?: boolean
"list-extensions"?: boolean
"install-extension"?: string[]
"show-versions"?: boolean
"uninstall-extension"?: string[]
"proxy-domain"?: string[] "proxy-domain"?: string[]
locale?: string
_: string[]
"reuse-window"?: boolean "reuse-window"?: boolean
"new-window"?: boolean "new-window"?: boolean
"ignore-last-opened"?: boolean
link?: OptionalString link?: OptionalString
verbose?: boolean
/* Positional arguments. */
_?: string[]
// VS Code flags.
"disable-telemetry"?: boolean
force?: boolean
"user-data-dir"?: string
"enable-proposed-api"?: string[]
"extensions-dir"?: string
"builtin-extensions-dir"?: string
"install-extension"?: string[]
"uninstall-extension"?: string[]
"list-extensions"?: boolean
"locate-extension"?: string[]
"show-versions"?: boolean
category?: string
} }
interface Option<T> { interface Option<T> {
@ -80,9 +105,9 @@ interface Option<T> {
description?: string description?: string
/** /**
* If marked as beta, the option is marked as beta in help. * If marked as deprecated, the option is marked as deprecated in help.
*/ */
beta?: boolean deprecated?: boolean
} }
type OptionType<T> = T extends boolean type OptionType<T> = T extends boolean
@ -105,7 +130,7 @@ type Options<T> = {
[P in keyof T]: Option<OptionType<T[P]>> [P in keyof T]: Option<OptionType<T[P]>>
} }
const options: Options<Required<Args>> = { const options: Options<Required<UserProvidedArgs>> = {
auth: { type: AuthType, description: "The type of authentication to use." }, auth: { type: AuthType, description: "The type of authentication to use." },
password: { password: {
type: "string", type: "string",
@ -139,6 +164,7 @@ const options: Options<Required<Args>> = {
enable: { type: "string[]" }, enable: { type: "string[]" },
help: { type: "boolean", short: "h", description: "Show this output." }, help: { type: "boolean", short: "h", description: "Show this output." },
json: { type: "boolean" }, json: { type: "boolean" },
locale: { type: "string" }, // The preferred way to set the locale is via the UI.
open: { type: "boolean", description: "Open in browser on startup. Does not work remotely." }, open: { type: "boolean", description: "Open in browser on startup. Does not work remotely." },
"bind-addr": { "bind-addr": {
@ -162,10 +188,10 @@ const options: Options<Required<Args>> = {
"user-data-dir": { type: "string", path: true, description: "Path to the user data directory." }, "user-data-dir": { type: "string", path: true, description: "Path to the user data directory." },
"extensions-dir": { type: "string", path: true, description: "Path to the extensions directory." }, "extensions-dir": { type: "string", path: true, description: "Path to the extensions directory." },
"builtin-extensions-dir": { type: "string", path: true }, "builtin-extensions-dir": { type: "string", path: true },
"extra-extensions-dir": { type: "string[]", path: true },
"extra-builtin-extensions-dir": { type: "string[]", path: true },
"list-extensions": { type: "boolean", description: "List installed VS Code extensions." }, "list-extensions": { type: "boolean", description: "List installed VS Code extensions." },
force: { type: "boolean", description: "Avoid prompts when installing VS Code extensions." }, force: { type: "boolean", description: "Avoid prompts when installing VS Code extensions." },
"locate-extension": { type: "string[]" },
category: { type: "string" },
"install-extension": { "install-extension": {
type: "string[]", type: "string[]",
description: description:
@ -196,7 +222,6 @@ const options: Options<Required<Args>> = {
description: "Force to open a file or folder in an already opened window.", description: "Force to open a file or folder in an already opened window.",
}, },
locale: { type: "string" },
log: { type: LogLevel }, log: { type: LogLevel },
verbose: { type: "boolean", short: "vvv", description: "Enable verbose logging." }, verbose: { type: "boolean", short: "vvv", description: "Enable verbose logging." },
@ -204,10 +229,10 @@ const options: Options<Required<Args>> = {
type: OptionalString, type: OptionalString,
description: ` description: `
Securely bind code-server via our cloud service with the passed name. You'll get a URL like Securely bind code-server via our cloud service with the passed name. You'll get a URL like
https://hostname-username.cdr.co at which you can easily access your code-server instance. https://hostname-username.coder.co at which you can easily access your code-server instance.
Authorization is done via GitHub. Authorization is done via GitHub.
`, `,
beta: true, deprecated: true,
}, },
} }
@ -230,7 +255,7 @@ export const optionDescriptions = (): string[] => {
.map((line, i) => { .map((line, i) => {
line = line.trim() line = line.trim()
if (i === 0) { if (i === 0) {
return " ".repeat(widths.long - k.length) + (v.beta ? "(beta) " : "") + line return " ".repeat(widths.long - k.length) + (v.deprecated ? "(deprecated) " : "") + line
} }
return " ".repeat(widths.long + widths.short + 6) + line return " ".repeat(widths.long + widths.short + 6) + line
}) })
@ -253,12 +278,16 @@ export function splitOnFirstEquals(str: string): string[] {
return split return split
} }
/**
* Parse arguments into UserProvidedArgs. This should not go beyond checking
* that arguments are valid types and have values when required.
*/
export const parse = ( export const parse = (
argv: string[], argv: string[],
opts?: { opts?: {
configFile?: string configFile?: string
}, },
): Args => { ): UserProvidedArgs => {
const error = (msg: string): Error => { const error = (msg: string): Error => {
if (opts?.configFile) { if (opts?.configFile) {
msg = `error reading ${opts.configFile}: ${msg}` msg = `error reading ${opts.configFile}: ${msg}`
@ -267,7 +296,7 @@ export const parse = (
return new Error(msg) return new Error(msg)
} }
const args: Args = { _: [] } const args: UserProvidedArgs = {}
let ended = false let ended = false
for (let i = 0; i < argv.length; ++i) { for (let i = 0; i < argv.length; ++i) {
@ -281,17 +310,17 @@ export const parse = (
// Options start with a dash and require a value if non-boolean. // Options start with a dash and require a value if non-boolean.
if (!ended && arg.startsWith("-")) { if (!ended && arg.startsWith("-")) {
let key: keyof Args | undefined let key: keyof UserProvidedArgs | undefined
let value: string | undefined let value: string | undefined
if (arg.startsWith("--")) { if (arg.startsWith("--")) {
const split = splitOnFirstEquals(arg.replace(/^--/, "")) const split = splitOnFirstEquals(arg.replace(/^--/, ""))
key = split[0] as keyof Args key = split[0] as keyof UserProvidedArgs
value = split[1] value = split[1]
} else { } else {
const short = arg.replace(/^-/, "") const short = arg.replace(/^-/, "")
const pair = Object.entries(options).find(([, v]) => v.short === short) const pair = Object.entries(options).find(([, v]) => v.short === short)
if (pair) { if (pair) {
key = pair[0] as keyof Args key = pair[0] as keyof UserProvidedArgs
} }
} }
@ -366,6 +395,10 @@ export const parse = (
} }
// Everything else goes into _. // Everything else goes into _.
if (typeof args._ === "undefined") {
args._ = []
}
args._.push(arg) args._.push(arg)
} }
@ -374,11 +407,19 @@ export const parse = (
throw new Error("--cert-key is missing") throw new Error("--cert-key is missing")
} }
logger.debug(() => ["parsed command line", field("args", { ...args, password: undefined })]) logger.debug(() => [
`parsed ${opts?.configFile ? "config" : "command line"}`,
field("args", { ...args, password: undefined }),
])
return args return args
} }
/**
* User-provided arguments with defaults. The distinction between user-provided
* args and defaulted args exists so we can tell the difference between end
* values and what the user actually provided on the command line.
*/
export interface DefaultedArgs extends ConfigArgs { export interface DefaultedArgs extends ConfigArgs {
auth: AuthType auth: AuthType
cert?: { cert?: {
@ -392,6 +433,8 @@ export interface DefaultedArgs extends ConfigArgs {
usingEnvHashedPassword: boolean usingEnvHashedPassword: boolean
"extensions-dir": string "extensions-dir": string
"user-data-dir": string "user-data-dir": string
/* Positional arguments. */
_: []
} }
/** /**
@ -399,7 +442,7 @@ export interface DefaultedArgs extends ConfigArgs {
* with the defaults set. Arguments from the CLI are prioritized over config * with the defaults set. Arguments from the CLI are prioritized over config
* arguments. * arguments.
*/ */
export async function setDefaults(cliArgs: Args, configArgs?: ConfigArgs): Promise<DefaultedArgs> { export async function setDefaults(cliArgs: UserProvidedArgs, configArgs?: ConfigArgs): Promise<DefaultedArgs> {
const args = Object.assign({}, configArgs || {}, cliArgs) const args = Object.assign({}, configArgs || {}, cliArgs)
if (!args["user-data-dir"]) { if (!args["user-data-dir"]) {
@ -454,7 +497,7 @@ export async function setDefaults(cliArgs: Args, configArgs?: ConfigArgs): Promi
args.auth = AuthType.Password args.auth = AuthType.Password
} }
const addr = bindAddrFromAllSources(configArgs || { _: [] }, cliArgs) const addr = bindAddrFromAllSources(configArgs || {}, cliArgs)
args.host = addr.host args.host = addr.host
args.port = addr.port args.port = addr.port
@ -495,6 +538,10 @@ export async function setDefaults(cliArgs: Args, configArgs?: ConfigArgs): Promi
const proxyDomains = new Set((args["proxy-domain"] || []).map((d) => d.replace(/^\*\./, ""))) const proxyDomains = new Set((args["proxy-domain"] || []).map((d) => d.replace(/^\*\./, "")))
args["proxy-domain"] = Array.from(proxyDomains) args["proxy-domain"] = Array.from(proxyDomains)
if (typeof args._ === "undefined") {
args._ = []
}
return { return {
...args, ...args,
usingEnvPassword, usingEnvPassword,
@ -521,7 +568,7 @@ cert: false
` `
} }
interface ConfigArgs extends Args { interface ConfigArgs extends UserProvidedArgs {
config: string config: string
} }
@ -545,8 +592,8 @@ export async function readConfigFile(configPath?: string): Promise<ConfigArgs> {
await fs.writeFile(configPath, defaultConfigFile(generatedPassword), { await fs.writeFile(configPath, defaultConfigFile(generatedPassword), {
flag: "wx", // wx means to fail if the path exists. flag: "wx", // wx means to fail if the path exists.
}) })
logger.info(`Wrote default config file to ${humanPath(configPath)}`) logger.info(`Wrote default config file to ${humanPath(os.homedir(), configPath)}`)
} catch (error) { } catch (error: any) {
// EEXIST is fine; we don't want to overwrite existing configurations. // EEXIST is fine; we don't want to overwrite existing configurations.
if (error.code !== "EEXIST") { if (error.code !== "EEXIST") {
throw error throw error
@ -563,7 +610,7 @@ export async function readConfigFile(configPath?: string): Promise<ConfigArgs> {
*/ */
export function parseConfigFile(configFile: string, configPath: string): ConfigArgs { export function parseConfigFile(configFile: string, configPath: string): ConfigArgs {
if (!configFile) { if (!configFile) {
return { _: [], config: configPath } return { config: configPath }
} }
const config = yaml.load(configFile, { const config = yaml.load(configFile, {
@ -610,7 +657,7 @@ interface Addr {
* This function creates the bind address * This function creates the bind address
* using the CLI args. * using the CLI args.
*/ */
export function bindAddrFromArgs(addr: Addr, args: Args): Addr { export function bindAddrFromArgs(addr: Addr, args: UserProvidedArgs): Addr {
addr = { ...addr } addr = { ...addr }
if (args["bind-addr"]) { if (args["bind-addr"]) {
addr = parseBindAddr(args["bind-addr"]) addr = parseBindAddr(args["bind-addr"])
@ -628,7 +675,7 @@ export function bindAddrFromArgs(addr: Addr, args: Args): Addr {
return addr return addr
} }
function bindAddrFromAllSources(...argsConfig: Args[]): Addr { function bindAddrFromAllSources(...argsConfig: UserProvidedArgs[]): Addr {
let addr: Addr = { let addr: Addr = {
host: "localhost", host: "localhost",
port: 8080, port: 8080,
@ -641,62 +688,92 @@ function bindAddrFromAllSources(...argsConfig: Args[]): Addr {
return addr return addr
} }
export const shouldRunVsCodeCli = (args: Args): boolean => { /**
// Create new interface with only Arg keys * Reads the socketPath based on path passed in.
// keyof Args *
// Turn that into an array * The one usually passed in is the DEFAULT_SOCKET_PATH.
// Array<...> *
type ExtensionArgs = Array<keyof Args> * If it can't read the path, it throws an error and returns undefined.
const extensionRelatedArgs: ExtensionArgs = ["list-extensions", "install-extension", "uninstall-extension"] */
export async function readSocketPath(path: string): Promise<string | undefined> {
const argKeys = Object.keys(args) try {
return await fs.readFile(path, "utf8")
// If any of the extensionRelatedArgs are included in args } catch (error) {
// then we don't want to run the vscode cli // If it doesn't exist, we don't care.
return extensionRelatedArgs.some((arg) => argKeys.includes(arg)) // 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 * 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 * existing instance. The arguments here should be the arguments the user
* explicitly passed on the command line, not defaults or the configuration. * explicitly passed on the command line, *NOT DEFAULTS* or the configuration.
*/ */
export const shouldOpenInExistingInstance = async (args: Args): Promise<string | undefined> => { export const shouldOpenInExistingInstance = async (args: UserProvidedArgs): Promise<string | undefined> => {
// Always use the existing instance if we're running from VS Code's terminal. // Always use the existing instance if we're running from VS Code's terminal.
if (process.env.VSCODE_IPC_HOOK_CLI) { if (process.env.VSCODE_IPC_HOOK_CLI) {
logger.debug("Found VSCODE_IPC_HOOK_CLI")
return process.env.VSCODE_IPC_HOOK_CLI return process.env.VSCODE_IPC_HOOK_CLI
} }
const readSocketPath = async (): Promise<string | undefined> => {
try {
return await fs.readFile(path.join(os.tmpdir(), "vscode-ipc"), "utf8")
} catch (error) {
if (error.code !== "ENOENT") {
throw error
}
}
return undefined
}
// If these flags are set then assume the user is trying to open in an // 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.
const openInFlagCount = ["reuse-window", "new-window"].reduce((prev, cur) => { const openInFlagCount = ["reuse-window", "new-window"].reduce((prev, cur) => {
return args[cur as keyof Args] ? prev + 1 : prev return args[cur as keyof UserProvidedArgs] ? prev + 1 : prev
}, 0) }, 0)
if (openInFlagCount > 0) { if (openInFlagCount > 0) {
return readSocketPath() logger.debug("Found --reuse-window or --new-window")
return readSocketPath(DEFAULT_SOCKET_PATH)
} }
// It's possible the user is trying to spawn another instance of code-server. // It's possible the user is trying to spawn another instance of code-server.
// Check if any unrelated flags are set (check against one because `_` always // 1. Check if any unrelated flags are set (this should only run when
// exists), that a file or directory was passed, and that the socket is // code-server is invoked exactly like this: `code-server my-file`).
// active. // 2. That a file or directory was passed.
if (Object.keys(args).length === 1 && args._.length > 0) { // 3. That the socket is active.
const socketPath = await readSocketPath() 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 (socketPath && (await canConnect(socketPath))) {
logger.debug("Found existing code-server socket")
return socketPath return socketPath
} }
} }
return undefined return undefined
} }
/**
* Convert our arguments to VS Code server arguments.
*/
export const toVsCodeArgs = async (args: DefaultedArgs): Promise<CodeServerLib.ServerParsedArgs> => {
let workspace = ""
let folder = ""
if (args._.length) {
const lastEntry = path.resolve(args._[args._.length - 1])
const entryIsFile = await isFile(lastEntry)
if (entryIsFile && path.extname(lastEntry) === ".code-workspace") {
workspace = lastEntry
} else if (!entryIsFile) {
folder = lastEntry
}
// Otherwise it is a regular file. Spawning VS Code with a file is not yet
// supported but it can be done separately after code-server spawns.
}
return {
"connection-token": "0000",
...args,
workspace,
folder,
"accept-server-license-terms": true,
/** Type casting. */
help: !!args.help,
version: !!args.version,
port: args.port?.toString(),
}
}

View File

@ -3,7 +3,7 @@ import { spawn } from "child_process"
import path from "path" import path from "path"
import split2 from "split2" import split2 from "split2"
// https://github.com/cdr/coder-cloud // https://github.com/coder/coder-cloud
const coderCloudAgent = path.resolve(__dirname, "../../lib/coder-cloud-agent") const coderCloudAgent = path.resolve(__dirname, "../../lib/coder-cloud-agent")
function runAgent(...args: string[]): Promise<void> { function runAgent(...args: string[]): Promise<void> {
@ -33,9 +33,11 @@ function runAgent(...args: string[]): Promise<void> {
}) })
} }
export function coderCloudBind(csAddr: string, serverName = ""): Promise<void> { export function coderCloudBind(address: URL | string, serverName = ""): Promise<void> {
// addr needs to be in host:port format. if (typeof address === "string") {
// So we trim the protocol. throw new Error("Cannot link socket paths")
csAddr = csAddr.replace(/^https?:\/\//, "") }
return runAgent("bind", `--code-server-addr=${csAddr}`, serverName)
// Address needs to be in hostname:port format without the protocol.
return runAgent("bind", `--code-server-addr=${address.host}`, serverName)
} }

View File

@ -1,13 +1,15 @@
import { logger } from "@coder/logger" import { logger } from "@coder/logger"
import { JSONSchemaForNPMPackageJsonFiles } from "@schemastore/package" import type { JSONSchemaForNPMPackageJsonFiles } from "@schemastore/package"
import * as os from "os" import * as os from "os"
import * as path from "path" import * as path from "path"
export const WORKBENCH_WEB_CONFIG_ID = "vscode-workbench-web-configuration"
export function getPackageJson(relativePath: string): JSONSchemaForNPMPackageJsonFiles { export function getPackageJson(relativePath: string): JSONSchemaForNPMPackageJsonFiles {
let pkg = {} let pkg = {}
try { try {
pkg = require(relativePath) pkg = require(relativePath)
} catch (error) { } catch (error: any) {
logger.warn(error.message) logger.warn(error.message)
} }
@ -16,8 +18,12 @@ export function getPackageJson(relativePath: string): JSONSchemaForNPMPackageJso
const pkg = getPackageJson("../../package.json") const pkg = getPackageJson("../../package.json")
export const pkgName = pkg.name || "code-server"
export const version = pkg.version || "development" export const version = pkg.version || "development"
export const commit = pkg.commit || "development" export const commit = pkg.commit || "development"
export const rootPath = path.resolve(__dirname, "../..") export const rootPath = path.resolve(__dirname, "../..")
export const vsRootPath = path.join(rootPath, "vendor/modules/code-oss-dev")
export const tmpdir = path.join(os.tmpdir(), "code-server") export const tmpdir = path.join(os.tmpdir(), "code-server")
export const isDevMode = commit === "development" export const isDevMode = commit === "development"
export const httpProxyUri =
process.env.HTTPS_PROXY || process.env.https_proxy || process.env.HTTP_PROXY || process.env.http_proxy

View File

@ -1,20 +1,10 @@
import { logger } from "@coder/logger" import { logger } from "@coder/logger"
import { import { optionDescriptions, parse, readConfigFile, setDefaults, shouldOpenInExistingInstance } from "./cli"
optionDescriptions,
parse,
readConfigFile,
setDefaults,
shouldOpenInExistingInstance,
shouldRunVsCodeCli,
} from "./cli"
import { commit, version } from "./constants" import { commit, version } from "./constants"
import { openInExistingInstance, runCodeServer, runVsCodeCli } from "./main" import { openInExistingInstance, runCodeServer, runVsCodeCli, shouldSpawnCliProcess } from "./main"
import * as proxyAgent from "./proxy_agent"
import { isChild, wrapper } from "./wrapper" import { isChild, wrapper } from "./wrapper"
async function entry(): Promise<void> { async function entry(): Promise<void> {
proxyAgent.monkeyPatch(false)
// There's no need to check flags like --help or to spawn in an existing // There's no need to check flags like --help or to spawn in an existing
// instance for the child process because these would have already happened in // instance for the child process because these would have already happened in
// the parent and the child wouldn't have been spawned. We also get the // the parent and the child wouldn't have been spawned. We also get the
@ -24,7 +14,8 @@ async function entry(): Promise<void> {
if (isChild(wrapper)) { if (isChild(wrapper)) {
const args = await wrapper.handshake() const args = await wrapper.handshake()
wrapper.preventExit() wrapper.preventExit()
await runCodeServer(args) const server = await runCodeServer(args)
wrapper.onDispose(() => server.dispose())
return return
} }
@ -36,6 +27,8 @@ async function entry(): Promise<void> {
console.log("code-server", version, commit) console.log("code-server", version, commit)
console.log("") console.log("")
console.log(`Usage: code-server [options] [path]`) console.log(`Usage: code-server [options] [path]`)
console.log(` - Opening a directory: code-server ./path/to/your/project`)
console.log(` - Opening a saved workspace: code-server ./path/to/your/project.code-workspace`)
console.log("") console.log("")
console.log("Options") console.log("Options")
optionDescriptions().forEach((description) => { optionDescriptions().forEach((description) => {
@ -46,23 +39,27 @@ async function entry(): Promise<void> {
if (args.version) { if (args.version) {
if (args.json) { if (args.json) {
console.log({ console.log(
codeServer: version, JSON.stringify({
commit, codeServer: version,
vscode: require("../../vendor/modules/code-oss-dev/package.json").version, commit,
}) vscode: require("../../vendor/modules/code-oss-dev/package.json").version,
}),
)
} else { } else {
console.log(version, commit) console.log(version, commit)
} }
return return
} }
if (shouldRunVsCodeCli(args)) { if (shouldSpawnCliProcess(args)) {
logger.debug("Found VS Code arguments; spawning VS Code CLI")
return runVsCodeCli(args) return runVsCodeCli(args)
} }
const socketPath = await shouldOpenInExistingInstance(cliArgs) const socketPath = await shouldOpenInExistingInstance(cliArgs)
if (socketPath) { if (socketPath) {
logger.debug("Trying to open in existing instance")
return openInExistingInstance(args, socketPath) return openInExistingInstance(args, socketPath)
} }

View File

@ -1,13 +1,29 @@
import { field, logger } from "@coder/logger" import { field, logger } from "@coder/logger"
import * as express from "express" import * as express from "express"
import * as expressCore from "express-serve-static-core" import * as expressCore from "express-serve-static-core"
import qs from "qs" import * as http from "http"
import { HttpCode, HttpError } from "../common/http" import * as net from "net"
import { normalize, Options } from "../common/util" import * as qs from "qs"
import { Disposable } from "../common/emitter"
import { CookieKeys, HttpCode, HttpError } from "../common/http"
import { normalize } from "../common/util"
import { AuthType, DefaultedArgs } from "./cli" import { AuthType, DefaultedArgs } from "./cli"
import { commit, rootPath } from "./constants" import { version as codeServerVersion } from "./constants"
import { Heart } from "./heart" import { Heart } from "./heart"
import { getPasswordMethod, IsCookieValidArgs, isCookieValid, sanitizeString, escapeHtml } from "./util" import { CoderSettings, SettingsProvider } from "./settings"
import { UpdateProvider } from "./update"
import { getPasswordMethod, IsCookieValidArgs, isCookieValid, sanitizeString, escapeHtml, escapeJSON } from "./util"
/**
* Base options included on every page.
*/
export interface ClientConfiguration {
codeServerVersion: string
/** Relative path from this page to the root. No trailing slash. */
base: string
/** Relative path from this page to the static root. No trailing slash. */
csStaticBase: string
}
declare global { declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace // eslint-disable-next-line @typescript-eslint/no-namespace
@ -15,10 +31,22 @@ declare global {
export interface Request { export interface Request {
args: DefaultedArgs args: DefaultedArgs
heart: Heart heart: Heart
settings: SettingsProvider<CoderSettings>
updater: UpdateProvider
} }
} }
} }
export const createClientConfiguration = (req: express.Request): ClientConfiguration => {
const base = relativeRoot(req.originalUrl)
return {
base,
csStaticBase: base + "/_static",
codeServerVersion,
}
}
/** /**
* Replace common variable strings in HTML templates. * Replace common variable strings in HTML templates.
*/ */
@ -27,18 +55,16 @@ export const replaceTemplates = <T extends object>(
content: string, content: string,
extraOpts?: Omit<T, "base" | "csStaticBase" | "logLevel">, extraOpts?: Omit<T, "base" | "csStaticBase" | "logLevel">,
): string => { ): string => {
const base = relativeRoot(req) const serverOptions: ClientConfiguration = {
const options: Options = { ...createClientConfiguration(req),
base,
csStaticBase: base + "/static/" + commit + rootPath,
logLevel: logger.level,
...extraOpts, ...extraOpts,
} }
return content return content
.replace(/{{TO}}/g, (typeof req.query.to === "string" && escapeHtml(req.query.to)) || "/") .replace(/{{TO}}/g, (typeof req.query.to === "string" && escapeHtml(req.query.to)) || "/")
.replace(/{{BASE}}/g, options.base) .replace(/{{BASE}}/g, serverOptions.base)
.replace(/{{CS_STATIC_BASE}}/g, options.csStaticBase) .replace(/{{CS_STATIC_BASE}}/g, serverOptions.csStaticBase)
.replace(/"{{OPTIONS}}"/, `'${JSON.stringify(options)}'`) .replace("{{OPTIONS}}", () => escapeJSON(serverOptions))
} }
/** /**
@ -72,7 +98,7 @@ export const authenticated = async (req: express.Request): Promise<boolean> => {
const passwordMethod = getPasswordMethod(hashedPasswordFromArgs) const passwordMethod = getPasswordMethod(hashedPasswordFromArgs)
const isCookieValidArgs: IsCookieValidArgs = { const isCookieValidArgs: IsCookieValidArgs = {
passwordMethod, passwordMethod,
cookieKey: sanitizeString(req.cookies.key), cookieKey: sanitizeString(req.cookies[CookieKeys.Session]),
passwordFromArgs: req.args.password || "", passwordFromArgs: req.args.password || "",
hashedPasswordFromArgs: req.args["hashed-password"], hashedPasswordFromArgs: req.args["hashed-password"],
} }
@ -87,21 +113,34 @@ export const authenticated = async (req: express.Request): Promise<boolean> => {
/** /**
* Get the relative path that will get us to the root of the page. For each * Get the relative path that will get us to the root of the page. For each
* slash we need to go up a directory. For example: * slash we need to go up a directory. Will not have a trailing slash.
*
* For example:
*
* / => . * / => .
* /foo => . * /foo => .
* /foo/ => ./.. * /foo/ => ./..
* /foo/bar => ./.. * /foo/bar => ./..
* /foo/bar/ => ./../.. * /foo/bar/ => ./../..
*
* All paths must be relative in order to work behind a reverse proxy since we
* we do not know the base path. Anything that needs to be absolute (for
* example cookies) must get the base path from the frontend.
*
* All relative paths must be prefixed with the relative root to ensure they
* work no matter the depth at which they happen to appear.
*
* For Express `req.originalUrl` should be used as they remove the base from the
* standard `url` property making it impossible to get the true depth.
*/ */
export const relativeRoot = (req: express.Request): string => { export const relativeRoot = (originalUrl: string): string => {
const depth = (req.originalUrl.split("?", 1)[0].match(/\//g) || []).length const depth = (originalUrl.split("?", 1)[0].match(/\//g) || []).length
return normalize("./" + (depth > 1 ? "../".repeat(depth - 1) : "")) return normalize("./" + (depth > 1 ? "../".repeat(depth - 1) : ""))
} }
/** /**
* Redirect relatively to `/${to}`. Query variables on the current URI will be preserved. * Redirect relatively to `/${to}`. Query variables on the current URI will be
* `to` should be a simple path without any query parameters * preserved. `to` should be a simple path without any query parameters
* `override` will merge with the existing query (use `undefined` to unset). * `override` will merge with the existing query (use `undefined` to unset).
*/ */
export const redirect = ( export const redirect = (
@ -117,7 +156,7 @@ export const redirect = (
} }
}) })
const relativePath = normalize(`${relativeRoot(req)}/${to}`, true) const relativePath = normalize(`${relativeRoot(req.originalUrl)}/${to}`, true)
const queryString = qs.stringify(query) const queryString = qs.stringify(query)
const redirectPath = `${relativePath}${queryString ? `?${queryString}` : ""}` const redirectPath = `${relativePath}${queryString ? `?${queryString}` : ""}`
logger.debug(`redirecting from ${req.originalUrl} to ${redirectPath}`) logger.debug(`redirecting from ${req.originalUrl} to ${redirectPath}`)
@ -170,3 +209,89 @@ export const getCookieDomain = (host: string, proxyDomains: string[]): string |
logger.debug("got cookie doman", field("host", host)) logger.debug("got cookie doman", field("host", host))
return host || undefined return host || undefined
} }
/**
* Return a function capable of fully disposing an HTTP server.
*/
export function disposer(server: http.Server): Disposable["dispose"] {
const sockets = new Set<net.Socket>()
let cleanupTimeout: undefined | NodeJS.Timeout
server.on("connection", (socket) => {
sockets.add(socket)
socket.on("close", () => {
sockets.delete(socket)
if (cleanupTimeout && sockets.size === 0) {
clearTimeout(cleanupTimeout)
cleanupTimeout = undefined
}
})
})
return () => {
return new Promise<void>((resolve, reject) => {
// The whole reason we need this disposer is because close will not
// actually close anything; it only prevents future connections then waits
// until everything is closed.
server.close((err) => {
if (err) {
return reject(err)
}
resolve()
})
// If there are sockets remaining we might need to force close them or
// this promise might never resolve.
if (sockets.size > 0) {
// Give sockets a chance to close up shop.
cleanupTimeout = setTimeout(() => {
cleanupTimeout = undefined
for (const socket of sockets.values()) {
console.warn("a socket was left hanging")
socket.destroy()
}
}, 1000)
}
})
}
}
/**
* Get the options for setting a cookie. The options must be identical for
* setting and unsetting cookies otherwise they are considered separate.
*/
export const getCookieOptions = (req: express.Request): express.CookieOptions => {
// Normally we set paths relatively. However browsers do not appear to allow
// cookies to be set relatively which means we need an absolute path. We
// cannot be guaranteed we know the path since a reverse proxy might have
// rewritten it. That means we need to get the path from the frontend.
// The reason we need to set the path (as opposed to defaulting to /) is to
// avoid code-server instances on different sub-paths clobbering each other or
// from accessing each other's tokens (and to prevent other services from
// accessing code-server's tokens).
// When logging in or out the request must include the href (the full current
// URL of that page) and the relative path to the root as given to it by the
// backend. Using these two we can determine the true absolute root.
const url = new URL(
req.query.base || req.body.base || "/",
req.query.href || req.body.href || "http://" + (req.headers.host || "localhost"),
)
return {
domain: getCookieDomain(url.host, req.args["proxy-domain"]),
path: normalize(url.pathname) || "/",
sameSite: "lax",
}
}
/**
* Return the full path to the current page, preserving any trailing slash.
*/
export const self = (req: express.Request): string => {
return normalize(`${req.baseUrl}${req.originalUrl.endsWith("/") ? "/" : ""}`, true)
}

View File

@ -1,68 +1,73 @@
import { field, logger } from "@coder/logger" import { field, logger } from "@coder/logger"
import * as cp from "child_process"
import http from "http" import http from "http"
import * as path from "path" import * as os from "os"
import { CliMessage, OpenCommandPipeArgs } from "../../typings/ipc" import path from "path"
import { Disposable } from "../common/emitter"
import { plural } from "../common/util" import { plural } from "../common/util"
import { createApp, ensureAddress } from "./app" import { createApp, ensureAddress } from "./app"
import { AuthType, DefaultedArgs, Feature } from "./cli" import { AuthType, DefaultedArgs, Feature, toVsCodeArgs, UserProvidedArgs } from "./cli"
import { coderCloudBind } from "./coder_cloud" import { coderCloudBind } from "./coder_cloud"
import { commit, version } from "./constants" import { commit, version } from "./constants"
import { register } from "./routes" import { register } from "./routes"
import { humanPath, isFile, open } from "./util" import { humanPath, isFile, loadAMDModule, open } from "./util"
export const runVsCodeCli = (args: DefaultedArgs): void => { /**
logger.debug("forking vs code cli...") * Return true if the user passed an extension-related VS Code flag.
const vscode = cp.fork(path.resolve(__dirname, "../../vendor/modules/code-oss-dev/out/vs/server/fork"), [], { */
env: { export const shouldSpawnCliProcess = (args: UserProvidedArgs): boolean => {
...process.env, return (
CODE_SERVER_PARENT_PID: process.pid.toString(), !!args["list-extensions"] ||
}, !!args["install-extension"] ||
}) !!args["uninstall-extension"] ||
vscode.once("message", (message: any) => { !!args["locate-extension"]
logger.debug("got message from VS Code", field("message", message)) )
if (message.type !== "ready") { }
logger.error("Unexpected response waiting for ready response", field("type", message.type))
process.exit(1) /**
} * This is useful when an CLI arg should be passed to VS Code directly,
const send: CliMessage = { type: "cli", args } * such as when managing extensions.
vscode.send(send) * @deprecated This should be removed when code-server merges with lib/vscode.
}) */
vscode.once("error", (error) => { export const runVsCodeCli = async (args: DefaultedArgs): Promise<void> => {
logger.error("Got error from VS Code", field("error", error)) logger.debug("Running VS Code CLI")
process.exit(1)
}) // See ../../vendor/modules/code-oss-dev/src/vs/server/main.js.
vscode.on("exit", (code) => process.exit(code || 0)) const spawnCli = await loadAMDModule<CodeServerLib.SpawnCli>("vs/server/remoteExtensionHostAgent", "spawnCli")
try {
await spawnCli(await toVsCodeArgs(args))
} catch (error: any) {
logger.error("Got error from VS Code", error)
}
process.exit(0)
} }
export const openInExistingInstance = async (args: DefaultedArgs, socketPath: string): Promise<void> => { export const openInExistingInstance = async (args: DefaultedArgs, socketPath: string): Promise<void> => {
const pipeArgs: OpenCommandPipeArgs & { fileURIs: string[] } = { const pipeArgs: CodeServerLib.OpenCommandPipeArgs & { fileURIs: string[] } = {
type: "open", type: "open",
folderURIs: [], folderURIs: [],
fileURIs: [], fileURIs: [],
forceReuseWindow: args["reuse-window"], forceReuseWindow: args["reuse-window"],
forceNewWindow: args["new-window"], forceNewWindow: args["new-window"],
} }
const paths = args._ || []
for (let i = 0; i < args._.length; i++) { for (let i = 0; i < paths.length; i++) {
const fp = path.resolve(args._[i]) const fp = path.resolve(paths[i])
if (await isFile(fp)) { if (await isFile(fp)) {
pipeArgs.fileURIs.push(fp) pipeArgs.fileURIs.push(fp)
} else { } else {
pipeArgs.folderURIs.push(fp) pipeArgs.folderURIs.push(fp)
} }
} }
if (pipeArgs.forceNewWindow && pipeArgs.fileURIs.length > 0) { if (pipeArgs.forceNewWindow && pipeArgs.fileURIs.length > 0) {
logger.error("--new-window can only be used with folder paths") logger.error("--new-window can only be used with folder paths")
process.exit(1) process.exit(1)
} }
if (pipeArgs.folderURIs.length === 0 && pipeArgs.fileURIs.length === 0) { if (pipeArgs.folderURIs.length === 0 && pipeArgs.fileURIs.length === 0) {
logger.error("Please specify at least one file or folder") logger.error("Please specify at least one file or folder")
process.exit(1) process.exit(1)
} }
const vscode = http.request( const vscode = http.request(
{ {
path: "/", path: "/",
@ -82,11 +87,13 @@ export const openInExistingInstance = async (args: DefaultedArgs, socketPath: st
vscode.end() vscode.end()
} }
export const runCodeServer = async (args: DefaultedArgs): Promise<http.Server> => { export const runCodeServer = async (
args: DefaultedArgs,
): Promise<{ dispose: Disposable["dispose"]; server: http.Server }> => {
logger.info(`code-server ${version} ${commit}`) logger.info(`code-server ${version} ${commit}`)
logger.info(`Using user-data-dir ${humanPath(args["user-data-dir"])}`) logger.info(`Using user-data-dir ${humanPath(os.homedir(), args["user-data-dir"])}`)
logger.trace(`Using extensions-dir ${humanPath(args["extensions-dir"])}`) logger.trace(`Using extensions-dir ${humanPath(os.homedir(), args["extensions-dir"])}`)
if (args.auth === AuthType.Password && !args.password && !args["hashed-password"]) { if (args.auth === AuthType.Password && !args.password && !args["hashed-password"]) {
throw new Error( throw new Error(
@ -94,12 +101,18 @@ export const runCodeServer = async (args: DefaultedArgs): Promise<http.Server> =
) )
} }
const [app, wsApp, server] = await createApp(args) const app = await createApp(args)
const serverAddress = ensureAddress(server) const protocol = args.cert ? "https" : "http"
await register(app, wsApp, server, args) const serverAddress = ensureAddress(app.server, protocol)
const disposeRoutes = await register(app, args)
logger.info(`Using config file ${humanPath(os.homedir(), args.config)}`)
logger.info(
`${protocol.toUpperCase()} server listening on ${serverAddress.toString()} ${
args.link ? "(randomized by --link)" : ""
}`,
)
logger.info(`Using config file ${humanPath(args.config)}`)
logger.info(`HTTP server listening on ${serverAddress} ${args.link ? "(randomized by --link)" : ""}`)
if (args.auth === AuthType.Password) { if (args.auth === AuthType.Password) {
logger.info(" - Authentication is enabled") logger.info(" - Authentication is enabled")
if (args.usingEnvPassword) { if (args.usingEnvPassword) {
@ -107,14 +120,14 @@ export const runCodeServer = async (args: DefaultedArgs): Promise<http.Server> =
} else if (args.usingEnvHashedPassword) { } else if (args.usingEnvHashedPassword) {
logger.info(" - Using password from $HASHED_PASSWORD") logger.info(" - Using password from $HASHED_PASSWORD")
} else { } else {
logger.info(` - Using password from ${humanPath(args.config)}`) logger.info(` - Using password from ${humanPath(os.homedir(), args.config)}`)
} }
} else { } else {
logger.info(` - Authentication is disabled ${args.link ? "(disabled by --link)" : ""}`) logger.info(` - Authentication is disabled ${args.link ? "(disabled by --link)" : ""}`)
} }
if (args.cert) { if (args.cert) {
logger.info(` - Using certificate for HTTPS: ${humanPath(args.cert.value)}`) logger.info(` - Using certificate for HTTPS: ${humanPath(os.homedir(), args.cert.value)}`)
} else { } else {
logger.info(` - Not serving HTTPS ${args.link ? "(disabled by --link)" : ""}`) logger.info(` - Not serving HTTPS ${args.link ? "(disabled by --link)" : ""}`)
} }
@ -125,7 +138,7 @@ export const runCodeServer = async (args: DefaultedArgs): Promise<http.Server> =
} }
if (args.link) { if (args.link) {
await coderCloudBind(serverAddress.replace(/^https?:\/\//, ""), args.link.value) await coderCloudBind(serverAddress, args.link.value)
logger.info(" - Connected to cloud agent") logger.info(" - Connected to cloud agent")
} }
@ -144,16 +157,20 @@ export const runCodeServer = async (args: DefaultedArgs): Promise<http.Server> =
) )
} }
if (!args.socket && args.open) { if (args.open) {
// The web socket doesn't seem to work if browsing with 0.0.0.0.
const openAddress = serverAddress.replace("://0.0.0.0", "://localhost")
try { try {
await open(openAddress) await open(serverAddress)
logger.info(`Opened ${openAddress}`) logger.info(`Opened ${serverAddress}`)
} catch (error) { } catch (error) {
logger.error("Failed to open", field("address", openAddress), field("error", error)) logger.error("Failed to open", field("address", serverAddress.toString()), field("error", error))
} }
} }
return server return {
server: app.server,
dispose: async () => {
disposeRoutes()
await app.dispose()
},
}
} }

View File

@ -172,9 +172,9 @@ export class PluginAPI {
} }
await this.loadPlugin(path.join(dir, ent.name)) await this.loadPlugin(path.join(dir, ent.name))
} }
} catch (err) { } catch (error: any) {
if (err.code !== "ENOENT") { if (error.code !== "ENOENT") {
this.logger.warn(`failed to load plugins from ${q(dir)}: ${err.message}`) this.logger.warn(`failed to load plugins from ${q(dir)}: ${error.message}`)
} }
} }
} }
@ -195,9 +195,9 @@ export class PluginAPI {
} }
const p = this._loadPlugin(dir, packageJSON) const p = this._loadPlugin(dir, packageJSON)
this.plugins.set(p.name, p) this.plugins.set(p.name, p)
} catch (err) { } catch (error: any) {
if (err.code !== "ENOENT") { if (error.code !== "ENOENT") {
this.logger.warn(`failed to load plugin: ${err.stack}`) this.logger.warn(`failed to load plugin: ${error.stack}`)
} }
} }
} }
@ -278,7 +278,7 @@ export class PluginAPI {
} }
try { try {
await p.deinit() await p.deinit()
} catch (error) { } catch (error: any) {
this.logger.error("plugin failed to deinit", field("name", p.name), field("error", error.message)) this.logger.error("plugin failed to deinit", field("name", p.name), field("error", error.message))
} }
}), }),

View File

@ -1,81 +0,0 @@
import { logger } from "@coder/logger"
import * as http from "http"
import * as proxyAgent from "proxy-agent"
import * as proxyFromEnv from "proxy-from-env"
/**
* This file has nothing to do with the code-server proxy.
* It is to support $HTTP_PROXY, $HTTPS_PROXY and $NO_PROXY.
*
* - https://github.com/cdr/code-server/issues/124
* - https://www.npmjs.com/package/proxy-agent
* - https://www.npmjs.com/package/proxy-from-env
*
* This file exists in two locations:
* - src/node/proxy_agent.ts
* - lib/vscode/src/vs/base/node/proxy_agent.ts
* The second is a symlink to the first.
*/
/**
* monkeyPatch patches the node http,https modules to route all requests through the
* agent we get from the proxy-agent package.
*
* This approach only works if there is no code specifying an explicit agent when making
* a request.
*
* None of our code ever passes in a explicit agent to the http,https modules.
* VS Code's does sometimes but only when a user sets the http.proxy configuration.
* See https://code.visualstudio.com/docs/setup/network#_legacy-proxy-server-support
*
* Even if they do, it's probably the same proxy so we should be fine! And those knobs
* are deprecated anyway.
*/
export function monkeyPatch(inVSCode: boolean): void {
if (shouldEnableProxy()) {
const http = require("http")
const https = require("https")
// If we do not pass in a proxy URL, proxy-agent will get the URL from the environment.
// See https://www.npmjs.com/package/proxy-from-env.
// Also see shouldEnableProxy.
const pa = newProxyAgent(inVSCode)
http.globalAgent = pa
https.globalAgent = pa
}
}
function newProxyAgent(inVSCode: boolean): http.Agent {
// The reasoning for this split is that VS Code's build process does not have
// esModuleInterop enabled but the code-server one does. As a result depending on where
// we execute, we either have a default attribute or we don't.
//
// I can't enable esModuleInterop in VS Code's build process as it breaks and spits out
// a huge number of errors. And we can't use require as otherwise the modules won't be
// included in the final product.
if (inVSCode) {
return new (proxyAgent as any)()
} else {
return new (proxyAgent as any).default()
}
}
// If they have $NO_PROXY set to example.com then this check won't work!
// But that's drastically unlikely.
export function shouldEnableProxy(): boolean {
let shouldEnable = false
const httpProxy = proxyFromEnv.getProxyForUrl(`http://example.com`)
if (httpProxy) {
shouldEnable = true
logger.debug(`using $HTTP_PROXY ${httpProxy}`)
}
const httpsProxy = proxyFromEnv.getProxyForUrl(`https://example.com`)
if (httpsProxy) {
shouldEnable = true
logger.debug(`using $HTTPS_PROXY ${httpsProxy}`)
}
return shouldEnable
}

View File

@ -1,7 +1,6 @@
import { Request, Router } from "express" import { Request, Router } from "express"
import { HttpCode, HttpError } from "../../common/http" import { HttpCode, HttpError } from "../../common/http"
import { normalize } from "../../common/util" import { authenticated, ensureAuthenticated, redirect, self } from "../http"
import { authenticated, ensureAuthenticated, redirect } from "../http"
import { proxy } from "../proxy" import { proxy } from "../proxy"
import { Router as WsRouter } from "../wsRouter" import { Router as WsRouter } from "../wsRouter"
@ -56,7 +55,7 @@ router.all("*", async (req, res, next) => {
return next() return next()
} }
// Redirect all other pages to the login. // Redirect all other pages to the login.
const to = normalize(`${req.baseUrl}${req.path}`) const to = self(req)
return redirect(req, res, "login", { return redirect(req, res, "login", {
to: to !== "/" ? to : undefined, to: to !== "/" ? to : undefined,
}) })

67
src/node/routes/errors.ts Normal file
View File

@ -0,0 +1,67 @@
import { logger } from "@coder/logger"
import express from "express"
import { promises as fs } from "fs"
import path from "path"
import { WebsocketRequest } from "../../../typings/pluginapi"
import { HttpCode } from "../../common/http"
import { rootPath } from "../constants"
import { replaceTemplates } from "../http"
import { escapeHtml, getMediaMime } from "../util"
interface ErrorWithStatusCode {
statusCode: number
}
interface ErrorWithCode {
code: string
}
/** Error is network related. */
export const errorHasStatusCode = (error: any): error is ErrorWithStatusCode => {
return error && "statusCode" in error
}
/** Error originates from file system. */
export const errorHasCode = (error: any): error is ErrorWithCode => {
return error && "code" in error
}
const notFoundCodes = [404, "ENOENT", "EISDIR"]
export const errorHandler: express.ErrorRequestHandler = async (err, req, res, next) => {
let statusCode = 500
if (errorHasStatusCode(err)) {
statusCode = err.statusCode
} else if (errorHasCode(err) && notFoundCodes.includes(err.code)) {
statusCode = HttpCode.NotFound
}
res.status(statusCode)
// Assume anything that explicitly accepts text/html is a user browsing a
// page (as opposed to an xhr request). Don't use `req.accepts()` since
// *every* request that I've seen (in Firefox and Chromium at least)
// includes `*/*` making it always truthy. Even for css/javascript.
if (req.headers.accept && req.headers.accept.includes("text/html")) {
const resourcePath = path.resolve(rootPath, "src/browser/pages/error.html")
res.set("Content-Type", getMediaMime(resourcePath))
const content = await fs.readFile(resourcePath, "utf8")
res.send(
replaceTemplates(req, content)
.replace(/{{ERROR_TITLE}}/g, statusCode.toString())
.replace(/{{ERROR_HEADER}}/g, statusCode.toString())
.replace(/{{ERROR_BODY}}/g, escapeHtml(err.message)),
)
} else {
res.json({
error: err.message,
...(err.details || {}),
})
}
}
export const wsErrorHandler: express.ErrorRequestHandler = async (err, req, res, next) => {
logger.error(`${err.message} ${err.stack}`)
;(req as WebsocketRequest).ws.end()
}

View File

@ -1,44 +1,39 @@
import { logger } from "@coder/logger" import { logger } from "@coder/logger"
import bodyParser from "body-parser"
import cookieParser from "cookie-parser" import cookieParser from "cookie-parser"
import * as express from "express" import * as express from "express"
import { promises as fs } from "fs" import { promises as fs } from "fs"
import http from "http"
import * as path from "path" import * as path from "path"
import * as tls from "tls" import * as tls from "tls"
import * as pluginapi from "../../../typings/pluginapi" import * as pluginapi from "../../../typings/pluginapi"
import { Disposable } from "../../common/emitter"
import { HttpCode, HttpError } from "../../common/http" import { HttpCode, HttpError } from "../../common/http"
import { plural } from "../../common/util" import { plural } from "../../common/util"
import { App } from "../app"
import { AuthType, DefaultedArgs } from "../cli" import { AuthType, DefaultedArgs } from "../cli"
import { rootPath } from "../constants" import { commit, rootPath } from "../constants"
import { Heart } from "../heart" import { Heart } from "../heart"
import { ensureAuthenticated, redirect, replaceTemplates } from "../http" import { ensureAuthenticated, redirect } from "../http"
import { PluginAPI } from "../plugin" import { PluginAPI } from "../plugin"
import { CoderSettings, SettingsProvider } from "../settings"
import { UpdateProvider } from "../update"
import { getMediaMime, paths } from "../util" import { getMediaMime, paths } from "../util"
import { wrapper } from "../wrapper"
import * as apps from "./apps" import * as apps from "./apps"
import * as domainProxy from "./domainProxy" import * as domainProxy from "./domainProxy"
import { errorHandler, wsErrorHandler } from "./errors"
import * as health from "./health" import * as health from "./health"
import * as login from "./login" import * as login from "./login"
import * as logout from "./logout" import * as logout from "./logout"
import * as pathProxy from "./pathProxy" import * as pathProxy from "./pathProxy"
// static is a reserved keyword.
import * as _static from "./static"
import * as update from "./update" import * as update from "./update"
import * as vscode from "./vscode" import { CodeServerRouteWrapper } from "./vscode"
/** /**
* Register all routes and middleware. * Register all routes and middleware.
*/ */
export const register = async ( export const register = async (app: App, args: DefaultedArgs): Promise<Disposable["dispose"]> => {
app: express.Express,
wsApp: express.Express,
server: http.Server,
args: DefaultedArgs,
): Promise<void> => {
const heart = new Heart(path.join(paths.data, "heartbeat"), async () => { const heart = new Heart(path.join(paths.data, "heartbeat"), async () => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
server.getConnections((error, count) => { app.server.getConnections((error, count) => {
if (error) { if (error) {
return reject(error) return reject(error)
} }
@ -47,15 +42,15 @@ export const register = async (
}) })
}) })
}) })
server.on("close", () => {
heart.dispose()
})
app.disable("x-powered-by") app.router.disable("x-powered-by")
wsApp.disable("x-powered-by") app.wsRouter.disable("x-powered-by")
app.use(cookieParser()) app.router.use(cookieParser())
wsApp.use(cookieParser()) app.wsRouter.use(cookieParser())
const settings = new SettingsProvider<CoderSettings>(path.join(args["user-data-dir"], "coder.json"))
const updater = new UpdateProvider("https://api.github.com/repos/coder/code-server/releases/latest", settings)
const common: express.RequestHandler = (req, _, next) => { const common: express.RequestHandler = (req, _, next) => {
// /healthz|/healthz/ needs to be excluded otherwise health checks will make // /healthz|/healthz/ needs to be excluded otherwise health checks will make
@ -67,14 +62,16 @@ export const register = async (
// Add common variables routes can use. // Add common variables routes can use.
req.args = args req.args = args
req.heart = heart req.heart = heart
req.settings = settings
req.updater = updater
next() next()
} }
app.use(common) app.router.use(common)
wsApp.use(common) app.wsRouter.use(common)
app.use(async (req, res, next) => { app.router.use(async (req, res, next) => {
// If we're handling TLS ensure all requests are redirected to HTTPS. // If we're handling TLS ensure all requests are redirected to HTTPS.
// TODO: This does *NOT* work if you have a base path since to specify the // TODO: This does *NOT* work if you have a base path since to specify the
// protocol we need to specify the whole path. // protocol we need to specify the whole path.
@ -92,100 +89,80 @@ export const register = async (
next() next()
}) })
app.use("/", domainProxy.router) app.router.use("/", domainProxy.router)
wsApp.use("/", domainProxy.wsRouter.router) app.wsRouter.use("/", domainProxy.wsRouter.router)
app.all("/proxy/(:port)(/*)?", (req, res) => { app.router.all("/proxy/(:port)(/*)?", (req, res) => {
pathProxy.proxy(req, res) pathProxy.proxy(req, res)
}) })
wsApp.get("/proxy/(:port)(/*)?", async (req) => { app.wsRouter.get("/proxy/(:port)(/*)?", async (req) => {
await pathProxy.wsProxy(req as pluginapi.WebsocketRequest) await pathProxy.wsProxy(req as pluginapi.WebsocketRequest)
}) })
// These two routes pass through the path directly. // These two routes pass through the path directly.
// So the proxied app must be aware it is running // So the proxied app must be aware it is running
// under /absproxy/<someport>/ // under /absproxy/<someport>/
app.all("/absproxy/(:port)(/*)?", (req, res) => { app.router.all("/absproxy/(:port)(/*)?", (req, res) => {
pathProxy.proxy(req, res, { pathProxy.proxy(req, res, {
passthroughPath: true, passthroughPath: true,
}) })
}) })
wsApp.get("/absproxy/(:port)(/*)?", async (req) => { app.wsRouter.get("/absproxy/(:port)(/*)?", async (req) => {
await pathProxy.wsProxy(req as pluginapi.WebsocketRequest, { await pathProxy.wsProxy(req as pluginapi.WebsocketRequest, {
passthroughPath: true, passthroughPath: true,
}) })
}) })
let pluginApi: PluginAPI
if (!process.env.CS_DISABLE_PLUGINS) { if (!process.env.CS_DISABLE_PLUGINS) {
const workingDir = args._ && args._.length > 0 ? path.resolve(args._[args._.length - 1]) : undefined const workingDir = args._ && args._.length > 0 ? path.resolve(args._[args._.length - 1]) : undefined
const pluginApi = new PluginAPI(logger, process.env.CS_PLUGIN, process.env.CS_PLUGIN_PATH, workingDir) pluginApi = new PluginAPI(logger, process.env.CS_PLUGIN, process.env.CS_PLUGIN_PATH, workingDir)
await pluginApi.loadPlugins() await pluginApi.loadPlugins()
pluginApi.mount(app, wsApp) pluginApi.mount(app.router, app.wsRouter)
app.use("/api/applications", ensureAuthenticated, apps.router(pluginApi)) app.router.use("/api/applications", ensureAuthenticated, apps.router(pluginApi))
wrapper.onDispose(() => pluginApi.dispose())
} }
app.use(bodyParser.json()) app.router.use(express.json())
app.use(bodyParser.urlencoded({ extended: true })) app.router.use(express.urlencoded({ extended: true }))
app.use("/", vscode.router) app.router.use(
wsApp.use("/", vscode.wsRouter.router) "/_static",
app.use("/vscode", vscode.router) express.static(rootPath, {
wsApp.use("/vscode", vscode.wsRouter.router) cacheControl: commit !== "development",
fallthrough: false,
}),
)
app.use("/healthz", health.router) app.router.use("/healthz", health.router)
wsApp.use("/healthz", health.wsRouter.router) app.wsRouter.use("/healthz", health.wsRouter.router)
if (args.auth === AuthType.Password) { if (args.auth === AuthType.Password) {
app.use("/login", login.router) app.router.use("/login", login.router)
app.use("/logout", logout.router) app.router.use("/logout", logout.router)
} else { } else {
app.all("/login", (req, res) => redirect(req, res, "/", {})) app.router.all("/login", (req, res) => redirect(req, res, "/", {}))
app.all("/logout", (req, res) => redirect(req, res, "/", {})) app.router.all("/logout", (req, res) => redirect(req, res, "/", {}))
} }
app.use("/static", _static.router) app.router.use("/update", update.router)
app.use("/update", update.router)
app.use(() => { const vsServerRouteHandler = new CodeServerRouteWrapper()
// Note that the root route is replaced in Coder Enterprise by the plugin API.
for (const routePrefix of ["/vscode", "/"]) {
app.router.use(routePrefix, vsServerRouteHandler.router)
app.wsRouter.use(routePrefix, vsServerRouteHandler.wsRouter)
}
app.router.use(() => {
throw new HttpError("Not Found", HttpCode.NotFound) throw new HttpError("Not Found", HttpCode.NotFound)
}) })
const errorHandler: express.ErrorRequestHandler = async (err, req, res, next) => { app.router.use(errorHandler)
if (err.code === "ENOENT" || err.code === "EISDIR") { app.wsRouter.use(wsErrorHandler)
err.status = HttpCode.NotFound
}
const status = err.status ?? err.statusCode ?? 500 return () => {
res.status(status) heart.dispose()
pluginApi?.dispose()
// Assume anything that explicitly accepts text/html is a user browsing a vsServerRouteHandler.dispose()
// page (as opposed to an xhr request). Don't use `req.accepts()` since
// *every* request that I've seen (in Firefox and Chromium at least)
// includes `*/*` making it always truthy. Even for css/javascript.
if (req.headers.accept && req.headers.accept.includes("text/html")) {
const resourcePath = path.resolve(rootPath, "src/browser/pages/error.html")
res.set("Content-Type", getMediaMime(resourcePath))
const content = await fs.readFile(resourcePath, "utf8")
res.send(
replaceTemplates(req, content)
.replace(/{{ERROR_TITLE}}/g, status)
.replace(/{{ERROR_HEADER}}/g, status)
.replace(/{{ERROR_BODY}}/g, err.message),
)
} else {
res.json({
error: err.message,
...(err.details || {}),
})
}
} }
app.use(errorHandler)
const wsErrorHandler: express.ErrorRequestHandler = async (err, req, res, next) => {
logger.error(`${err.message} ${err.stack}`)
;(req as pluginapi.WebsocketRequest).ws.end()
}
wsApp.use(wsErrorHandler)
} }

View File

@ -1,15 +1,13 @@
import { Router, Request } from "express" import { Router, Request } from "express"
import { promises as fs } from "fs" import { promises as fs } from "fs"
import { RateLimiter as Limiter } from "limiter" import { RateLimiter as Limiter } from "limiter"
import * as os from "os"
import * as path from "path" import * as path from "path"
import { CookieKeys } from "../../common/http"
import { rootPath } from "../constants" import { rootPath } from "../constants"
import { authenticated, getCookieDomain, redirect, replaceTemplates } from "../http" import { authenticated, getCookieOptions, redirect, replaceTemplates } from "../http"
import { getPasswordMethod, handlePasswordValidation, humanPath, sanitizeString, escapeHtml } from "../util" import { getPasswordMethod, handlePasswordValidation, humanPath, sanitizeString, escapeHtml } from "../util"
export enum Cookie {
Key = "key",
}
// RateLimiter wraps around the limiter library for logins. // RateLimiter wraps around the limiter library for logins.
// It allows 2 logins every minute plus 12 logins every hour. // It allows 2 logins every minute plus 12 logins every hour.
export class RateLimiter { export class RateLimiter {
@ -30,7 +28,7 @@ export class RateLimiter {
const getRoot = async (req: Request, error?: Error): Promise<string> => { const getRoot = async (req: Request, error?: Error): Promise<string> => {
const content = await fs.readFile(path.join(rootPath, "src/browser/pages/login.html"), "utf8") const content = await fs.readFile(path.join(rootPath, "src/browser/pages/login.html"), "utf8")
let passwordMsg = `Check the config file at ${humanPath(req.args.config)} for the password.` let passwordMsg = `Check the config file at ${humanPath(os.homedir(), req.args.config)} for the password.`
if (req.args.usingEnvPassword) { if (req.args.usingEnvPassword) {
passwordMsg = "Password was set from $PASSWORD." passwordMsg = "Password was set from $PASSWORD."
} else if (req.args.usingEnvHashedPassword) { } else if (req.args.usingEnvHashedPassword) {
@ -61,7 +59,7 @@ router.get("/", async (req, res) => {
res.send(await getRoot(req)) res.send(await getRoot(req))
}) })
router.post("/", async (req, res) => { router.post<{}, string, { password: string; base?: string }, { to?: string }>("/", async (req, res) => {
const password = sanitizeString(req.body.password) const password = sanitizeString(req.body.password)
const hashedPasswordFromArgs = req.args["hashed-password"] const hashedPasswordFromArgs = req.args["hashed-password"]
@ -86,11 +84,7 @@ router.post("/", async (req, res) => {
if (isPasswordValid) { if (isPasswordValid) {
// The hash does not add any actual security but we do it for // The hash does not add any actual security but we do it for
// obfuscation purposes (and as a side effect it handles escaping). // obfuscation purposes (and as a side effect it handles escaping).
res.cookie(Cookie.Key, hashedPassword, { res.cookie(CookieKeys.Session, hashedPassword, getCookieOptions(req))
domain: getCookieDomain(req.headers.host || "", req.args["proxy-domain"]),
path: req.body.base || "/",
sameSite: "lax",
})
const to = (typeof req.query.to === "string" && req.query.to) || "/" const to = (typeof req.query.to === "string" && req.query.to) || "/"
return redirect(req, res, to, { to: undefined }) return redirect(req, res, to, { to: undefined })
@ -111,7 +105,7 @@ router.post("/", async (req, res) => {
) )
throw new Error("Incorrect password") throw new Error("Incorrect password")
} catch (error) { } catch (error: any) {
const renderedHtml = await getRoot(req, error) const renderedHtml = await getRoot(req, error)
res.send(renderedHtml) res.send(renderedHtml)
} }

View File

@ -1,17 +1,14 @@
import { Router } from "express" import { Router } from "express"
import { getCookieDomain, redirect } from "../http" import { CookieKeys } from "../../common/http"
import { Cookie } from "./login" import { getCookieOptions, redirect } from "../http"
import { sanitizeString } from "../util"
export const router = Router() export const router = Router()
router.get("/", async (req, res) => { router.get<{}, undefined, undefined, { base?: string; to?: string }>("/", async (req, res) => {
// Must use the *identical* properties used to set the cookie. // Must use the *identical* properties used to set the cookie.
res.clearCookie(Cookie.Key, { res.clearCookie(CookieKeys.Session, getCookieOptions(req))
domain: getCookieDomain(req.headers.host || "", req.args["proxy-domain"]),
path: req.query.base || "/",
sameSite: "lax",
})
const to = (typeof req.query.to === "string" && req.query.to) || "/" const to = sanitizeString(req.query.to) || "/"
return redirect(req, res, to, { to: undefined, base: undefined }) return redirect(req, res, to, { to: undefined, base: undefined, href: undefined })
}) })

View File

@ -1,10 +1,9 @@
import { Request, Response } from "express" import { Request, Response } from "express"
import * as path from "path" import * as path from "path"
import qs from "qs" import * as qs from "qs"
import * as pluginapi from "../../../typings/pluginapi" import * as pluginapi from "../../../typings/pluginapi"
import { HttpCode, HttpError } from "../../common/http" import { HttpCode, HttpError } from "../../common/http"
import { normalize } from "../../common/util" import { authenticated, ensureAuthenticated, redirect, self } from "../http"
import { authenticated, ensureAuthenticated, redirect } from "../http"
import { proxy as _proxy } from "../proxy" import { proxy as _proxy } from "../proxy"
const getProxyTarget = (req: Request, passthroughPath?: boolean): string => { const getProxyTarget = (req: Request, passthroughPath?: boolean): string => {
@ -25,7 +24,7 @@ export function proxy(
if (!authenticated(req)) { if (!authenticated(req)) {
// If visiting the root (/:port only) redirect to the login page. // If visiting the root (/:port only) redirect to the login page.
if (!req.params[0] || req.params[0] === "/") { if (!req.params[0] || req.params[0] === "/") {
const to = normalize(`${req.baseUrl}${req.path}`) const to = self(req)
return redirect(req, res, "login", { return redirect(req, res, "login", {
to: to !== "/" ? to : undefined, to: to !== "/" ? to : undefined,
}) })

View File

@ -1,71 +0,0 @@
import { field, logger } from "@coder/logger"
import { Router } from "express"
import { promises as fs } from "fs"
import * as path from "path"
import { Readable } from "stream"
import * as tarFs from "tar-fs"
import * as zlib from "zlib"
import { HttpCode, HttpError } from "../../common/http"
import { getFirstString } from "../../common/util"
import { rootPath } from "../constants"
import { authenticated, ensureAuthenticated, replaceTemplates } from "../http"
import { getMediaMime, pathToFsPath } from "../util"
export const router = Router()
// The commit is for caching.
router.get("/(:commit)(/*)?", async (req, res) => {
// Used by VS Code to load extensions into the web worker.
const tar = getFirstString(req.query.tar)
if (tar) {
await ensureAuthenticated(req)
let stream: Readable = tarFs.pack(pathToFsPath(tar))
if (req.headers["accept-encoding"] && req.headers["accept-encoding"].includes("gzip")) {
logger.debug("gzipping tar", field("path", tar))
const compress = zlib.createGzip()
stream.pipe(compress)
stream.on("error", (error) => compress.destroy(error))
stream.on("close", () => compress.end())
stream = compress
res.header("content-encoding", "gzip")
}
res.set("Content-Type", "application/x-tar")
stream.on("close", () => res.end())
return stream.pipe(res)
}
// If not a tar use the remainder of the path to load the resource.
if (!req.params[0]) {
throw new HttpError("Not Found", HttpCode.NotFound)
}
const resourcePath = path.resolve(req.params[0])
// Make sure it's in code-server if you aren't authenticated. This lets
// unauthenticated users load the login assets.
const isAuthenticated = await authenticated(req)
if (!resourcePath.startsWith(rootPath) && !isAuthenticated) {
throw new HttpError("Unauthorized", HttpCode.Unauthorized)
}
// Don't cache during development. - can also be used if you want to make a
// static request without caching.
if (req.params.commit !== "development" && req.params.commit !== "-") {
res.header("Cache-Control", "public, max-age=31536000")
}
// Without this the default is to use the directory the script loaded from.
if (req.headers["service-worker"]) {
res.header("service-worker-allowed", "/")
}
res.set("Content-Type", getMediaMime(resourcePath))
if (resourcePath.endsWith("manifest.json")) {
const content = await fs.readFile(resourcePath, "utf8")
return res.send(replaceTemplates(req, content))
}
const content = await fs.readFile(resourcePath)
return res.send(content)
})

View File

@ -1,18 +1,15 @@
import { Router } from "express" import { Router } from "express"
import { version } from "../constants" import { version } from "../constants"
import { ensureAuthenticated } from "../http" import { ensureAuthenticated } from "../http"
import { UpdateProvider } from "../update"
export const router = Router() export const router = Router()
const provider = new UpdateProvider()
router.get("/check", ensureAuthenticated, async (req, res) => { router.get("/check", ensureAuthenticated, async (req, res) => {
const update = await provider.getUpdate(req.query.force === "true") const update = await req.updater.getUpdate(req.query.force === "true")
res.json({ res.json({
checked: update.checked, checked: update.checked,
latest: update.version, latest: update.version,
current: version, current: version,
isLatest: provider.isLatestVersion(update), isLatest: req.updater.isLatestVersion(update),
}) })
}) })

View File

@ -1,232 +1,138 @@
import * as crypto from "crypto" import { logger } from "@coder/logger"
import { Request, Router } from "express" import * as express from "express"
import { promises as fs } from "fs" import { WebsocketRequest } from "../../../typings/pluginapi"
import * as path from "path" import { logError } from "../../common/util"
import qs from "qs" import { toVsCodeArgs } from "../cli"
import * as ipc from "../../../typings/ipc" import { isDevMode } from "../constants"
import { Emitter } from "../../common/emitter" import { authenticated, ensureAuthenticated, redirect, self } from "../http"
import { HttpCode, HttpError } from "../../common/http" import { SocketProxyProvider } from "../socket"
import { getFirstString } from "../../common/util" import { loadAMDModule } from "../util"
import { Feature } from "../cli"
import { isDevMode, rootPath, version } from "../constants"
import { authenticated, ensureAuthenticated, redirect, replaceTemplates } from "../http"
import { getMediaMime, pathToFsPath } from "../util"
import { VscodeProvider } from "../vscode"
import { Router as WsRouter } from "../wsRouter" import { Router as WsRouter } from "../wsRouter"
import { errorHandler } from "./errors"
export const router = Router() export class CodeServerRouteWrapper {
/** Assigned in `ensureCodeServerLoaded` */
private _codeServerMain!: CodeServerLib.IServerAPI
private _wsRouterWrapper = WsRouter()
private _socketProxyProvider = new SocketProxyProvider()
public router = express.Router()
const vscode = new VscodeProvider() public get wsRouter() {
return this._wsRouterWrapper.router
router.get("/", async (req, res) => {
const isAuthenticated = await authenticated(req)
if (!isAuthenticated) {
return redirect(req, res, "login", {
// req.baseUrl can be blank if already at the root.
to: req.baseUrl && req.baseUrl !== "/" ? req.baseUrl : undefined,
})
} }
const [content, options] = await Promise.all([ //#region Route Handlers
await fs.readFile(path.join(rootPath, "src/browser/pages/vscode.html"), "utf8"),
(async () => { private $root: express.Handler = async (req, res, next) => {
try { const isAuthenticated = await authenticated(req)
return await vscode.initialize({ args: req.args, remoteAuthority: req.headers.host || "" }, req.query)
} catch (error) { if (!isAuthenticated) {
const devMessage = isDevMode ? "It might not have finished compiling." : "" const to = self(req)
throw new Error(`VS Code failed to load. ${devMessage} ${error.message}`) return redirect(req, res, "login", {
to: to !== "/" ? to : undefined,
})
}
const { query } = await req.settings.read()
if (query) {
// Ew means the workspace was closed so clear the last folder/workspace.
if (req.query.ew) {
delete query.folder
delete query.workspace
} }
})(),
])
options.productConfiguration.codeServerVersion = version // Redirect to the last folder/workspace if nothing else is opened.
if (
!req.query.folder &&
!req.query.workspace &&
(query.folder || query.workspace) &&
!req.args["ignore-last-opened"] // This flag disables this behavior.
) {
const to = self(req)
return redirect(req, res, to, {
folder: query.folder,
workspace: query.workspace,
})
}
}
res.send( // Store the query parameters so we can use them on the next load. This
replaceTemplates<ipc.Options>( // also allows users to create functionality around query parameters.
req, await req.settings.write({ query: req.query })
// Uncomment prod blocks if not in development. TODO: Would this be
// better as a build step? Or maintain two HTML files again? next()
!isDevMode ? content.replace(/<!-- PROD_ONLY/g, "").replace(/END_PROD_ONLY -->/g, "") : content, }
{
authed: req.args.auth !== "none", private $proxyRequest: express.Handler = async (req, res, next) => {
disableUpdateCheck: !!req.args["disable-update-check"], // We allow certain errors to propagate so that other routers may handle requests
}, // outside VS Code
const requestErrorHandler = (error: any) => {
if (error instanceof Error && ["EntryNotFound", "FileNotFound", "HttpError"].includes(error.message)) {
next()
}
errorHandler(error, req, res, next)
}
req.once("error", requestErrorHandler)
this._codeServerMain.handleRequest(req, res)
}
private $proxyWebsocket = async (req: WebsocketRequest) => {
const wrappedSocket = await this._socketProxyProvider.createProxy(req.ws)
this._codeServerMain.handleUpgrade(req, wrappedSocket)
req.ws.resume()
}
//#endregion
/**
* Fetches a code server instance asynchronously to avoid an initial memory overhead.
*/
private ensureCodeServerLoaded: express.Handler = async (req, _res, next) => {
if (this._codeServerMain) {
// Already loaded...
return next()
}
// Create the server...
const { args } = req
/**
* @file ../../../vendor/modules/code-oss-dev/src/vs/server/main.js
*/
const createVSServer = await loadAMDModule<CodeServerLib.CreateServer>(
"vs/server/remoteExtensionHostAgent",
"createServer",
) )
.replace(`"{{REMOTE_USER_DATA_URI}}"`, `'${JSON.stringify(options.remoteUserDataUri)}'`)
.replace(`"{{PRODUCT_CONFIGURATION}}"`, `'${JSON.stringify(options.productConfiguration)}'`)
.replace(`"{{WORKBENCH_WEB_CONFIGURATION}}"`, `'${JSON.stringify(options.workbenchWebConfiguration)}'`)
.replace(`"{{NLS_CONFIGURATION}}"`, `'${JSON.stringify(options.nlsConfiguration)}'`),
)
})
/** try {
* TODO: Might currently be unused. this._codeServerMain = await createVSServer(null, {
*/ ...(await toVsCodeArgs(args)),
router.get("/resource(/*)?", ensureAuthenticated, async (req, res) => { // TODO: Make the browser helper script work.
const path = getFirstString(req.query.path) "without-browser-env-var": true,
if (path) { })
res.set("Content-Type", getMediaMime(path)) } catch (error) {
res.send(await fs.readFile(pathToFsPath(path))) logError(logger, "CodeServerRouteWrapper", error)
} if (isDevMode) {
}) return next(new Error((error instanceof Error ? error.message : error) + " (VS Code may still be compiling)"))
}
/** return next(error)
* Used by VS Code to load files.
*/
router.get("/vscode-remote-resource(/*)?", ensureAuthenticated, async (req, res) => {
const path = getFirstString(req.query.path)
if (path) {
res.set("Content-Type", getMediaMime(path))
res.send(await fs.readFile(pathToFsPath(path)))
}
})
/**
* VS Code webviews use these paths to load files and to load webview assets
* like HTML and JavaScript.
*/
router.get("/webview/*", ensureAuthenticated, async (req, res) => {
res.set("Content-Type", getMediaMime(req.path))
if (/^vscode-resource/.test(req.params[0])) {
return res.send(await fs.readFile(req.params[0].replace(/^vscode-resource(\/file)?/, "")))
}
return res.send(
await fs.readFile(path.join(vscode.vsRootPath, "out/vs/workbench/contrib/webview/browser/pre", req.params[0])),
)
})
interface Callback {
uri: {
scheme: string
authority?: string
path?: string
query?: string
fragment?: string
}
timeout: NodeJS.Timeout
}
const callbacks = new Map<string, Callback>()
const callbackEmitter = new Emitter<{ id: string; callback: Callback }>()
/**
* Get vscode-requestId from the query and throw if it's missing or invalid.
*/
const getRequestId = (req: Request): string => {
if (!req.query["vscode-requestId"]) {
throw new HttpError("vscode-requestId is missing", HttpCode.BadRequest)
}
if (typeof req.query["vscode-requestId"] !== "string") {
throw new HttpError("vscode-requestId is not a string", HttpCode.BadRequest)
}
return req.query["vscode-requestId"]
}
// Matches VS Code's fetch timeout.
const fetchTimeout = 5 * 60 * 1000
// The callback endpoints are used during authentication. A URI is stored on
// /callback and then fetched later on /fetch-callback.
// See ../../../lib/vscode/resources/web/code-web.js
router.get("/callback", ensureAuthenticated, async (req, res) => {
const uriKeys = [
"vscode-requestId",
"vscode-scheme",
"vscode-authority",
"vscode-path",
"vscode-query",
"vscode-fragment",
]
const id = getRequestId(req)
// Move any query variables that aren't URI keys into the URI's query
// (importantly, this will include the code for oauth).
const query: qs.ParsedQs = {}
for (const key in req.query) {
if (!uriKeys.includes(key)) {
query[key] = req.query[key]
} }
return next()
} }
const callback = { constructor() {
uri: { this.router.get("/", this.ensureCodeServerLoaded, this.$root)
scheme: getFirstString(req.query["vscode-scheme"]) || "code-oss", this.router.all("*", ensureAuthenticated, this.ensureCodeServerLoaded, this.$proxyRequest)
authority: getFirstString(req.query["vscode-authority"]), this._wsRouterWrapper.ws("/", ensureAuthenticated, this.ensureCodeServerLoaded, this.$proxyWebsocket)
path: getFirstString(req.query["vscode-path"]),
query: (getFirstString(req.query.query) || "") + "&" + qs.stringify(query),
fragment: getFirstString(req.query["vscode-fragment"]),
},
// Make sure the map doesn't leak if nothing fetches this URI.
timeout: setTimeout(() => callbacks.delete(id), fetchTimeout),
} }
callbacks.set(id, callback) dispose() {
callbackEmitter.emit({ id, callback }) this._codeServerMain?.dispose()
this._socketProxyProvider.stop()
res.sendFile(path.join(rootPath, "vendor/modules/code-oss-dev/resources/web/callback.html"))
})
router.get("/fetch-callback", ensureAuthenticated, async (req, res) => {
const id = getRequestId(req)
const send = (callback: Callback) => {
clearTimeout(callback.timeout)
callbacks.delete(id)
res.json(callback.uri)
} }
}
const callback = callbacks.get(id)
if (callback) {
return send(callback)
}
// VS Code will try again if the route returns no content but it seems more
// efficient to just wait on this request for as long as possible?
const handler = callbackEmitter.event(({ id: emitId, callback }) => {
if (id === emitId) {
handler.dispose()
send(callback)
}
})
// If the client closes the connection.
req.on("close", () => handler.dispose())
})
export const wsRouter = WsRouter()
wsRouter.ws("/", ensureAuthenticated, async (req) => {
const magic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
const reply = crypto
.createHash("sha1")
.update(req.headers["sec-websocket-key"] + magic)
.digest("base64")
const responseHeaders = [
"HTTP/1.1 101 Switching Protocols",
"Upgrade: websocket",
"Connection: Upgrade",
`Sec-WebSocket-Accept: ${reply}`,
]
// See if the browser reports it supports web socket compression.
// TODO: Parse this header properly.
const extensions = req.headers["sec-websocket-extensions"]
const isCompressionSupported = extensions ? extensions.includes("permessage-deflate") : false
// TODO: For now we only use compression if the user enables it.
const isCompressionEnabled = !!req.args.enable?.includes(Feature.PermessageDeflate)
const useCompression = isCompressionEnabled && isCompressionSupported
if (useCompression) {
// This response header tells the browser the server supports compression.
responseHeaders.push("Sec-WebSocket-Extensions: permessage-deflate; server_max_window_bits=15")
}
req.ws.write(responseHeaders.join("\r\n") + "\r\n\r\n")
await vscode.sendWebsocket(req.ws, req.query, useCompression)
})

View File

@ -1,8 +1,6 @@
import { logger } from "@coder/logger" import { logger } from "@coder/logger"
import { Query } from "express-serve-static-core" import { Query } from "express-serve-static-core"
import { promises as fs } from "fs" import { promises as fs } from "fs"
import * as path from "path"
import { paths } from "./util"
export type Settings = { [key: string]: Settings | string | boolean | number } export type Settings = { [key: string]: Settings | string | boolean | number }
@ -20,7 +18,7 @@ export class SettingsProvider<T> {
try { try {
const raw = (await fs.readFile(this.settingsPath, "utf8")).trim() const raw = (await fs.readFile(this.settingsPath, "utf8")).trim()
return raw ? JSON.parse(raw) : {} return raw ? JSON.parse(raw) : {}
} catch (error) { } catch (error: any) {
if (error.code !== "ENOENT") { if (error.code !== "ENOENT") {
logger.warn(error.message) logger.warn(error.message)
} }
@ -37,7 +35,7 @@ export class SettingsProvider<T> {
const oldSettings = await this.read() const oldSettings = await this.read()
const nextSettings = { ...oldSettings, ...settings } const nextSettings = { ...oldSettings, ...settings }
await fs.writeFile(this.settingsPath, JSON.stringify(nextSettings, null, 2)) await fs.writeFile(this.settingsPath, JSON.stringify(nextSettings, null, 2))
} catch (error) { } catch (error: any) {
logger.warn(error.message) logger.warn(error.message)
} }
} }
@ -54,14 +52,5 @@ export interface UpdateSettings {
* Global code-server settings. * Global code-server settings.
*/ */
export interface CoderSettings extends UpdateSettings { export interface CoderSettings extends UpdateSettings {
lastVisited: { query?: Query
url: string
workspace: boolean
}
query: Query
} }
/**
* Global code-server settings file.
*/
export const settings = new SettingsProvider<CoderSettings>(path.join(paths.data, "coder.json"))

View File

@ -1,10 +1,11 @@
import { field, logger } from "@coder/logger" import { field, logger } from "@coder/logger"
import * as http from "http" import * as http from "http"
import * as https from "https" import * as https from "https"
import ProxyAgent from "proxy-agent"
import * as semver from "semver" import * as semver from "semver"
import * as url from "url" import * as url from "url"
import { version } from "./constants" import { httpProxyUri, version } from "./constants"
import { settings as globalSettings, SettingsProvider, UpdateSettings } from "./settings" import { SettingsProvider, UpdateSettings } from "./settings"
export interface Update { export interface Update {
checked: number checked: number
@ -27,12 +28,11 @@ export class UpdateProvider {
* The URL for getting the latest version of code-server. Should return JSON * The URL for getting the latest version of code-server. Should return JSON
* that fulfills `LatestResponse`. * that fulfills `LatestResponse`.
*/ */
private readonly latestUrl = "https://api.github.com/repos/cdr/code-server/releases/latest", private readonly latestUrl: string,
/** /**
* Update information will be stored here. If not provided, the global * Update information will be stored here.
* settings will be used.
*/ */
private readonly settings: SettingsProvider<UpdateSettings> = globalSettings, private readonly settings: SettingsProvider<UpdateSettings>,
) {} ) {}
/** /**
@ -60,7 +60,7 @@ export class UpdateProvider {
} }
logger.debug("got latest version", field("latest", update.version)) logger.debug("got latest version", field("latest", update.version))
return update return update
} catch (error) { } catch (error: any) {
logger.error("Failed to get latest version", field("error", error.message)) logger.error("Failed to get latest version", field("error", error.message))
return { return {
checked: now, checked: now,
@ -103,8 +103,10 @@ export class UpdateProvider {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const request = (uri: string): void => { const request = (uri: string): void => {
logger.debug("Making request", field("uri", uri)) logger.debug("Making request", field("uri", uri))
const httpx = uri.startsWith("https") ? https : http const isHttps = uri.startsWith("https")
const client = httpx.get(uri, { headers: { "User-Agent": "code-server" } }, (response) => { const agent = httpProxyUri ? new ProxyAgent(httpProxyUri) : undefined
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) { if (!response.statusCode || response.statusCode < 200 || response.statusCode >= 400) {
response.destroy() response.destroy()
return reject(new Error(`${uri}: ${response.statusCode || "500"}`)) return reject(new Error(`${uri}: ${response.statusCode || "500"}`))

View File

@ -1,66 +0,0 @@
// In a bit of a hack, this file is stored in two places
// - src/node/uri_transformer.ts
// - lib/vscode/src/vs/server/uriTransformer.ts
// The reason for this is that we need a CommonJS-compiled
// version of this file to supply as a command line argument
// to extensionHostProcessSetup.ts; but we also need to include
// it ourselves cleanly in `lib/vscode/src/vs/server`.
// @oxy: Could not figure out how to compile as a CommonJS module
// in the same tree as VSCode, which is why I came up with the solution
// of storing it in two places.
// NOTE: copied over from lib/vscode/src/vs/common/uriIpc.ts
// remember to update this for proper type checks!
interface UriParts {
scheme: string
authority?: string
path?: string
}
interface IRawURITransformer {
transformIncoming(uri: UriParts): UriParts
transformOutgoing(uri: UriParts): UriParts
transformOutgoingScheme(scheme: string): string
}
// Using `export =` is deliberate.
// See lib/vscode/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts;
// they include the file directly with a node require and expect a function as `module.exports`.
// `export =` in TypeScript is equivalent to `module.exports =` in vanilla JS.
export = function rawURITransformerFactory(authority: string) {
return new RawURITransformer(authority)
}
class RawURITransformer implements IRawURITransformer {
constructor(private readonly authority: string) {}
transformIncoming(uri: UriParts): UriParts {
switch (uri.scheme) {
case "vscode-remote":
return { scheme: "file", path: uri.path }
default:
return uri
}
}
transformOutgoing(uri: UriParts): UriParts {
switch (uri.scheme) {
case "file":
return { scheme: "vscode-remote", authority: this.authority, path: uri.path }
default:
return uri
}
}
transformOutgoingScheme(scheme: string): string {
switch (scheme) {
case "file":
return "vscode-remote"
default:
return scheme
}
}
}

View File

@ -1,4 +1,3 @@
import { logger } from "@coder/logger"
import * as argon2 from "argon2" import * as argon2 from "argon2"
import * as cp from "child_process" import * as cp from "child_process"
import * as crypto from "crypto" import * as crypto from "crypto"
@ -10,7 +9,7 @@ import * as path from "path"
import safeCompare from "safe-compare" import safeCompare from "safe-compare"
import * as util from "util" import * as util from "util"
import xdgBasedir from "xdg-basedir" import xdgBasedir from "xdg-basedir"
import { getFirstString } from "../common/util" import { vsRootPath } from "./constants"
export interface Paths { export interface Paths {
data: string data: string
@ -25,10 +24,11 @@ const pattern = [
].join("|") ].join("|")
const re = new RegExp(pattern, "g") const re = new RegExp(pattern, "g")
export type OnLineCallback = (strippedLine: string, originalLine: string) => void
/** /**
* Split stdout on newlines and strip ANSI codes. * Split stdout on newlines and strip ANSI codes.
*/ */
export const onLine = (proc: cp.ChildProcess, callback: (strippedLine: string, originalLine: string) => void): void => { export const onLine = (proc: cp.ChildProcess, callback: OnLineCallback): void => {
let buffer = "" let buffer = ""
if (!proc.stdout) { if (!proc.stdout) {
throw new Error("no stdout") throw new Error("no stdout")
@ -57,10 +57,10 @@ export const paths = getEnvPaths()
* On MacOS this function gets the standard XDG directories instead of using the native macOS * On MacOS this function gets the standard XDG directories instead of using the native macOS
* ones. Most CLIs do this as in practice only GUI apps use the standard macOS directories. * ones. Most CLIs do this as in practice only GUI apps use the standard macOS directories.
*/ */
export function getEnvPaths(): Paths { export function getEnvPaths(platform = process.platform): Paths {
const paths = envPaths("code-server", { suffix: "" }) const paths = envPaths("code-server", { suffix: "" })
const append = (p: string): string => path.join(p, "code-server") const append = (p: string): string => path.join(p, "code-server")
switch (process.platform) { switch (platform) {
case "darwin": case "darwin":
return { return {
// envPaths uses native directories so force Darwin to use the XDG spec // envPaths uses native directories so force Darwin to use the XDG spec
@ -88,16 +88,17 @@ export function getEnvPaths(): Paths {
} }
/** /**
* humanPath replaces the home directory in p with ~. * humanPath replaces the home directory in path with ~.
* Makes it more readable. * Makes it more readable.
* *
* @param p * @param homedir - the home directory(i.e. `os.homedir()`)
* @param path - a file path
*/ */
export function humanPath(p?: string): string { export function humanPath(homedir: string, path?: string): string {
if (!p) { if (!path) {
return "" return ""
} }
return p.replace(os.homedir(), "~") return path.replace(homedir, "~")
} }
export const generateCertificate = async (hostname: string): Promise<{ cert: string; certKey: string }> => { export const generateCertificate = async (hostname: string): Promise<{ cert: string; certKey: string }> => {
@ -155,12 +156,7 @@ export const generatePassword = async (length = 24): Promise<string> => {
* Used to hash the password. * Used to hash the password.
*/ */
export const hash = async (password: string): Promise<string> => { export const hash = async (password: string): Promise<string> => {
try { return await argon2.hash(password)
return await argon2.hash(password)
} catch (error) {
logger.error(error)
return ""
}
} }
/** /**
@ -170,11 +166,7 @@ export const isHashMatch = async (password: string, hash: string) => {
if (password === "" || hash === "" || !hash.startsWith("$")) { if (password === "" || hash === "" || !hash.startsWith("$")) {
return false return false
} }
try { return await argon2.verify(hash, password)
return await argon2.verify(hash, password)
} catch (error) {
throw new Error(error)
}
} }
/** /**
@ -318,10 +310,10 @@ export async function isCookieValid({
* - greater than 0 characters * - greater than 0 characters
* - trims whitespace * - trims whitespace
*/ */
export function sanitizeString(str: string): string { export function sanitizeString(str: unknown): string {
// Very basic sanitization of string // Very basic sanitization of string
// Credit: https://stackoverflow.com/a/46719000/3015595 // Credit: https://stackoverflow.com/a/46719000/3015595
return typeof str === "string" && str.trim().length > 0 ? str.trim() : "" return typeof str === "string" ? str.trim() : ""
} }
const mimeTypes: { [key: string]: string } = { const mimeTypes: { [key: string]: string } = {
@ -393,9 +385,17 @@ export const isWsl = async (): Promise<boolean> => {
} }
/** /**
* Try opening a URL using whatever the system has set for opening URLs. * Try opening an address using whatever the system has set for opening URLs.
*/ */
export const open = async (url: string): Promise<void> => { export const open = async (address: URL | string): Promise<void> => {
if (typeof address === "string") {
throw new Error("Cannot open socket paths")
}
// Web sockets do not seem to work if browsing with 0.0.0.0.
const url = new URL(address)
if (url.hostname === "0.0.0.0") {
url.hostname = "localhost"
}
const args = [] as string[] const args = [] as string[]
const options = {} as cp.SpawnOptions const options = {} as cp.SpawnOptions
const platform = (await isWsl()) ? "wsl" : process.platform const platform = (await isWsl()) ? "wsl" : process.platform
@ -403,9 +403,9 @@ export const open = async (url: string): Promise<void> => {
if (platform === "win32" || platform === "wsl") { if (platform === "win32" || platform === "wsl") {
command = platform === "wsl" ? "cmd.exe" : "cmd" command = platform === "wsl" ? "cmd.exe" : "cmd"
args.push("/c", "start", '""', "/b") args.push("/c", "start", '""', "/b")
url = url.replace(/&/g, "^&") url.search = url.search.replace(/&/g, "^&")
} }
const proc = cp.spawn(command, [...args, url], options) const proc = cp.spawn(command, [...args, url.toString()], options)
await new Promise<void>((resolve, reject) => { await new Promise<void>((resolve, reject) => {
proc.on("error", reject) proc.on("error", reject)
proc.on("close", (code) => { proc.on("close", (code) => {
@ -439,55 +439,6 @@ export const isObject = <T extends object>(obj: T): obj is T => {
return !Array.isArray(obj) && typeof obj === "object" && obj !== null return !Array.isArray(obj) && typeof obj === "object" && obj !== null
} }
/**
* Taken from vs/base/common/charCode.ts. Copied for now instead of importing so
* we don't have to set up a `vs` alias to be able to import with types (since
* the alternative is to directly import from `out`).
*/
enum CharCode {
Slash = 47,
A = 65,
Z = 90,
a = 97,
z = 122,
Colon = 58,
}
/**
* Compute `fsPath` for the given uri.
* Taken from vs/base/common/uri.ts. It's not imported to avoid also importing
* everything that file imports.
*/
export function pathToFsPath(path: string, keepDriveLetterCasing = false): string {
const isWindows = process.platform === "win32"
const uri = { authority: undefined, path: getFirstString(path) || "", scheme: "file" }
let value: string
if (uri.authority && uri.path.length > 1 && uri.scheme === "file") {
// unc path: file://shares/c$/far/boo
value = `//${uri.authority}${uri.path}`
} else if (
uri.path.charCodeAt(0) === CharCode.Slash &&
((uri.path.charCodeAt(1) >= CharCode.A && uri.path.charCodeAt(1) <= CharCode.Z) ||
(uri.path.charCodeAt(1) >= CharCode.a && uri.path.charCodeAt(1) <= CharCode.z)) &&
uri.path.charCodeAt(2) === CharCode.Colon
) {
if (!keepDriveLetterCasing) {
// windows drive letter: file:///c:/far/boo
value = uri.path[1].toLowerCase() + uri.path.substr(2)
} else {
value = uri.path.substr(1)
}
} else {
// other path
value = uri.path
}
if (isWindows) {
value = value.replace(/\//g, "\\")
}
return value
}
/** /**
* Return a promise that resolves with whether the socket path is active. * Return a promise that resolves with whether the socket path is active.
*/ */
@ -531,5 +482,33 @@ export function escapeHtml(unsafe: string): string {
* it has a .code property. * it has a .code property.
*/ */
export function isNodeJSErrnoException(error: unknown): error is NodeJS.ErrnoException { export function isNodeJSErrnoException(error: unknown): error is NodeJS.ErrnoException {
return error instanceof Error && (error as NodeJS.ErrnoException).code !== undefined return error !== undefined && (error as NodeJS.ErrnoException).code !== undefined
}
// TODO: Replace with proper templating system.
export const escapeJSON = (value: cp.Serializable) => JSON.stringify(value).replace(/"/g, "&quot;")
type AMDModule<T> = { [exportName: string]: T }
/**
* Loads AMD module, typically from a compiled VSCode bundle.
*
* @deprecated This should be gradually phased out as code-server migrates to lib/vscode
* @param amdPath Path to module relative to lib/vscode
* @param exportName Given name of export in the file
*/
export const loadAMDModule = async <T>(amdPath: string, exportName: string): Promise<T> => {
// Set default remote native node modules path, if unset
process.env["VSCODE_INJECT_NODE_MODULE_LOOKUP_PATH"] =
process.env["VSCODE_INJECT_NODE_MODULE_LOOKUP_PATH"] || path.join(vsRootPath, "remote", "node_modules")
require(path.join(vsRootPath, "out/bootstrap-node")).injectNodeModuleLookupPath(
process.env["VSCODE_INJECT_NODE_MODULE_LOOKUP_PATH"],
)
const module = await new Promise<AMDModule<T>>((resolve, reject) => {
require(path.join(vsRootPath, "out/bootstrap-amd")).load(amdPath, resolve, reject)
})
return module[exportName] as T
} }

View File

@ -1,168 +0,0 @@
import { logger } from "@coder/logger"
import * as cp from "child_process"
import * as net from "net"
import * as path from "path"
import * as ipc from "../../typings/ipc"
import { arrayify, generateUuid } from "../common/util"
import { rootPath } from "./constants"
import { settings } from "./settings"
import { SocketProxyProvider } from "./socket"
import { isFile } from "./util"
import { onMessage, wrapper } from "./wrapper"
export class VscodeProvider {
public readonly serverRootPath: string
public readonly vsRootPath: string
private _vscode?: Promise<cp.ChildProcess>
private readonly socketProvider = new SocketProxyProvider()
public constructor() {
this.vsRootPath = path.resolve(rootPath, "vendor/modules/code-oss-dev")
this.serverRootPath = path.join(this.vsRootPath, "out/vs/server")
wrapper.onDispose(() => this.dispose())
}
public async dispose(): Promise<void> {
this.socketProvider.stop()
if (this._vscode) {
const vscode = await this._vscode
vscode.removeAllListeners()
vscode.kill()
this._vscode = undefined
}
}
public async initialize(
options: Omit<ipc.VscodeOptions, "startPath">,
query: ipc.Query,
): Promise<ipc.WorkbenchOptions> {
const { lastVisited } = await settings.read()
let startPath = await this.getFirstPath([
{ url: query.workspace, workspace: true },
{ url: query.folder, workspace: false },
options.args._ && options.args._.length > 0
? { url: path.resolve(options.args._[options.args._.length - 1]) }
: undefined,
!options.args["ignore-last-opened"] ? lastVisited : undefined,
])
if (query.ew) {
startPath = undefined
}
settings.write({
lastVisited: startPath,
query,
})
const id = generateUuid()
const vscode = await this.fork()
logger.debug("setting up vs code...")
this.send(
{
type: "init",
id,
options: {
...options,
startPath,
},
},
vscode,
)
const message = await onMessage<ipc.VscodeMessage, ipc.OptionsMessage>(
vscode,
(message): message is ipc.OptionsMessage => {
// There can be parallel initializations so wait for the right ID.
return message.type === "options" && message.id === id
},
)
return message.options
}
private fork(): Promise<cp.ChildProcess> {
if (this._vscode) {
return this._vscode
}
logger.debug("forking vs code...")
const vscode = cp.fork(path.join(this.serverRootPath, "fork"))
const dispose = () => {
vscode.removeAllListeners()
vscode.kill()
this._vscode = undefined
}
vscode.on("error", (error: Error) => {
logger.error(error.message)
if (error.stack) {
logger.debug(error.stack)
}
dispose()
})
vscode.on("exit", (code) => {
logger.error(`VS Code exited unexpectedly with code ${code}`)
dispose()
})
this._vscode = onMessage<ipc.VscodeMessage, ipc.ReadyMessage>(vscode, (message): message is ipc.ReadyMessage => {
return message.type === "ready"
}).then(() => vscode)
return this._vscode
}
/**
* VS Code expects a raw socket. It will handle all the web socket frames.
*/
public async sendWebsocket(socket: net.Socket, query: ipc.Query, permessageDeflate: boolean): Promise<void> {
const vscode = await this._vscode
// TLS sockets cannot be transferred to child processes so we need an
// in-between. Non-TLS sockets will be returned as-is.
const socketProxy = await this.socketProvider.createProxy(socket)
this.send({ type: "socket", query, permessageDeflate }, vscode, socketProxy)
}
private send(message: ipc.CodeServerMessage, vscode?: cp.ChildProcess, socket?: net.Socket): void {
if (!vscode || vscode.killed) {
throw new Error("vscode is not running")
}
vscode.send(message, socket)
}
/**
* Choose the first non-empty path from the provided array.
*
* Each array item consists of `url` and an optional `workspace` boolean that
* indicates whether that url is for a workspace.
*
* `url` can be a fully qualified URL or just the path portion.
*
* `url` can also be a query object to make it easier to pass in query
* variables directly but anything that isn't a string or string array is not
* valid and will be ignored.
*/
private async getFirstPath(
startPaths: Array<{ url?: string | string[] | ipc.Query | ipc.Query[]; workspace?: boolean } | undefined>,
): Promise<ipc.StartPath | undefined> {
for (let i = 0; i < startPaths.length; ++i) {
const startPath = startPaths[i]
const url = arrayify(startPath && startPath.url).find((p) => !!p)
if (startPath && url && typeof url === "string") {
return {
url,
// The only time `workspace` is undefined is for the command-line
// argument, in which case it's a path (not a URL) so we can stat it
// without having to parse it.
workspace: typeof startPath.workspace !== "undefined" ? startPath.workspace : await isFile(url),
}
}
}
return undefined
}
}

Some files were not shown because too many files have changed in this diff Show More