Compare commits
76 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
11f53784c5 | ||
|
|
7e1bb8fc96 | ||
|
|
ebe4d7ef29 | ||
|
|
f71d98f95c | ||
|
|
7fe475c1ef | ||
|
|
261af28f70 | ||
|
|
0713fa900b | ||
|
|
cc18175ce3 | ||
|
|
27f0f195a8 | ||
|
|
7282ebf436 | ||
|
|
c35d558352 | ||
|
|
8cb4e2c226 | ||
|
|
e5067ba2a9 | ||
|
|
fa0853dca6 | ||
|
|
a898dd34b9 | ||
|
|
4eb4375119 | ||
|
|
290c533c8e | ||
|
|
67e2a99df2 | ||
|
|
0ad7d93ea6 | ||
|
|
4cb8a32f4c | ||
|
|
833314aae8 | ||
|
|
5247878d93 | ||
|
|
ae65c83cbd | ||
|
|
eca4448877 | ||
|
|
93fb76e4a7 | ||
|
|
a1537d7138 | ||
|
|
def81245a4 | ||
|
|
37c80c9bbd | ||
|
|
be37821ab9 | ||
|
|
f74f1721e6 | ||
|
|
fb63c0cd22 | ||
|
|
bb26d2edd3 | ||
|
|
303fe2bc4e | ||
|
|
5a38ab95fe | ||
|
|
19710ab144 | ||
|
|
a018e30d6f | ||
|
|
fb835838db | ||
|
|
3d7fbec40f | ||
|
|
96170de191 | ||
|
|
2e2d03371f | ||
|
|
a0db6723c1 | ||
|
|
23ead21b1d | ||
|
|
42390da097 | ||
|
|
d0f6cbb02d | ||
|
|
fa59156a2a | ||
|
|
8ffe599796 | ||
|
|
a6f8840009 | ||
|
|
1feb30a7ff | ||
|
|
182aca6490 | ||
|
|
8311cf5657 | ||
|
|
4de2511162 | ||
|
|
3f7b91e2e2 | ||
|
|
431137da45 | ||
|
|
4d276b88c0 | ||
|
|
e28c9ab287 | ||
|
|
b540737b10 | ||
|
|
4380356e0c | ||
|
|
72caafe8b0 | ||
|
|
08b9e9ad1f | ||
|
|
2dc7863ec3 | ||
|
|
30100caf0c | ||
|
|
f79bb210ec | ||
|
|
182791319a | ||
|
|
624cd9d44f | ||
|
|
95ef6dbf2f | ||
|
|
016daf2fdd | ||
|
|
247c4ec776 | ||
|
|
d55e06936b | ||
|
|
2a3608df53 | ||
|
|
c6062c3d0a | ||
|
|
9ff535eddc | ||
|
|
2bf91ff6a6 | ||
|
|
ccc519ecbd | ||
|
|
40e1f066ff | ||
|
|
ac09aa6ea8 | ||
|
|
f5e3dca3b9 |
@@ -35,7 +35,8 @@ We also have an in-depth [setup and configuration](./doc/guide.md) guide.
|
|||||||
|
|
||||||
### Alpha Program 🐣
|
### Alpha Program 🐣
|
||||||
|
|
||||||
We're working on a cloud platform that makes deploying and managing code-server easier. Consider [updating to 3.7.0](https://github.com/cdr/code-server/releases/tag/v3.7.0) and running code-server with our experimental flag `--link` if you don't want to worry about
|
We're working on a cloud platform that makes deploying and managing code-server easier.
|
||||||
|
Consider updating to the latest version and running code-server with our experimental flag `--link` if you don't want to worry about
|
||||||
|
|
||||||
- TLS
|
- TLS
|
||||||
- Authentication
|
- Authentication
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ Make sure you have `$GITHUB_TOKEN` set and [hub](https://github.com/github/hub)
|
|||||||
1. Update in `package.json`
|
1. Update in `package.json`
|
||||||
2. Update in [./doc/install.md](../doc/install.md)
|
2. Update in [./doc/install.md](../doc/install.md)
|
||||||
3. Update in [./ci/helm-chart/README.md](../ci/helm-chart/README.md)
|
3. Update in [./ci/helm-chart/README.md](../ci/helm-chart/README.md)
|
||||||
|
- Remember to update the chart version as well on top of appVersion in `Chart.yaml`.
|
||||||
2. GitHub actions will generate the `npm-package`, `release-packages` and `release-images` artifacts.
|
2. GitHub actions will generate the `npm-package`, `release-packages` and `release-images` artifacts.
|
||||||
1. You do not have to wait for these.
|
1. You do not have to wait for these.
|
||||||
3. Run `yarn release:github-draft` to create a GitHub draft release from the template with
|
3. Run `yarn release:github-draft` to create a GitHub draft release from the template with
|
||||||
@@ -66,6 +67,10 @@ This directory contains scripts used for the development of code-server.
|
|||||||
- [./ci/dev/watch.ts](./dev/watch.ts) (`yarn watch`)
|
- [./ci/dev/watch.ts](./dev/watch.ts) (`yarn watch`)
|
||||||
- Starts a process to build and launch code-server and restart on any code changes.
|
- Starts a process to build and launch code-server and restart on any code changes.
|
||||||
- Example usage in [./doc/CONTRIBUTING.md](../doc/CONTRIBUTING.md).
|
- Example usage in [./doc/CONTRIBUTING.md](../doc/CONTRIBUTING.md).
|
||||||
|
- [./ci/dev/gen_icons.sh](./ci/dev/gen_icons.sh) (`yarn icons`)
|
||||||
|
- Generates the various icons from a single `.svg` favicon in
|
||||||
|
`src/browser/media/favicon.svg`.
|
||||||
|
- Requires [imagemagick](https://imagemagick.org/index.php)
|
||||||
|
|
||||||
## build
|
## build
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,17 @@ v$VERSION
|
|||||||
|
|
||||||
VS Code v$(vscode_version)
|
VS Code v$(vscode_version)
|
||||||
|
|
||||||
- Summarize changes here with references to issues
|
Upgrading is as easy as installing the new version over the old one. code-server
|
||||||
|
maintains all user data in \`~/.local/share/code-server\` so that it is preserved in between
|
||||||
|
installations.
|
||||||
|
|
||||||
|
## New Features
|
||||||
|
- ⭐ Summarize new features here with references to issues
|
||||||
|
|
||||||
|
## Bug Fixes
|
||||||
|
- ⭐ Summarize bug fixes here with references to issues
|
||||||
|
|
||||||
|
Cheers! 🍻
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
21
ci/dev/gen_icons.sh
Executable file
@@ -0,0 +1,21 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
main() {
|
||||||
|
cd src/browser/media
|
||||||
|
|
||||||
|
# We need .ico for backwards compatibility.
|
||||||
|
# The other two are the only icon sizes required by Chrome and
|
||||||
|
# we use them for stuff like apple-touch-icon as well.
|
||||||
|
# https://web.dev/add-manifest/
|
||||||
|
#
|
||||||
|
# This should be enough and we can always add more if there are problems.
|
||||||
|
|
||||||
|
# -background defaults to white but we want it transparent.
|
||||||
|
# https://imagemagick.org/script/command-line-options.php#background
|
||||||
|
convert -background transparent -resize 256x256 favicon.svg favicon.ico
|
||||||
|
convert -background transparent -resize 192x192 favicon.svg pwa-icon-192.png
|
||||||
|
convert -background transparent -resize 512x512 favicon.svg pwa-icon-512.png
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
@@ -11,6 +11,11 @@ main() {
|
|||||||
if command -v helm && helm kubeval --help > /dev/null; then
|
if command -v helm && helm kubeval --help > /dev/null; then
|
||||||
helm kubeval ci/helm-chart
|
helm kubeval ci/helm-chart
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
cd lib/vscode
|
||||||
|
# Run this periodically in vanilla VS code to make sure we don't add any more warnings.
|
||||||
|
yarn eslint --max-warnings=3
|
||||||
|
cd "$OLDPWD"
|
||||||
}
|
}
|
||||||
|
|
||||||
main "$@"
|
main "$@"
|
||||||
|
|||||||
1176
ci/dev/vscode.patch
@@ -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.0
|
version: 1.0.3
|
||||||
|
|
||||||
# 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.7.0
|
appVersion: 3.7.4
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# code-server
|
# code-server
|
||||||
|
|
||||||
  
|
  
|
||||||
|
|
||||||
[code-server](https://github.com/cdr/code-server) code-server is VS Code running
|
[code-server](https://github.com/cdr/code-server) code-server is VS Code running
|
||||||
on a remote server, accessible through the browser.
|
on a remote server, accessible through the browser.
|
||||||
@@ -72,7 +72,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.7.0"` | |
|
| image.tag | string | `"3.7.4"` | |
|
||||||
| imagePullSecrets | list | `[]` | |
|
| imagePullSecrets | list | `[]` | |
|
||||||
| ingress.enabled | bool | `false` | |
|
| ingress.enabled | bool | `false` | |
|
||||||
| nameOverride | string | `""` | |
|
| nameOverride | string | `""` | |
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ replicaCount: 1
|
|||||||
|
|
||||||
image:
|
image:
|
||||||
repository: codercom/code-server
|
repository: codercom/code-server
|
||||||
tag: '3.7.0'
|
tag: '3.7.4'
|
||||||
pullPolicy: Always
|
pullPolicy: Always
|
||||||
|
|
||||||
imagePullSecrets: []
|
imagePullSecrets: []
|
||||||
|
|||||||
@@ -27,6 +27,6 @@ ENV PATH=/usr/local/go/bin:$GOPATH/bin:$PATH
|
|||||||
# Install Go dependencies
|
# Install Go dependencies
|
||||||
ENV GO111MODULE=on
|
ENV GO111MODULE=on
|
||||||
RUN go get mvdan.cc/sh/v3/cmd/shfmt
|
RUN go get mvdan.cc/sh/v3/cmd/shfmt
|
||||||
RUN go get github.com/goreleaser/nfpm/cmd/nfpm
|
RUN go get github.com/goreleaser/nfpm/cmd/nfpm@v1.9.0
|
||||||
|
|
||||||
RUN curl -fsSL https://get.docker.com | sh
|
RUN curl -fsSL https://get.docker.com | sh
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ ENV PATH=/usr/local/go/bin:$GOPATH/bin:$PATH
|
|||||||
# Install Go dependencies
|
# Install Go dependencies
|
||||||
ENV GO111MODULE=on
|
ENV GO111MODULE=on
|
||||||
RUN go get mvdan.cc/sh/v3/cmd/shfmt
|
RUN go get mvdan.cc/sh/v3/cmd/shfmt
|
||||||
RUN go get github.com/goreleaser/nfpm/cmd/nfpm
|
RUN go get github.com/goreleaser/nfpm/cmd/nfpm@v1.9.0
|
||||||
|
|
||||||
RUN VERSION="$(curl -fsSL https://storage.googleapis.com/kubernetes-release/release/stable.txt)" && \
|
RUN VERSION="$(curl -fsSL https://storage.googleapis.com/kubernetes-release/release/stable.txt)" && \
|
||||||
curl -fsSL "https://storage.googleapis.com/kubernetes-release/release/$VERSION/bin/linux/amd64/kubectl" > /usr/local/bin/kubectl \
|
curl -fsSL "https://storage.googleapis.com/kubernetes-release/release/$VERSION/bin/linux/amd64/kubectl" > /usr/local/bin/kubectl \
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ main() {
|
|||||||
yarn --frozen-lockfile
|
yarn --frozen-lockfile
|
||||||
|
|
||||||
git submodule update --init
|
git submodule update --init
|
||||||
# We do not `yarn vscode` to make test.sh faster.
|
# We do not `yarn vscode` to make fmt.sh faster.
|
||||||
# If the patch fails to apply, then it's likely already applied
|
# If the patch fails to apply, then it's likely already applied
|
||||||
yarn vscode:patch &> /dev/null || true
|
yarn vscode:patch &> /dev/null || true
|
||||||
|
|
||||||
|
|||||||
@@ -7,9 +7,8 @@ main() {
|
|||||||
yarn --frozen-lockfile
|
yarn --frozen-lockfile
|
||||||
|
|
||||||
git submodule update --init
|
git submodule update --init
|
||||||
# We do not `yarn vscode` to make test.sh faster.
|
# We need to fetch VS Code's deps for lint dependencies.
|
||||||
# If the patch fails to apply, then it's likely already applied
|
yarn vscode
|
||||||
yarn vscode:patch &> /dev/null || true
|
|
||||||
|
|
||||||
yarn lint
|
yarn lint
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
|
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
|
||||||
# Install
|
# Install
|
||||||
|
|
||||||
|
- [Upgrading](#upgrading)
|
||||||
- [install.sh](#installsh)
|
- [install.sh](#installsh)
|
||||||
- [Flags](#flags)
|
- [Flags](#flags)
|
||||||
- [Detection Reference](#detection-reference)
|
- [Detection Reference](#detection-reference)
|
||||||
@@ -19,6 +20,12 @@
|
|||||||
This document demonstrates how to install `code-server` on
|
This document demonstrates how to install `code-server` on
|
||||||
various distros and operating systems.
|
various distros and operating systems.
|
||||||
|
|
||||||
|
## Upgrading
|
||||||
|
|
||||||
|
When upgrading you can just install the new version over the old one. code-server
|
||||||
|
maintains all user data in `~/.local/share/code-server` so that it is preserved in between
|
||||||
|
installations.
|
||||||
|
|
||||||
## install.sh
|
## install.sh
|
||||||
|
|
||||||
We have a [script](../install.sh) to install code-server for Linux, macOS and FreeBSD.
|
We have a [script](../install.sh) to install code-server for Linux, macOS and FreeBSD.
|
||||||
@@ -80,8 +87,8 @@ commands presented in the rest of this document.
|
|||||||
## Debian, Ubuntu
|
## Debian, Ubuntu
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -fOL https://github.com/cdr/code-server/releases/download/v3.7.0/code-server_3.7.0_amd64.deb
|
curl -fOL https://github.com/cdr/code-server/releases/download/v3.7.4/code-server_3.7.4_amd64.deb
|
||||||
sudo dpkg -i code-server_3.7.0_amd64.deb
|
sudo dpkg -i code-server_3.7.4_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
|
||||||
```
|
```
|
||||||
@@ -89,8 +96,8 @@ sudo systemctl enable --now code-server@$USER
|
|||||||
## Fedora, CentOS, RHEL, SUSE
|
## Fedora, CentOS, RHEL, SUSE
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -fOL https://github.com/cdr/code-server/releases/download/v3.7.0/code-server-3.7.0-amd64.rpm
|
curl -fOL https://github.com/cdr/code-server/releases/download/v3.7.4/code-server-3.7.4-amd64.rpm
|
||||||
sudo rpm -i code-server-3.7.0-amd64.rpm
|
sudo rpm -i code-server-3.7.4-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
|
||||||
```
|
```
|
||||||
@@ -159,10 +166,10 @@ Here is an example script for installing and using a standalone `code-server` re
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
mkdir -p ~/.local/lib ~/.local/bin
|
mkdir -p ~/.local/lib ~/.local/bin
|
||||||
curl -fL https://github.com/cdr/code-server/releases/download/v3.7.0/code-server-3.7.0-linux-amd64.tar.gz \
|
curl -fL https://github.com/cdr/code-server/releases/download/v3.7.4/code-server-3.7.4-linux-amd64.tar.gz \
|
||||||
| tar -C ~/.local/lib -xz
|
| tar -C ~/.local/lib -xz
|
||||||
mv ~/.local/lib/code-server-3.7.0-linux-amd64 ~/.local/lib/code-server-3.7.0
|
mv ~/.local/lib/code-server-3.7.4-linux-amd64 ~/.local/lib/code-server-3.7.4
|
||||||
ln -s ~/.local/lib/code-server-3.7.0/bin/code-server ~/.local/bin/code-server
|
ln -s ~/.local/lib/code-server-3.7.4/bin/code-server ~/.local/bin/code-server
|
||||||
PATH="~/.local/bin:$PATH"
|
PATH="~/.local/bin:$PATH"
|
||||||
code-server
|
code-server
|
||||||
# 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
|
||||||
|
|||||||
@@ -2,14 +2,11 @@
|
|||||||
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
|
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
|
||||||
# iPad
|
# iPad
|
||||||
|
|
||||||
- [iPad](#ipad)
|
|
||||||
- [Known Issues](#known-issues)
|
- [Known Issues](#known-issues)
|
||||||
- [How to access code-server with a self signed certificate on iPad?](#how-to-access-code-server-with-a-self-signed-certificate-on-ipad)
|
- [How to access code-server with a self signed certificate on iPad?](#how-to-access-code-server-with-a-self-signed-certificate-on-ipad)
|
||||||
|
|
||||||
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
||||||
|
|
||||||
# iPad
|
|
||||||
|
|
||||||
## Known Issues
|
## Known Issues
|
||||||
|
|
||||||
- Getting self signed certificates certificates to work is involved, see below.
|
- Getting self signed certificates certificates to work is involved, see below.
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "code-server",
|
"name": "code-server",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"version": "3.7.0",
|
"version": "3.7.4",
|
||||||
"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/cdr/code-server",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
@@ -26,7 +26,8 @@
|
|||||||
"lint": "./ci/dev/lint.sh",
|
"lint": "./ci/dev/lint.sh",
|
||||||
"test": "./ci/dev/test.sh",
|
"test": "./ci/dev/test.sh",
|
||||||
"ci": "./ci/dev/ci.sh",
|
"ci": "./ci/dev/ci.sh",
|
||||||
"watch": "VSCODE_IPC_HOOK_CLI= NODE_OPTIONS=--max_old_space_size=32384 ts-node ./ci/dev/watch.ts"
|
"watch": "VSCODE_IPC_HOOK_CLI= NODE_OPTIONS=--max_old_space_size=32384 ts-node ./ci/dev/watch.ts",
|
||||||
|
"icons": "./ci/dev/gen_icons.sh"
|
||||||
},
|
},
|
||||||
"main": "out/node/entry.js",
|
"main": "out/node/entry.js",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 12 KiB |
1
src/browser/media/favicon.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 2250 2250" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;"><path d="M2029.18,672.912c-0,-249.515 -202.574,-452.089 -452.089,-452.089l-904.176,0c-249.515,0 -452.089,202.574 -452.089,452.089l0,904.176c0,249.515 202.574,452.089 452.089,452.089l904.176,-0c249.515,-0 452.089,-202.574 452.089,-452.089l-0,-904.176Z" style="fill:#fff;"/><path d="M1748.89,1058.72c-28.26,-0 -47.092,-16.57 -47.092,-50.58l0,-195.345c0,-124.707 -51.376,-193.601 -184.095,-193.601l-61.651,0l0,131.683l18.839,-0c52.23,-0 77.061,28.779 77.061,80.23l0,172.672c0,74.998 22.262,105.521 71.07,121.218c-48.808,14.827 -71.07,46.22 -71.07,121.218l0,128.197c0,35.753 0,70.636 -9.418,106.39c-9.418,33.14 -24.831,64.534 -46.237,91.567c-11.987,15.701 -25.688,28.78 -41.098,40.991l-0,17.44l61.647,-0c132.72,-0 184.097,-68.895 184.097,-193.601l-0,-195.345c-0,-34.883 17.975,-50.58 47.091,-50.58l35.108,0l-0,-131.684l-34.252,0l0,-0.87Z" style="fill-rule:nonzero;"/><path d="M1329.33,818.057l-190.087,-0c-4.282,-0 -7.705,-3.489 -7.705,-7.849l0,-14.824c0,-4.362 3.423,-7.849 7.705,-7.849l190.943,-0c4.28,-0 7.705,3.487 7.705,7.849l0,14.824c0,4.36 -4.282,7.849 -8.561,7.849Z" style="fill-rule:nonzero;"/><path d="M1361.87,1006.42l-138.711,-0c-4.282,-0 -7.708,-3.491 -7.708,-7.851l-0,-14.824c-0,-4.359 3.426,-7.849 7.708,-7.849l138.711,0c4.283,0 7.705,3.49 7.705,7.849l0,14.824c0,3.49 -3.422,7.851 -7.705,7.851Z" style="fill-rule:nonzero;"/><path d="M1416.67,912.236l-277.423,0c-4.282,0 -7.705,-3.487 -7.705,-7.848l0,-14.826c0,-4.36 3.423,-7.848 7.705,-7.848l276.567,0c4.282,0 7.707,3.488 7.707,7.848l0,14.826c0,3.488 -2.569,7.848 -6.851,7.848Z" style="fill-rule:nonzero;"/><path d="M919.188,860.762c18.837,0 37.676,1.745 55.657,6.105l-0,-35.757c-0,-50.58 25.687,-80.23 77.063,-80.23l18.837,-0l-0,-131.683l-61.651,0c-132.72,0 -184.093,68.894 -184.093,193.601l0,64.532c29.967,-10.463 61.651,-16.568 94.187,-16.568Z" style="fill-rule:nonzero;"/><path d="M1474.9,1335.15c-13.701,-110.754 -97.614,-203.194 -205.501,-224.124c-29.967,-6.103 -59.938,-6.978 -89.049,-1.744c-0.856,-0 -0.856,-0.873 -1.712,-0.873c-47.094,-100.288 -148.13,-166.566 -257.731,-166.566c-109.6,-0 -209.78,64.535 -257.731,164.823c-0.856,-0 -0.856,0.872 -1.712,0.872c-30.824,-3.49 -61.65,-1.747 -92.475,6.104c-106.174,26.16 -186.662,116.857 -201.218,226.738c-1.712,11.337 -2.569,22.673 -2.569,33.141c0,33.136 22.263,63.659 54.8,68.02c40.244,6.106 75.35,-25.29 74.494,-65.404c-0,-6.106 -0,-13.084 0.856,-19.187c6.85,-55.814 48.806,-102.904 103.605,-115.987c17.126,-4.361 34.251,-5.231 50.519,-2.614c52.232,6.977 103.606,-20.059 125.869,-67.149c16.27,-34.884 41.957,-65.409 76.207,-81.978c37.672,-18.314 80.487,-20.927 119.876,-6.974c41.097,14.824 71.921,46.218 90.76,85.461c19.693,38.374 29.111,65.407 71.069,70.64c17.124,2.614 65.074,1.743 83.056,0.871c35.106,-0 70.213,12.209 95.044,37.499c16.266,17.441 28.254,39.244 33.393,63.661c7.705,39.244 -1.713,78.486 -24.832,108.137c-16.27,20.93 -38.532,36.626 -63.363,43.604c-11.987,3.49 -23.975,4.36 -35.962,4.36l-189.232,0c-37.672,0 -67.642,-30.52 -67.642,-68.894l-0,-255.519c-0,-10.462 -8.561,-19.182 -18.837,-19.182l-26.544,-0c-52.233,0.87 -94.187,60.173 -94.187,122.96l-0,229.358c-0,68.021 53.942,122.961 120.731,122.961c0,0 297.119,-0.874 301.399,-0.874c68.499,-6.976 131.863,-42.73 174.673,-97.671c42.814,-53.196 62.507,-122.963 53.946,-194.47Z" style="fill-rule:nonzero;"/></svg>
|
||||||
|
After Width: | Height: | Size: 3.6 KiB |
@@ -6,31 +6,11 @@
|
|||||||
"background-color": "#fff",
|
"background-color": "#fff",
|
||||||
"description": "Run editors on a remote server.",
|
"description": "Run editors on a remote server.",
|
||||||
"icons": [
|
"icons": [
|
||||||
{
|
|
||||||
"src": "{{CS_STATIC_BASE}}/src/browser/media/pwa-icon-96.png",
|
|
||||||
"type": "image/png",
|
|
||||||
"sizes": "96x96"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"src": "{{CS_STATIC_BASE}}/src/browser/media/pwa-icon-128.png",
|
|
||||||
"type": "image/png",
|
|
||||||
"sizes": "128x128"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"src": "{{CS_STATIC_BASE}}/src/browser/media/pwa-icon-192.png",
|
"src": "{{CS_STATIC_BASE}}/src/browser/media/pwa-icon-192.png",
|
||||||
"type": "image/png",
|
"type": "image/png",
|
||||||
"sizes": "192x192"
|
"sizes": "192x192"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"src": "{{CS_STATIC_BASE}}/src/browser/media/pwa-icon-256.png",
|
|
||||||
"type": "image/png",
|
|
||||||
"sizes": "256x256"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"src": "{{CS_STATIC_BASE}}/src/browser/media/pwa-icon-384.png",
|
|
||||||
"type": "image/png",
|
|
||||||
"sizes": "384x384"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"src": "{{CS_STATIC_BASE}}/src/browser/media/pwa-icon-512.png",
|
"src": "{{CS_STATIC_BASE}}/src/browser/media/pwa-icon-512.png",
|
||||||
"type": "image/png",
|
"type": "image/png",
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 9.3 KiB |
|
Before Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 86 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 8.2 KiB |
@@ -11,9 +11,11 @@
|
|||||||
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.ico" type="image/x-icon" />
|
<link rel="icon" href="{{CS_STATIC_BASE}}/src/browser/media/favicon.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" />
|
<link rel="manifest" href="{{CS_STATIC_BASE}}/src/browser/media/manifest.json" crossorigin="use-credentials" />
|
||||||
<link rel="apple-touch-icon" href="{{CS_STATIC_BASE}}/src/browser/media/pwa-icon-384.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 href="{{CS_STATIC_BASE}}/dist/register.css" rel="stylesheet" />
|
<link href="{{CS_STATIC_BASE}}/dist/register.css" rel="stylesheet" />
|
||||||
<meta id="coder-options" data-settings="{{OPTIONS}}" />
|
<meta id="coder-options" data-settings="{{OPTIONS}}" />
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
@@ -37,3 +37,7 @@ body {
|
|||||||
.login-form > .field > .submit {
|
.login-form > .field > .submit {
|
||||||
margin-left: 20px;
|
margin-left: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,9 +11,11 @@
|
|||||||
content="style-src 'self'; script-src 'self' 'unsafe-inline'; manifest-src 'self'; img-src 'self' data:; font-src 'self' data:;"
|
content="style-src 'self'; script-src 'self' 'unsafe-inline'; manifest-src 'self'; img-src 'self' data:; font-src 'self' data:;"
|
||||||
/>
|
/>
|
||||||
<title>code-server login</title>
|
<title>code-server login</title>
|
||||||
<link rel="icon" href="{{CS_STATIC_BASE}}/src/browser/media/favicon.ico" type="image/x-icon" />
|
<link rel="icon" href="{{CS_STATIC_BASE}}/src/browser/media/favicon.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" />
|
<link rel="manifest" href="{{CS_STATIC_BASE}}/src/browser/media/manifest.json" crossorigin="use-credentials" />
|
||||||
<link rel="apple-touch-icon" href="{{CS_STATIC_BASE}}/src/browser/media/pwa-icon-384.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 href="{{CS_STATIC_BASE}}/dist/register.css" rel="stylesheet" />
|
<link href="{{CS_STATIC_BASE}}/dist/register.css" rel="stylesheet" />
|
||||||
<meta id="coder-options" data-settings="{{OPTIONS}}" />
|
<meta id="coder-options" data-settings="{{OPTIONS}}" />
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
@@ -24,19 +24,16 @@
|
|||||||
<meta id="vscode-remote-nls-configuration" data-settings="{{NLS_CONFIGURATION}}" />
|
<meta id="vscode-remote-nls-configuration" data-settings="{{NLS_CONFIGURATION}}" />
|
||||||
|
|
||||||
<!-- Workbench Icon/Manifest/CSS -->
|
<!-- Workbench Icon/Manifest/CSS -->
|
||||||
<link rel="icon" href="{{CS_STATIC_BASE}}/src/browser/media/favicon.ico" type="image/x-icon" />
|
<link rel="icon" href="{{CS_STATIC_BASE}}/src/browser/media/favicon.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" />
|
<link rel="manifest" href="{{CS_STATIC_BASE}}/src/browser/media/manifest.json" crossorigin="use-credentials" />
|
||||||
<!-- PROD_ONLY
|
<!-- PROD_ONLY
|
||||||
<link data-name="vs/workbench/workbench.web.api" rel="stylesheet" href="{{CS_STATIC_BASE}}/lib/vscode/out/vs/workbench/workbench.web.api.css">
|
<link data-name="vs/workbench/workbench.web.api" rel="stylesheet" href="{{CS_STATIC_BASE}}/lib/vscode/out/vs/workbench/workbench.web.api.css">
|
||||||
END_PROD_ONLY -->
|
END_PROD_ONLY -->
|
||||||
<link rel="apple-touch-icon" href="{{CS_STATIC_BASE}}/src/browser/media/pwa-icon-384.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" />
|
||||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||||
|
|
||||||
<!-- Prefetch to avoid waterfall -->
|
|
||||||
<!-- PROD_ONLY
|
|
||||||
<link rel="prefetch" href="{{CS_STATIC_BASE}}/lib/vscode/node_modules/semver-umd/lib/semver-umd.js">
|
|
||||||
END_PROD_ONLY -->
|
|
||||||
|
|
||||||
<meta id="coder-options" data-settings="{{OPTIONS}}" />
|
<meta id="coder-options" data-settings="{{OPTIONS}}" />
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
|||||||
@@ -41,7 +41,6 @@ try {
|
|||||||
"xterm-addon-search": `../node_modules/xterm-addon-search/lib/xterm-addon-search.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-unicode11": `../node_modules/xterm-addon-unicode11/lib/xterm-addon-unicode11.js`,
|
||||||
"xterm-addon-webgl": `../node_modules/xterm-addon-webgl/lib/xterm-addon-webgl.js`,
|
"xterm-addon-webgl": `../node_modules/xterm-addon-webgl/lib/xterm-addon-webgl.js`,
|
||||||
"semver-umd": `../node_modules/semver-umd/lib/semver-umd.js`,
|
|
||||||
"tas-client-umd": `../node_modules/tas-client-umd/lib/tas-client-umd.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`,
|
"iconv-lite-umd": `../node_modules/iconv-lite-umd/lib/iconv-lite-umd.js`,
|
||||||
jschardet: `../node_modules/jschardet/dist/jschardet.min.js`,
|
jschardet: `../node_modules/jschardet/dist/jschardet.min.js`,
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ export interface Args extends VsArgs {
|
|||||||
"cert-host"?: string
|
"cert-host"?: string
|
||||||
"cert-key"?: string
|
"cert-key"?: string
|
||||||
"disable-telemetry"?: boolean
|
"disable-telemetry"?: boolean
|
||||||
|
"disable-update-check"?: boolean
|
||||||
help?: boolean
|
help?: boolean
|
||||||
host?: string
|
host?: string
|
||||||
json?: boolean
|
json?: boolean
|
||||||
@@ -114,6 +115,12 @@ const options: Options<Required<Args>> = {
|
|||||||
},
|
},
|
||||||
"cert-key": { type: "string", path: true, description: "Path to certificate key when using non-generated cert." },
|
"cert-key": { type: "string", path: true, description: "Path to certificate key when using non-generated cert." },
|
||||||
"disable-telemetry": { type: "boolean", description: "Disable telemetry." },
|
"disable-telemetry": { type: "boolean", description: "Disable telemetry." },
|
||||||
|
"disable-update-check": {
|
||||||
|
type: "boolean",
|
||||||
|
description:
|
||||||
|
"Disable update check. Without this flag, code-server checks every 6 hours against the latest github release and \n" +
|
||||||
|
"then notifies you once every week that a new release is available.",
|
||||||
|
},
|
||||||
help: { type: "boolean", short: "h", description: "Show this output." },
|
help: { type: "boolean", short: "h", description: "Show this output." },
|
||||||
json: { type: "boolean" },
|
json: { type: "boolean" },
|
||||||
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." },
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ 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, open } from "./util"
|
||||||
import { ipcMain, WrapperProcess } from "./wrapper"
|
import { isChild, wrapper } from "./wrapper"
|
||||||
|
|
||||||
export const runVsCodeCli = (args: DefaultedArgs): void => {
|
export const runVsCodeCli = (args: DefaultedArgs): void => {
|
||||||
logger.debug("forking vs code cli...")
|
logger.debug("forking vs code cli...")
|
||||||
@@ -121,7 +121,7 @@ const main = async (args: DefaultedArgs): Promise<void> => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (args.cert) {
|
if (args.cert) {
|
||||||
logger.info(" - Using certificate for HTTPS: ${humanPath(args.cert.value)}")
|
logger.info(` - Using certificate for HTTPS: ${humanPath(args.cert.value)}`)
|
||||||
} else {
|
} else {
|
||||||
logger.info(" - Not serving HTTPS")
|
logger.info(" - Not serving HTTPS")
|
||||||
}
|
}
|
||||||
@@ -137,7 +137,7 @@ const main = async (args: DefaultedArgs): Promise<void> => {
|
|||||||
logger.info(" - Connected to cloud agent")
|
logger.info(" - Connected to cloud agent")
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error(err.message)
|
logger.error(err.message)
|
||||||
ipcMain.exit(1)
|
wrapper.exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,19 +154,22 @@ const main = async (args: DefaultedArgs): Promise<void> => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function entry(): Promise<void> {
|
async function entry(): Promise<void> {
|
||||||
|
// 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
|
||||||
|
// the parent and the child wouldn't have been spawned. We also get the
|
||||||
|
// arguments from the parent so we don't have to parse twice and to account
|
||||||
|
// for environment manipulation (like how PASSWORD gets removed to avoid
|
||||||
|
// leaking to child processes).
|
||||||
|
if (isChild(wrapper)) {
|
||||||
|
const args = await wrapper.handshake()
|
||||||
|
wrapper.preventExit()
|
||||||
|
return main(args)
|
||||||
|
}
|
||||||
|
|
||||||
const cliArgs = parse(process.argv.slice(2))
|
const cliArgs = parse(process.argv.slice(2))
|
||||||
const configArgs = await readConfigFile(cliArgs.config)
|
const configArgs = await readConfigFile(cliArgs.config)
|
||||||
const args = await setDefaults(cliArgs, configArgs)
|
const args = await setDefaults(cliArgs, configArgs)
|
||||||
|
|
||||||
// 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
|
|
||||||
// the parent and the child wouldn't have been spawned.
|
|
||||||
if (ipcMain.isChild) {
|
|
||||||
await ipcMain.handshake()
|
|
||||||
ipcMain.preventExit()
|
|
||||||
return main(args)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (args.help) {
|
if (args.help) {
|
||||||
console.log("code-server", version, commit)
|
console.log("code-server", version, commit)
|
||||||
console.log("")
|
console.log("")
|
||||||
@@ -201,11 +204,10 @@ async function entry(): Promise<void> {
|
|||||||
return openInExistingInstance(args, socketPath)
|
return openInExistingInstance(args, socketPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
const wrapper = new WrapperProcess(require("../../package.json").version)
|
return wrapper.start(args)
|
||||||
return wrapper.start()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
entry().catch((error) => {
|
entry().catch((error) => {
|
||||||
logger.error(error.message)
|
logger.error(error.message)
|
||||||
ipcMain.exit(error)
|
wrapper.exit(error)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -66,7 +66,11 @@ export const register = async (
|
|||||||
app.use(bodyParser.urlencoded({ extended: true }))
|
app.use(bodyParser.urlencoded({ extended: true }))
|
||||||
|
|
||||||
const common: express.RequestHandler = (req, _, next) => {
|
const common: express.RequestHandler = (req, _, next) => {
|
||||||
|
// /healthz|/healthz/ needs to be excluded otherwise health checks will make
|
||||||
|
// it look like code-server is always in use.
|
||||||
|
if (!/^\/healthz\/?$/.test(req.url)) {
|
||||||
heart.beat()
|
heart.beat()
|
||||||
|
}
|
||||||
|
|
||||||
// Add common variables routes can use.
|
// Add common variables routes can use.
|
||||||
req.args = args
|
req.args = args
|
||||||
|
|||||||
@@ -7,13 +7,33 @@ import * as tarFs from "tar-fs"
|
|||||||
import * as zlib from "zlib"
|
import * as zlib from "zlib"
|
||||||
import { HttpCode, HttpError } from "../../common/http"
|
import { HttpCode, HttpError } from "../../common/http"
|
||||||
import { rootPath } from "../constants"
|
import { rootPath } from "../constants"
|
||||||
import { authenticated, replaceTemplates } from "../http"
|
import { authenticated, ensureAuthenticated, replaceTemplates } from "../http"
|
||||||
import { getMediaMime, pathToFsPath } from "../util"
|
import { getMediaMime, pathToFsPath } from "../util"
|
||||||
|
|
||||||
export const router = Router()
|
export const router = Router()
|
||||||
|
|
||||||
// The commit is for caching.
|
// The commit is for caching.
|
||||||
router.get("/(:commit)(/*)?", async (req, res) => {
|
router.get("/(:commit)(/*)?", async (req, res) => {
|
||||||
|
// Used by VS Code to load extensions into the web worker.
|
||||||
|
const tar = Array.isArray(req.query.tar) ? req.query.tar[0] : req.query.tar
|
||||||
|
if (typeof tar === "string") {
|
||||||
|
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]) {
|
if (!req.params[0]) {
|
||||||
throw new HttpError("Not Found", HttpCode.NotFound)
|
throw new HttpError("Not Found", HttpCode.NotFound)
|
||||||
}
|
}
|
||||||
@@ -32,24 +52,9 @@ router.get("/(:commit)(/*)?", async (req, res) => {
|
|||||||
res.header("Cache-Control", "public, max-age=31536000")
|
res.header("Cache-Control", "public, max-age=31536000")
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Without this the default is to use the directory the script loaded from.
|
||||||
* Used by VS Code to load extensions into the web worker.
|
if (req.headers["service-worker"]) {
|
||||||
*/
|
res.header("service-worker-allowed", "/")
|
||||||
const tar = Array.isArray(req.query.tar) ? req.query.tar[0] : req.query.tar
|
|
||||||
if (typeof tar === "string") {
|
|
||||||
let stream: Readable = tarFs.pack(pathToFsPath(tar))
|
|
||||||
if (req.headers["accept-encoding"] && req.headers["accept-encoding"].includes("gzip")) {
|
|
||||||
logger.debug("gzipping tar", field("path", resourcePath))
|
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
res.set("Content-Type", getMediaMime(resourcePath))
|
res.set("Content-Type", getMediaMime(resourcePath))
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ export const router = Router()
|
|||||||
|
|
||||||
const provider = new UpdateProvider()
|
const provider = new UpdateProvider()
|
||||||
|
|
||||||
router.get("/", ensureAuthenticated, async (req, res) => {
|
router.get("/check", ensureAuthenticated, async (req, res) => {
|
||||||
const update = await provider.getUpdate(req.query.force === "true")
|
const update = await provider.getUpdate(req.query.force === "true")
|
||||||
res.json({
|
res.json({
|
||||||
checked: update.checked,
|
checked: update.checked,
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ router.get("/", async (req, res) => {
|
|||||||
commit !== "development" ? content.replace(/<!-- PROD_ONLY/g, "").replace(/END_PROD_ONLY -->/g, "") : content,
|
commit !== "development" ? content.replace(/<!-- PROD_ONLY/g, "").replace(/END_PROD_ONLY -->/g, "") : content,
|
||||||
{
|
{
|
||||||
disableTelemetry: !!req.args["disable-telemetry"],
|
disableTelemetry: !!req.args["disable-telemetry"],
|
||||||
|
disableUpdateCheck: !!req.args["disable-update-check"],
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.replace(`"{{REMOTE_USER_DATA_URI}}"`, `'${JSON.stringify(options.remoteUserDataUri)}'`)
|
.replace(`"{{REMOTE_USER_DATA_URI}}"`, `'${JSON.stringify(options.remoteUserDataUri)}'`)
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ export class UpdateProvider {
|
|||||||
public isLatestVersion(latest: Update): boolean {
|
public isLatestVersion(latest: Update): boolean {
|
||||||
logger.debug("comparing versions", field("current", version), field("latest", latest.version))
|
logger.debug("comparing versions", field("current", version), field("latest", latest.version))
|
||||||
try {
|
try {
|
||||||
return latest.version === version || semver.lt(latest.version, version)
|
return semver.lte(latest.version, version)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { field, logger } from "@coder/logger"
|
import { logger } from "@coder/logger"
|
||||||
import * as cp from "child_process"
|
import * as cp from "child_process"
|
||||||
import * as net from "net"
|
import * as net from "net"
|
||||||
import * as path from "path"
|
import * as path from "path"
|
||||||
@@ -8,19 +8,18 @@ import { rootPath } from "./constants"
|
|||||||
import { settings } from "./settings"
|
import { settings } from "./settings"
|
||||||
import { SocketProxyProvider } from "./socket"
|
import { SocketProxyProvider } from "./socket"
|
||||||
import { isFile } from "./util"
|
import { isFile } from "./util"
|
||||||
import { ipcMain } from "./wrapper"
|
import { onMessage, wrapper } from "./wrapper"
|
||||||
|
|
||||||
export class VscodeProvider {
|
export class VscodeProvider {
|
||||||
public readonly serverRootPath: string
|
public readonly serverRootPath: string
|
||||||
public readonly vsRootPath: string
|
public readonly vsRootPath: string
|
||||||
private _vscode?: Promise<cp.ChildProcess>
|
private _vscode?: Promise<cp.ChildProcess>
|
||||||
private timeoutInterval = 10000 // 10s, matches VS Code's timeouts.
|
|
||||||
private readonly socketProvider = new SocketProxyProvider()
|
private readonly socketProvider = new SocketProxyProvider()
|
||||||
|
|
||||||
public constructor() {
|
public constructor() {
|
||||||
this.vsRootPath = path.resolve(rootPath, "lib/vscode")
|
this.vsRootPath = path.resolve(rootPath, "lib/vscode")
|
||||||
this.serverRootPath = path.join(this.vsRootPath, "out/vs/server")
|
this.serverRootPath = path.join(this.vsRootPath, "out/vs/server")
|
||||||
ipcMain.onDispose(() => this.dispose())
|
wrapper.onDispose(() => this.dispose())
|
||||||
}
|
}
|
||||||
|
|
||||||
public async dispose(): Promise<void> {
|
public async dispose(): Promise<void> {
|
||||||
@@ -69,10 +68,13 @@ export class VscodeProvider {
|
|||||||
vscode,
|
vscode,
|
||||||
)
|
)
|
||||||
|
|
||||||
const message = await this.onMessage(vscode, (message): message is ipc.OptionsMessage => {
|
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.
|
// There can be parallel initializations so wait for the right ID.
|
||||||
return message.type === "options" && message.id === id
|
return message.type === "options" && message.id === id
|
||||||
})
|
},
|
||||||
|
)
|
||||||
|
|
||||||
return message.options
|
return message.options
|
||||||
}
|
}
|
||||||
@@ -104,61 +106,13 @@ export class VscodeProvider {
|
|||||||
dispose()
|
dispose()
|
||||||
})
|
})
|
||||||
|
|
||||||
this._vscode = this.onMessage(vscode, (message): message is ipc.ReadyMessage => {
|
this._vscode = onMessage<ipc.VscodeMessage, ipc.ReadyMessage>(vscode, (message): message is ipc.ReadyMessage => {
|
||||||
return message.type === "ready"
|
return message.type === "ready"
|
||||||
}).then(() => vscode)
|
}).then(() => vscode)
|
||||||
|
|
||||||
return this._vscode
|
return this._vscode
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Listen to a single message from a process. Reject if the process errors,
|
|
||||||
* exits, or times out.
|
|
||||||
*
|
|
||||||
* `fn` is a function that determines whether the message is the one we're
|
|
||||||
* waiting for.
|
|
||||||
*/
|
|
||||||
private onMessage<T extends ipc.VscodeMessage>(
|
|
||||||
proc: cp.ChildProcess,
|
|
||||||
fn: (message: ipc.VscodeMessage) => message is T,
|
|
||||||
): Promise<T> {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const cleanup = () => {
|
|
||||||
proc.off("error", onError)
|
|
||||||
proc.off("exit", onExit)
|
|
||||||
proc.off("message", onMessage)
|
|
||||||
clearTimeout(timeout)
|
|
||||||
}
|
|
||||||
|
|
||||||
const timeout = setTimeout(() => {
|
|
||||||
cleanup()
|
|
||||||
reject(new Error("timed out"))
|
|
||||||
}, this.timeoutInterval)
|
|
||||||
|
|
||||||
const onError = (error: Error) => {
|
|
||||||
cleanup()
|
|
||||||
reject(error)
|
|
||||||
}
|
|
||||||
|
|
||||||
const onExit = (code: number | null) => {
|
|
||||||
cleanup()
|
|
||||||
reject(new Error(`VS Code exited unexpectedly with code ${code}`))
|
|
||||||
}
|
|
||||||
|
|
||||||
const onMessage = (message: ipc.VscodeMessage) => {
|
|
||||||
logger.trace("got message from vscode", field("message", message))
|
|
||||||
if (fn(message)) {
|
|
||||||
cleanup()
|
|
||||||
resolve(message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
proc.on("message", onMessage)
|
|
||||||
proc.on("error", onError)
|
|
||||||
proc.on("exit", onExit)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* VS Code expects a raw socket. It will handle all the web socket frames.
|
* VS Code expects a raw socket. It will handle all the web socket frames.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,11 +1,70 @@
|
|||||||
import { field, logger } from "@coder/logger"
|
import { field, Logger, logger } from "@coder/logger"
|
||||||
import * as cp from "child_process"
|
import * as cp from "child_process"
|
||||||
import * as path from "path"
|
import * as path from "path"
|
||||||
import * as rfs from "rotating-file-stream"
|
import * as rfs from "rotating-file-stream"
|
||||||
import { Emitter } from "../common/emitter"
|
import { Emitter } from "../common/emitter"
|
||||||
|
import { DefaultedArgs } from "./cli"
|
||||||
import { paths } from "./util"
|
import { paths } from "./util"
|
||||||
|
|
||||||
interface HandshakeMessage {
|
const timeoutInterval = 10000 // 10s, matches VS Code's timeouts.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listen to a single message from a process. Reject if the process errors,
|
||||||
|
* exits, or times out.
|
||||||
|
*
|
||||||
|
* `fn` is a function that determines whether the message is the one we're
|
||||||
|
* waiting for.
|
||||||
|
*/
|
||||||
|
export function onMessage<M, T extends M>(
|
||||||
|
proc: cp.ChildProcess | NodeJS.Process,
|
||||||
|
fn: (message: M) => message is T,
|
||||||
|
customLogger?: Logger,
|
||||||
|
): Promise<T> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const cleanup = () => {
|
||||||
|
proc.off("error", onError)
|
||||||
|
proc.off("exit", onExit)
|
||||||
|
proc.off("message", onMessage)
|
||||||
|
clearTimeout(timeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
const timeout = setTimeout(() => {
|
||||||
|
cleanup()
|
||||||
|
reject(new Error("timed out"))
|
||||||
|
}, timeoutInterval)
|
||||||
|
|
||||||
|
const onError = (error: Error) => {
|
||||||
|
cleanup()
|
||||||
|
reject(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onExit = (code: number) => {
|
||||||
|
cleanup()
|
||||||
|
reject(new Error(`exited unexpectedly with code ${code}`))
|
||||||
|
}
|
||||||
|
|
||||||
|
const onMessage = (message: M) => {
|
||||||
|
;(customLogger || logger).trace("got message", field("message", message))
|
||||||
|
if (fn(message)) {
|
||||||
|
cleanup()
|
||||||
|
resolve(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
proc.on("message", onMessage)
|
||||||
|
// NodeJS.Process doesn't have `error` but binding anyway shouldn't break
|
||||||
|
// anything. It does have `exit` but the types aren't working.
|
||||||
|
;(proc as cp.ChildProcess).on("error", onError)
|
||||||
|
;(proc as cp.ChildProcess).on("exit", onExit)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ParentHandshakeMessage {
|
||||||
|
type: "handshake"
|
||||||
|
args: DefaultedArgs
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ChildHandshakeMessage {
|
||||||
type: "handshake"
|
type: "handshake"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -14,9 +73,10 @@ interface RelaunchMessage {
|
|||||||
version: string
|
version: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Message = RelaunchMessage | HandshakeMessage
|
type ChildMessage = RelaunchMessage | ChildHandshakeMessage
|
||||||
|
type ParentMessage = ParentHandshakeMessage
|
||||||
|
|
||||||
export class ProcessError extends Error {
|
class ProcessError extends Error {
|
||||||
public constructor(message: string, public readonly code: number | undefined) {
|
public constructor(message: string, public readonly code: number | undefined) {
|
||||||
super(message)
|
super(message)
|
||||||
this.name = this.constructor.name
|
this.name = this.constructor.name
|
||||||
@@ -25,16 +85,26 @@ export class ProcessError extends Error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allows the wrapper and inner processes to communicate.
|
* Wrapper around a process that tries to gracefully exit when a process exits
|
||||||
|
* and provides a way to prevent `process.exit`.
|
||||||
*/
|
*/
|
||||||
export class IpcMain {
|
abstract class Process {
|
||||||
private readonly _onMessage = new Emitter<Message>()
|
/**
|
||||||
public readonly onMessage = this._onMessage.event
|
* Emit this to trigger a graceful exit.
|
||||||
private readonly _onDispose = new Emitter<NodeJS.Signals | undefined>()
|
*/
|
||||||
public readonly onDispose = this._onDispose.event
|
protected readonly _onDispose = new Emitter<NodeJS.Signals | undefined>()
|
||||||
public readonly processExit: (code?: number) => never = process.exit
|
|
||||||
|
|
||||||
public constructor(private readonly parentPid?: number) {
|
/**
|
||||||
|
* Emitted when the process is about to be disposed.
|
||||||
|
*/
|
||||||
|
public readonly onDispose = this._onDispose.event
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uniquely named logger for the process.
|
||||||
|
*/
|
||||||
|
public abstract logger: Logger
|
||||||
|
|
||||||
|
public constructor() {
|
||||||
process.on("SIGINT", () => this._onDispose.emit("SIGINT"))
|
process.on("SIGINT", () => this._onDispose.emit("SIGINT"))
|
||||||
process.on("SIGTERM", () => this._onDispose.emit("SIGTERM"))
|
process.on("SIGTERM", () => this._onDispose.emit("SIGTERM"))
|
||||||
process.on("exit", () => this._onDispose.emit(undefined))
|
process.on("exit", () => this._onDispose.emit(undefined))
|
||||||
@@ -43,42 +113,27 @@ export class IpcMain {
|
|||||||
// Remove listeners to avoid possibly triggering disposal again.
|
// Remove listeners to avoid possibly triggering disposal again.
|
||||||
process.removeAllListeners()
|
process.removeAllListeners()
|
||||||
|
|
||||||
// Try waiting for other handlers run first then exit.
|
// Try waiting for other handlers to run first then exit.
|
||||||
logger.debug(`${parentPid ? "inner process" : "wrapper"} ${process.pid} disposing`, field("code", signal))
|
this.logger.debug("disposing", field("code", signal))
|
||||||
wait.then(() => this.exit(0))
|
wait.then(() => this.exit(0))
|
||||||
setTimeout(() => this.exit(0), 5000)
|
setTimeout(() => this.exit(0), 5000)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Kill the inner process if the parent dies. This is for the case where the
|
|
||||||
// parent process is forcefully terminated and cannot clean up.
|
|
||||||
if (parentPid) {
|
|
||||||
setInterval(() => {
|
|
||||||
try {
|
|
||||||
// process.kill throws an exception if the process doesn't exist.
|
|
||||||
process.kill(parentPid, 0)
|
|
||||||
} catch (_) {
|
|
||||||
// Consider this an error since it should have been able to clean up
|
|
||||||
// the child process unless it was forcefully killed.
|
|
||||||
logger.error(`parent process ${parentPid} died`)
|
|
||||||
this._onDispose.emit(undefined)
|
|
||||||
}
|
|
||||||
}, 5000)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ensure we control when the process exits.
|
* Ensure control over when the process exits.
|
||||||
*/
|
*/
|
||||||
public preventExit(): void {
|
public preventExit(): void {
|
||||||
process.exit = function (code?: number) {
|
;(process.exit as any) = (code?: number) => {
|
||||||
logger.warn(`process.exit() was prevented: ${code || "unknown code"}.`)
|
this.logger.warn(`process.exit() was prevented: ${code || "unknown code"}.`)
|
||||||
} as (code?: number) => never
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public get isChild(): boolean {
|
private readonly processExit: (code?: number) => never = process.exit
|
||||||
return typeof this.parentPid !== "undefined"
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will always exit even if normal exit is being prevented.
|
||||||
|
*/
|
||||||
public exit(error?: number | ProcessError): never {
|
public exit(error?: number | ProcessError): never {
|
||||||
if (error && typeof error !== "number") {
|
if (error && typeof error !== "number") {
|
||||||
this.processExit(typeof error.code === "number" ? error.code : 1)
|
this.processExit(typeof error.code === "number" ? error.code : 1)
|
||||||
@@ -86,48 +141,59 @@ export class IpcMain {
|
|||||||
this.processExit(error)
|
this.processExit(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public handshake(child?: cp.ChildProcess): Promise<void> {
|
/**
|
||||||
return new Promise((resolve, reject) => {
|
* Child process that will clean up after itself if the parent goes away and can
|
||||||
const target = child || process
|
* perform a handshake with the parent and ask it to relaunch.
|
||||||
const onMessage = (message: Message): void => {
|
*/
|
||||||
logger.debug(
|
class ChildProcess extends Process {
|
||||||
`${child ? "wrapper" : "inner process"} ${process.pid} received message from ${
|
public logger = logger.named(`child:${process.pid}`)
|
||||||
child ? child.pid : this.parentPid
|
|
||||||
}`,
|
public constructor(private readonly parentPid: number) {
|
||||||
field("message", message),
|
super()
|
||||||
)
|
|
||||||
if (message.type === "handshake") {
|
// Kill the inner process if the parent dies. This is for the case where the
|
||||||
target.removeListener("message", onMessage)
|
// parent process is forcefully terminated and cannot clean up.
|
||||||
target.on("message", (msg) => this._onMessage.emit(msg))
|
setInterval(() => {
|
||||||
// The wrapper responds once the inner process starts the handshake.
|
try {
|
||||||
if (child) {
|
// process.kill throws an exception if the process doesn't exist.
|
||||||
if (!target.send) {
|
process.kill(this.parentPid, 0)
|
||||||
throw new Error("child not spawned with IPC")
|
} catch (_) {
|
||||||
|
// Consider this an error since it should have been able to clean up
|
||||||
|
// the child process unless it was forcefully killed.
|
||||||
|
this.logger.error(`parent process ${parentPid} died`)
|
||||||
|
this._onDispose.emit(undefined)
|
||||||
}
|
}
|
||||||
target.send({ type: "handshake" })
|
}, 5000)
|
||||||
}
|
}
|
||||||
resolve()
|
|
||||||
}
|
/**
|
||||||
}
|
* Initiate the handshake and wait for a response from the parent.
|
||||||
target.on("message", onMessage)
|
*/
|
||||||
if (child) {
|
public async handshake(): Promise<DefaultedArgs> {
|
||||||
child.once("error", reject)
|
|
||||||
child.once("exit", (code) => {
|
|
||||||
reject(new ProcessError(`Unexpected exit with code ${code}`, code !== null ? code : undefined))
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
// The inner process initiates the handshake.
|
|
||||||
this.send({ type: "handshake" })
|
this.send({ type: "handshake" })
|
||||||
}
|
const message = await onMessage<ParentMessage, ParentHandshakeMessage>(
|
||||||
})
|
process,
|
||||||
|
(message): message is ParentHandshakeMessage => {
|
||||||
|
return message.type === "handshake"
|
||||||
|
},
|
||||||
|
this.logger,
|
||||||
|
)
|
||||||
|
return message.args
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notify the parent process that it should relaunch the child.
|
||||||
|
*/
|
||||||
public relaunch(version: string): void {
|
public relaunch(version: string): void {
|
||||||
this.send({ type: "relaunch", version })
|
this.send({ type: "relaunch", version })
|
||||||
}
|
}
|
||||||
|
|
||||||
private send(message: Message): void {
|
/**
|
||||||
|
* Send a message to the parent.
|
||||||
|
*/
|
||||||
|
private send(message: ChildMessage): void {
|
||||||
if (!process.send) {
|
if (!process.send) {
|
||||||
throw new Error("not spawned with IPC")
|
throw new Error("not spawned with IPC")
|
||||||
}
|
}
|
||||||
@@ -136,28 +202,31 @@ export class IpcMain {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Channel for communication between the child and parent processes.
|
* Parent process wrapper that spawns the child process and performs a handshake
|
||||||
|
* with it. Will relaunch the child if it receives a SIGUSR1 or is asked to by
|
||||||
|
* the child. If the child otherwise exits the parent will also exit.
|
||||||
*/
|
*/
|
||||||
export const ipcMain = new IpcMain(
|
export class ParentProcess extends Process {
|
||||||
typeof process.env.CODE_SERVER_PARENT_PID !== "undefined" ? parseInt(process.env.CODE_SERVER_PARENT_PID) : undefined,
|
public logger = logger.named(`parent:${process.pid}`)
|
||||||
)
|
|
||||||
|
|
||||||
export interface WrapperOptions {
|
private child?: cp.ChildProcess
|
||||||
maxMemory?: number
|
|
||||||
nodeOptions?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provides a way to wrap a process for the purpose of updating the running
|
|
||||||
* instance.
|
|
||||||
*/
|
|
||||||
export class WrapperProcess {
|
|
||||||
private process?: cp.ChildProcess
|
|
||||||
private started?: Promise<void>
|
private started?: Promise<void>
|
||||||
private readonly logStdoutStream: rfs.RotatingFileStream
|
private readonly logStdoutStream: rfs.RotatingFileStream
|
||||||
private readonly logStderrStream: rfs.RotatingFileStream
|
private readonly logStderrStream: rfs.RotatingFileStream
|
||||||
|
|
||||||
public constructor(private currentVersion: string, private readonly options?: WrapperOptions) {
|
protected readonly _onChildMessage = new Emitter<ChildMessage>()
|
||||||
|
protected readonly onChildMessage = this._onChildMessage.event
|
||||||
|
|
||||||
|
private args?: DefaultedArgs
|
||||||
|
|
||||||
|
public constructor(private currentVersion: string) {
|
||||||
|
super()
|
||||||
|
|
||||||
|
process.on("SIGUSR1", async () => {
|
||||||
|
this.logger.info("Received SIGUSR1; hotswapping")
|
||||||
|
this.relaunch()
|
||||||
|
})
|
||||||
|
|
||||||
const opts = {
|
const opts = {
|
||||||
size: "10M",
|
size: "10M",
|
||||||
maxFiles: 10,
|
maxFiles: 10,
|
||||||
@@ -165,19 +234,19 @@ export class WrapperProcess {
|
|||||||
this.logStdoutStream = rfs.createStream(path.join(paths.data, "coder-logs", "code-server-stdout.log"), opts)
|
this.logStdoutStream = rfs.createStream(path.join(paths.data, "coder-logs", "code-server-stdout.log"), opts)
|
||||||
this.logStderrStream = rfs.createStream(path.join(paths.data, "coder-logs", "code-server-stderr.log"), opts)
|
this.logStderrStream = rfs.createStream(path.join(paths.data, "coder-logs", "code-server-stderr.log"), opts)
|
||||||
|
|
||||||
ipcMain.onDispose(() => {
|
this.onDispose(() => {
|
||||||
this.disposeChild()
|
this.disposeChild()
|
||||||
})
|
})
|
||||||
|
|
||||||
ipcMain.onMessage((message) => {
|
this.onChildMessage((message) => {
|
||||||
switch (message.type) {
|
switch (message.type) {
|
||||||
case "relaunch":
|
case "relaunch":
|
||||||
logger.info(`Relaunching: ${this.currentVersion} -> ${message.version}`)
|
this.logger.info(`Relaunching: ${this.currentVersion} -> ${message.version}`)
|
||||||
this.currentVersion = message.version
|
this.currentVersion = message.version
|
||||||
this.relaunch()
|
this.relaunch()
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
logger.error(`Unrecognized message ${message}`)
|
this.logger.error(`Unrecognized message ${message}`)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -185,30 +254,26 @@ export class WrapperProcess {
|
|||||||
|
|
||||||
private disposeChild(): void {
|
private disposeChild(): void {
|
||||||
this.started = undefined
|
this.started = undefined
|
||||||
if (this.process) {
|
if (this.child) {
|
||||||
this.process.removeAllListeners()
|
this.child.removeAllListeners()
|
||||||
this.process.kill()
|
this.child.kill()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async relaunch(): Promise<void> {
|
private async relaunch(): Promise<void> {
|
||||||
this.disposeChild()
|
this.disposeChild()
|
||||||
try {
|
try {
|
||||||
await this.start()
|
this.started = this._start()
|
||||||
|
await this.started
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(error.message)
|
this.logger.error(error.message)
|
||||||
ipcMain.exit(typeof error.code === "number" ? error.code : 1)
|
this.exit(typeof error.code === "number" ? error.code : 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public start(): Promise<void> {
|
public start(args: DefaultedArgs): Promise<void> {
|
||||||
// If we have a process then we've already bound this.
|
// Store for relaunches.
|
||||||
if (!this.process) {
|
this.args = args
|
||||||
process.on("SIGUSR1", async () => {
|
|
||||||
logger.info("Received SIGUSR1; hotswapping")
|
|
||||||
this.relaunch()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if (!this.started) {
|
if (!this.started) {
|
||||||
this.started = this._start()
|
this.started = this._start()
|
||||||
}
|
}
|
||||||
@@ -217,7 +282,7 @@ export class WrapperProcess {
|
|||||||
|
|
||||||
private async _start(): Promise<void> {
|
private async _start(): Promise<void> {
|
||||||
const child = this.spawn()
|
const child = this.spawn()
|
||||||
this.process = child
|
this.child = child
|
||||||
|
|
||||||
// Log both to stdout and to the log directory.
|
// Log both to stdout and to the log directory.
|
||||||
if (child.stdout) {
|
if (child.stdout) {
|
||||||
@@ -229,45 +294,75 @@ export class WrapperProcess {
|
|||||||
child.stderr.pipe(process.stderr)
|
child.stderr.pipe(process.stderr)
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.debug(`spawned inner process ${child.pid}`)
|
this.logger.debug(`spawned inner process ${child.pid}`)
|
||||||
|
|
||||||
await ipcMain.handshake(child)
|
await this.handshake(child)
|
||||||
|
|
||||||
child.once("exit", (code) => {
|
child.once("exit", (code) => {
|
||||||
logger.debug(`inner process ${child.pid} exited unexpectedly`)
|
this.logger.debug(`inner process ${child.pid} exited unexpectedly`)
|
||||||
ipcMain.exit(code || 0)
|
this.exit(code || 0)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private spawn(): cp.ChildProcess {
|
private spawn(): cp.ChildProcess {
|
||||||
// Flags to pass along to the Node binary.
|
|
||||||
let nodeOptions = `${process.env.NODE_OPTIONS || ""} ${(this.options && this.options.nodeOptions) || ""}`
|
|
||||||
if (!/max_old_space_size=(\d+)/g.exec(nodeOptions)) {
|
|
||||||
nodeOptions += ` --max_old_space_size=${(this.options && this.options.maxMemory) || 2048}`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use spawn (instead of fork) to use the new binary in case it was updated.
|
// Use spawn (instead of fork) to use the new binary in case it was updated.
|
||||||
return cp.spawn(process.argv[0], process.argv.slice(1), {
|
return cp.spawn(process.argv[0], process.argv.slice(1), {
|
||||||
env: {
|
env: {
|
||||||
...process.env,
|
...process.env,
|
||||||
CODE_SERVER_PARENT_PID: process.pid.toString(),
|
CODE_SERVER_PARENT_PID: process.pid.toString(),
|
||||||
NODE_OPTIONS: nodeOptions,
|
NODE_OPTIONS: `--max-old-space-size=2048 ${process.env.NODE_OPTIONS || ""}`,
|
||||||
},
|
},
|
||||||
stdio: ["ipc"],
|
stdio: ["ipc"],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wait for a handshake from the child then reply.
|
||||||
|
*/
|
||||||
|
private async handshake(child: cp.ChildProcess): Promise<void> {
|
||||||
|
if (!this.args) {
|
||||||
|
throw new Error("started without args")
|
||||||
|
}
|
||||||
|
await onMessage<ChildMessage, ChildHandshakeMessage>(
|
||||||
|
child,
|
||||||
|
(message): message is ChildHandshakeMessage => {
|
||||||
|
return message.type === "handshake"
|
||||||
|
},
|
||||||
|
this.logger,
|
||||||
|
)
|
||||||
|
this.send(child, { type: "handshake", args: this.args })
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a message to the child.
|
||||||
|
*/
|
||||||
|
private send(child: cp.ChildProcess, message: ParentMessage): void {
|
||||||
|
child.send(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process wrapper.
|
||||||
|
*/
|
||||||
|
export const wrapper =
|
||||||
|
typeof process.env.CODE_SERVER_PARENT_PID !== "undefined"
|
||||||
|
? new ChildProcess(parseInt(process.env.CODE_SERVER_PARENT_PID))
|
||||||
|
: new ParentProcess(require("../../package.json").version)
|
||||||
|
|
||||||
|
export function isChild(proc: ChildProcess | ParentProcess): proc is ChildProcess {
|
||||||
|
return proc instanceof ChildProcess
|
||||||
}
|
}
|
||||||
|
|
||||||
// It's possible that the pipe has closed (for example if you run code-server
|
// It's possible that the pipe has closed (for example if you run code-server
|
||||||
// --version | head -1). Assume that means we're done.
|
// --version | head -1). Assume that means we're done.
|
||||||
if (!process.stdout.isTTY) {
|
if (!process.stdout.isTTY) {
|
||||||
process.stdout.on("error", () => ipcMain.exit())
|
process.stdout.on("error", () => wrapper.exit())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't let uncaught exceptions crash the process.
|
// Don't let uncaught exceptions crash the process.
|
||||||
process.on("uncaughtException", (error) => {
|
process.on("uncaughtException", (error) => {
|
||||||
logger.error(`Uncaught exception: ${error.message}`)
|
wrapper.logger.error(`Uncaught exception: ${error.message}`)
|
||||||
if (typeof error.stack !== "undefined") {
|
if (typeof error.stack !== "undefined") {
|
||||||
logger.error(error.stack)
|
wrapper.logger.error(error.stack)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||