diff --git a/vscode-web/README.md b/vscode-web/README.md index cfc092c..bc030d4 100644 --- a/vscode-web/README.md +++ b/vscode-web/README.md @@ -9,7 +9,7 @@ tags: [helper, ide, vscode, web] # VS Code Web -Automatically install [Visual Studio Code Server](https://code.visualstudio.com/docs/remote/vscode-server) in a workspace using the [VS Code CLI](https://code.visualstudio.com/docs/editor/command-line) and create an app to access it via the dashboard. +Automatically install [Visual Studio Code Server](https://code.visualstudio.com/docs/remote/vscode-server) in a workspace and create an app to access it via the dashboard. ```tf module "vscode-web" { @@ -31,8 +31,20 @@ module "vscode-web" { source = "registry.coder.com/modules/vscode-web/coder" version = "1.0.3" agent_id = coder_agent.example.id - install_dir = "/home/coder/.vscode-web" + install_prefix = "/home/coder/.vscode-web" folder = "/home/coder" accept_license = true } ``` + +### Install Extensions + +```tf +module "vscode-web" { + source = "registry.coder.com/modules/vscode-web/coder" + version = "1.0.2" + agent_id = coder_agent.example.id + extensions = ["github.copilot", "ms-python.python", "ms-toolsai.jupyter"] + accept_license = true +} +``` diff --git a/vscode-web/main.test.ts b/vscode-web/main.test.ts deleted file mode 100644 index 57277df..0000000 --- a/vscode-web/main.test.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { describe, expect, it } from "bun:test"; -import { - executeScriptInContainer, - runTerraformApply, - runTerraformInit, -} from "../test"; - -describe("vscode-web", async () => { - await runTerraformInit(import.meta.dir); - - // replaces testRequiredVariables due to license variable - // may add a testRequiredVariablesWithLicense function later - it("missing agent_id", async () => { - try { - await runTerraformApply(import.meta.dir, { - accept_license: "true", - }); - } catch (ex) { - expect(ex.message).toContain('input variable "agent_id" is not set'); - } - }); - - it("invalid license_agreement", async () => { - try { - await runTerraformApply(import.meta.dir, { - agent_id: "foo", - }); - } catch (ex) { - expect(ex.message).toContain( - "You must accept the VS Code license agreement by setting accept_license=true", - ); - } - }); - - it("fails without curl", async () => { - const state = await runTerraformApply(import.meta.dir, { - agent_id: "foo", - accept_license: "true", - }); - const output = await executeScriptInContainer(state, "alpine"); - expect(output.exitCode).toBe(1); - expect(output.stdout).toEqual([ - "\u001b[0;1mInstalling vscode-cli!", - "Failed to install vscode-cli:", // TODO: manually test error log - ]); - }); - - it("runs with curl", async () => { - const state = await runTerraformApply(import.meta.dir, { - agent_id: "foo", - accept_license: "true", - }); - const output = await executeScriptInContainer(state, "alpine/curl"); - expect(output.exitCode).toBe(0); - expect(output.stdout).toEqual([ - "\u001b[0;1mInstalling vscode-cli!", - "🥳 vscode-cli has been installed.", - "", - "👷 Running /tmp/vscode-cli/bin/code serve-web --port 13338 --without-connection-token --accept-server-license-terms in the background...", - "Check logs at /tmp/vscode-web.log!", - ]); - }); -}); diff --git a/vscode-web/main.tf b/vscode-web/main.tf index 9932fac..640bf4e 100644 --- a/vscode-web/main.tf +++ b/vscode-web/main.tf @@ -53,15 +53,21 @@ variable "log_path" { default = "/tmp/vscode-web.log" } -variable "install_dir" { +variable "install_prefix" { type = string - description = "The directory to install VS Code CLI" - default = "/tmp/vscode-cli" + description = "The prefix to install vscode-web to." + default = "/tmp/vscode-web" +} + +variable "extensions" { + type = list(string) + description = "A list of extensions to install." + default = [] } variable "accept_license" { type = bool - description = "Accept the VS Code license. https://code.visualstudio.com/license" + description = "Accept the VS Code Server license. https://code.visualstudio.com/license/server" default = false validation { condition = var.accept_license == true @@ -69,6 +75,16 @@ variable "accept_license" { } } +variable "telemetry_level" { + type = string + description = "Set the telemetry level for VS Code Web." + default = "error" + validation { + condition = var.telemetry_level == "off" || var.telemetry_level == "crash" || var.telemetry_level == "error" || var.telemetry_level == "all" + error_message = "Incorrect value. Please set either 'off', 'crash', 'error', or 'all'." + } +} + resource "coder_script" "vscode-web" { agent_id = var.agent_id display_name = "VS Code Web" @@ -76,7 +92,9 @@ resource "coder_script" "vscode-web" { script = templatefile("${path.module}/run.sh", { PORT : var.port, LOG_PATH : var.log_path, - INSTALL_DIR : var.install_dir, + INSTALL_PREFIX : var.install_prefix, + EXTENSIONS : join(",", var.extensions), + TELEMETRY_LEVEL : var.telemetry_level, }) run_on_start = true } diff --git a/vscode-web/run.sh b/vscode-web/run.sh old mode 100644 new mode 100755 index d19be62..5977440 --- a/vscode-web/run.sh +++ b/vscode-web/run.sh @@ -1,21 +1,49 @@ -#!/usr/bin/env sh +#!/usr/bin/env bash BOLD='\033[0;1m' +EXTENSIONS=("${EXTENSIONS}") -# Create install directory if it doesn't exist -mkdir -p ${INSTALL_DIR} +# Create install prefix +mkdir -p ${INSTALL_PREFIX} -printf "$${BOLD}Installing vscode-cli!\n" +printf "$${BOLD}Installing Microsoft Visual Studio Code Server!\n" -# Download and extract code-cli tarball -output=$(curl -Lk 'https://code.visualstudio.com/sha/download?build=stable&os=cli-alpine-x64' --output vscode_cli.tar.gz && tar -xf vscode_cli.tar.gz -C ${INSTALL_DIR} && rm vscode_cli.tar.gz) +# Download and extract vscode-server +ARCH=$(uname -m) +case "$ARCH" in + x86_64) ARCH="x64" ;; + aarch64) ARCH="arm64" ;; + *) + echo "Unsupported architecture" + exit 1 + ;; +esac + +HASH=$(curl https://update.code.visualstudio.com/api/commits/stable/server-linux-$ARCH-web | cut -d '"' -f 2) +output=$(curl -sL https://vscode.download.prss.microsoft.com/dbazure/download/stable/$HASH/vscode-server-linux-$ARCH-web.tar.gz | tar -xz -C ${INSTALL_PREFIX} --strip-components 1) if [ $? -ne 0 ]; then - echo "Failed to install vscode-cli: $output" + echo "Failed to install Microsoft Visual Studio Code Server: $output" exit 1 fi -printf "🥳 vscode-cli has been installed.\n\n" +printf "$${BOLD}Microsoft Visual Studio Code Server has been installed.\n" + +VSCODE_SERVER="${INSTALL_PREFIX}/bin/code-server" + +# Install each extension... +IFS=',' read -r -a EXTENSIONLIST <<< "$${EXTENSIONS}" +for extension in "$${EXTENSIONLIST[@]}"; do + if [ -z "$extension" ]; then + continue + fi + printf "🧩 Installing extension $${CODE}$extension$${RESET}...\n" + output=$($VSCODE_SERVER --install-extension "$extension" --force) + if [ $? -ne 0 ]; then + echo "Failed to install extension: $extension: $output" + exit 1 + fi +done -echo "👷 Running ${INSTALL_DIR}/bin/code serve-web --port ${PORT} --without-connection-token --accept-server-license-terms in the background..." +echo "👷 Running ${INSTALL_PREFIX}/bin/code-server serve-local --port ${PORT} --accept-server-license-terms serve-local --without-connection-token --telemetry-level ${TELEMETRY_LEVEL} in the background..." echo "Check logs at ${LOG_PATH}!" -${INSTALL_DIR}/code serve-web --port ${PORT} --without-connection-token --accept-server-license-terms > ${LOG_PATH} 2>&1 & +"${INSTALL_PREFIX}/bin/code-server" serve-local --port "${PORT}" --accept-server-license-terms serve-local --without-connection-token --telemetry-level "${TELEMETRY_LEVEL}" > "${LOG_PATH}" 2>&1 &