mirror: registry.coder.com/coder/code-server/coder v1.4.3

This commit is contained in:
Coder Module Mirror
2026-03-13 15:55:25 +01:00
commit eae721f792
5 changed files with 618 additions and 0 deletions

130
README.md Normal file
View File

@@ -0,0 +1,130 @@
---
display_name: code-server
description: VS Code in the browser
icon: ../../../../.icons/code.svg
verified: true
tags: [ide, web, code-server]
---
# code-server
Automatically install [code-server](https://github.com/coder/code-server) in a workspace, create an app to access it via the dashboard, install extensions, and pre-configure editor settings.
```tf
module "code-server" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/code-server/coder"
version = "1.4.3"
agent_id = coder_agent.example.id
}
```
![Screenshot 1](https://github.com/coder/code-server/raw/main/docs/assets/screenshot-1.png?raw=true)
## Examples
### Pin Versions
```tf
module "code-server" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/code-server/coder"
version = "1.4.3"
agent_id = coder_agent.example.id
install_version = "4.106.3"
}
```
### Pre-install Extensions
Install the Dracula theme from [OpenVSX](https://open-vsx.org/):
```tf
module "code-server" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/code-server/coder"
version = "1.4.3"
agent_id = coder_agent.example.id
extensions = [
"dracula-theme.theme-dracula"
]
}
```
Enter the `<author>.<name>` into the extensions array and code-server will automatically install on start.
### Pre-configure Settings
Configure VS Code's [settings.json](https://code.visualstudio.com/docs/getstarted/settings#_settings-json-file) file:
```tf
module "code-server" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/code-server/coder"
version = "1.4.3"
agent_id = coder_agent.example.id
extensions = ["dracula-theme.theme-dracula"]
settings = {
"workbench.colorTheme" = "Dracula"
}
}
```
### Install multiple extensions
Just run code-server in the background, don't fetch it from GitHub:
```tf
module "code-server" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/code-server/coder"
version = "1.4.3"
agent_id = coder_agent.example.id
extensions = ["dracula-theme.theme-dracula", "ms-azuretools.vscode-docker"]
}
```
### Pass Additional Arguments
You can pass additional command-line arguments to code-server using the `additional_args` variable. For example, to disable workspace trust:
```tf
module "code-server" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/code-server/coder"
version = "1.4.3"
agent_id = coder_agent.example.id
additional_args = "--disable-workspace-trust"
}
```
### Offline and Use Cached Modes
By default the module looks for code-server at `/tmp/code-server` but this can be changed with `install_prefix`.
Run an existing copy of code-server if found, otherwise download from GitHub:
```tf
module "code-server" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/code-server/coder"
version = "1.4.3"
agent_id = coder_agent.example.id
use_cached = true
extensions = ["dracula-theme.theme-dracula", "ms-azuretools.vscode-docker"]
}
```
Just run code-server in the background, don't fetch it from GitHub:
```tf
module "code-server" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/code-server/coder"
version = "1.4.3"
agent_id = coder_agent.example.id
offline = true
}
```
Some of the key differences between code-server and [VS Code Web](https://registry.coder.com/modules/coder/vscode-web) are listed in [docs](https://coder.com/docs/user-guides/workspace-access/code-server#differences-between-code-server-and-vs-code-web).

50
code-server.tftest.hcl Normal file
View File

@@ -0,0 +1,50 @@
run "required_vars" {
command = plan
variables {
agent_id = "foo"
}
}
run "offline_and_use_cached_conflict" {
command = plan
variables {
agent_id = "foo"
use_cached = true
offline = true
}
expect_failures = [
resource.coder_script.code-server
]
}
run "offline_disallows_extensions" {
command = plan
variables {
agent_id = "foo"
offline = true
extensions = ["ms-python.python", "golang.go"]
}
expect_failures = [
resource.coder_script.code-server
]
}
run "url_with_folder_query" {
command = plan
variables {
agent_id = "foo"
folder = "/home/coder/project"
port = 13337
}
assert {
condition = resource.coder_app.code-server.url == "http://localhost:13337/?folder=%2Fhome%2Fcoder%2Fproject"
error_message = "coder_app URL must include encoded folder query param"
}
}

84
main.test.ts Normal file
View File

@@ -0,0 +1,84 @@
import { describe, expect, it } from "bun:test";
import {
execContainer,
findResourceInstance,
removeContainer,
runContainer,
runTerraformApply,
runTerraformInit,
testRequiredVariables,
} from "~test";
describe("code-server", async () => {
await runTerraformInit(import.meta.dir);
testRequiredVariables(import.meta.dir, {
agent_id: "foo",
});
it("use_cached and offline can not be used together", () => {
const t = async () => {
await runTerraformApply(import.meta.dir, {
agent_id: "foo",
use_cached: "true",
offline: "true",
});
};
expect(t).toThrow("Offline and Use Cached can not be used together");
});
it("offline and extensions can not be used together", () => {
const t = async () => {
await runTerraformApply(import.meta.dir, {
agent_id: "foo",
offline: "true",
extensions: '["1", "2"]',
});
};
expect(t).toThrow("Offline mode does not allow extensions to be installed");
});
it("installs and runs code-server", async () => {
const state = await runTerraformApply(import.meta.dir, {
agent_id: "foo",
});
const id = await runContainer("ubuntu:latest");
try {
await execContainer(id, [
"bash",
"-c",
"apt-get update && apt-get install -y curl",
]);
const script = findResourceInstance(state, "coder_script").script;
const result = await execContainer(id, ["bash", "-c", script]);
if (result.exitCode !== 0) {
console.log(result.stdout);
console.log(result.stderr);
}
expect(result.exitCode).toBe(0);
const version = await execContainer(id, [
"/tmp/code-server/bin/code-server",
"--version",
]);
expect(version.exitCode).toBe(0);
expect(version.stdout).toMatch(/\d+\.\d+\.\d+/);
const health = await execContainer(id, [
"curl",
"--retry",
"10",
"--retry-delay",
"1",
"--retry-all-errors",
"-sf",
"http://localhost:13337/healthz",
]);
expect(health.exitCode).toBe(0);
} finally {
await removeContainer(id);
}
}, 60000);
});

211
main.tf Normal file
View File

@@ -0,0 +1,211 @@
terraform {
required_version = ">= 1.0"
required_providers {
coder = {
source = "coder/coder"
version = ">= 2.5"
}
}
}
variable "agent_id" {
type = string
description = "The ID of a Coder agent."
}
variable "extensions" {
type = list(string)
description = "A list of extensions to install."
default = []
}
variable "port" {
type = number
description = "The port to run code-server on."
default = 13337
}
variable "display_name" {
type = string
description = "The display name for the code-server application."
default = "code-server"
}
variable "slug" {
type = string
description = "The slug for the code-server application."
default = "code-server"
}
variable "settings" {
type = any
description = "A map of settings to apply to code-server."
default = {}
}
variable "machine_settings" {
type = any
description = "A map of template level machine settings to apply to code-server. This will be overwritten at each container start."
default = {}
}
variable "folder" {
type = string
description = "The folder to open in code-server."
default = ""
}
variable "install_prefix" {
type = string
description = "The prefix to install code-server to."
default = "/tmp/code-server"
}
variable "log_path" {
type = string
description = "The path to log code-server to."
default = "/tmp/code-server.log"
}
variable "install_version" {
type = string
description = "The version of code-server to install."
default = ""
}
variable "share" {
type = string
default = "owner"
validation {
condition = var.share == "owner" || var.share == "authenticated" || var.share == "public"
error_message = "Incorrect value. Please set either 'owner', 'authenticated', or 'public'."
}
}
variable "order" {
type = number
description = "The order determines the position of app in the UI presentation. The lowest order is shown first and apps with equal order are sorted by name (ascending order)."
default = null
}
variable "group" {
type = string
description = "The name of a group that this app belongs to."
default = null
}
variable "offline" {
type = bool
description = "Just run code-server in the background, don't fetch it from GitHub"
default = false
}
variable "use_cached" {
type = bool
description = "Uses cached copy code-server in the background, otherwise fetched it from GitHub"
default = false
}
variable "use_cached_extensions" {
type = bool
description = "Uses cached copy of extensions, otherwise do a forced upgrade"
default = false
}
variable "extensions_dir" {
type = string
description = "Override the directory to store extensions in."
default = ""
}
variable "auto_install_extensions" {
type = bool
description = "Automatically install recommended extensions when code-server starts."
default = false
}
variable "subdomain" {
type = bool
description = <<-EOT
Determines whether the app will be accessed via it's own subdomain or whether it will be accessed via a path on Coder.
If wildcards have not been setup by the administrator then apps with "subdomain" set to true will not be accessible.
EOT
default = false
}
variable "open_in" {
type = string
description = <<-EOT
Determines where the app will be opened. Valid values are `"tab"` and `"slim-window" (default)`.
`"tab"` opens in a new tab in the same browser window.
`"slim-window"` opens a new browser window without navigation controls.
EOT
default = "slim-window"
validation {
condition = contains(["tab", "slim-window"], var.open_in)
error_message = "The 'open_in' variable must be one of: 'tab', 'slim-window'."
}
}
variable "additional_args" {
type = string
description = "Additional command-line arguments to pass to code-server (e.g., '--disable-workspace-trust')."
default = ""
}
resource "coder_script" "code-server" {
agent_id = var.agent_id
display_name = "code-server"
icon = "/icon/code.svg"
script = templatefile("${path.module}/run.sh", {
VERSION : var.install_version,
EXTENSIONS : join(",", var.extensions),
APP_NAME : var.display_name,
PORT : var.port,
LOG_PATH : var.log_path,
INSTALL_PREFIX : var.install_prefix,
// This is necessary otherwise the quotes are stripped!
SETTINGS : replace(jsonencode(var.settings), "\"", "\\\""),
MACHINE_SETTINGS : replace(jsonencode(var.machine_settings), "\"", "\\\""),
OFFLINE : var.offline,
USE_CACHED : var.use_cached,
USE_CACHED_EXTENSIONS : var.use_cached_extensions,
EXTENSIONS_DIR : var.extensions_dir,
FOLDER : var.folder,
AUTO_INSTALL_EXTENSIONS : var.auto_install_extensions,
ADDITIONAL_ARGS : var.additional_args,
})
run_on_start = true
lifecycle {
precondition {
condition = !var.offline || length(var.extensions) == 0
error_message = "Offline mode does not allow extensions to be installed"
}
precondition {
condition = !var.offline || !var.use_cached
error_message = "Offline and Use Cached can not be used together"
}
}
}
resource "coder_app" "code-server" {
agent_id = var.agent_id
slug = var.slug
display_name = var.display_name
url = "http://localhost:${var.port}/${var.folder != "" ? "?folder=${urlencode(var.folder)}" : ""}"
icon = "/icon/code.svg"
subdomain = var.subdomain
share = var.share
order = var.order
group = var.group
open_in = var.open_in
healthcheck {
url = "http://localhost:${var.port}/healthz"
interval = 5
threshold = 6
}
}

143
run.sh Normal file
View File

@@ -0,0 +1,143 @@
#!/usr/bin/env bash
EXTENSIONS=("${EXTENSIONS}")
BOLD='\033[0;1m'
CODE='\033[36;40;1m'
RESET='\033[0m'
CODE_SERVER="${INSTALL_PREFIX}/bin/code-server"
# Set extension directory
EXTENSION_ARG=""
if [ -n "${EXTENSIONS_DIR}" ]; then
EXTENSION_ARG="--extensions-dir=${EXTENSIONS_DIR}"
mkdir -p "${EXTENSIONS_DIR}"
fi
function run_code_server() {
echo "👷 Running code-server in the background..."
echo "Check logs at ${LOG_PATH}!"
$CODE_SERVER "$EXTENSION_ARG" --auth none --port "${PORT}" --app-name "${APP_NAME}" ${ADDITIONAL_ARGS} > "${LOG_PATH}" 2>&1 &
}
# Check if the settings file exists...
if [ ! -f ~/.local/share/code-server/User/settings.json ]; then
echo "⚙️ Creating settings file..."
mkdir -p ~/.local/share/code-server/User
if command -v jq &> /dev/null; then
echo "${SETTINGS}" | jq '.' > ~/.local/share/code-server/User/settings.json
else
echo "${SETTINGS}" > ~/.local/share/code-server/User/settings.json
fi
fi
# Apply/overwrite template based settings
echo "⚙️ Creating machine settings file..."
mkdir -p ~/.local/share/code-server/Machine
if command -v jq &> /dev/null; then
echo "${MACHINE_SETTINGS}" | jq '.' > ~/.local/share/code-server/Machine/settings.json
else
echo "${MACHINE_SETTINGS}" > ~/.local/share/code-server/Machine/settings.json
fi
# Check if code-server is already installed for offline
if [ "${OFFLINE}" = true ]; then
if [ -f "$CODE_SERVER" ]; then
echo "🥳 Found a copy of code-server"
run_code_server
exit 0
fi
# Offline mode always expects a copy of code-server to be present
echo "Failed to find a copy of code-server"
exit 1
fi
# If there is no cached install OR we don't want to use a cached install
if [ ! -f "$CODE_SERVER" ] || [ "${USE_CACHED}" != true ]; then
printf "$${BOLD}Installing code-server!\n"
# Clean up from other install (in case install prefix changed).
if [ -n "$CODER_SCRIPT_BIN_DIR" ] && [ -e "$CODER_SCRIPT_BIN_DIR/code-server" ]; then
rm "$CODER_SCRIPT_BIN_DIR/code-server"
fi
ARGS=(
"--method=standalone"
"--prefix=${INSTALL_PREFIX}"
)
if [ -n "${VERSION}" ]; then
ARGS+=("--version=${VERSION}")
fi
output=$(curl -fsSL https://code-server.dev/install.sh | sh -s -- "$${ARGS[@]}")
if [ $? -ne 0 ]; then
echo "Failed to install code-server: $output"
exit 1
fi
printf "🥳 code-server has been installed in ${INSTALL_PREFIX}\n\n"
fi
# Make the code-server available in PATH.
if [ -n "$CODER_SCRIPT_BIN_DIR" ] && [ ! -e "$CODER_SCRIPT_BIN_DIR/code-server" ]; then
ln -s "$CODE_SERVER" "$CODER_SCRIPT_BIN_DIR/code-server"
fi
# Get the list of installed extensions...
LIST_EXTENSIONS=$($CODE_SERVER --list-extensions $EXTENSION_ARG)
readarray -t EXTENSIONS_ARRAY <<< "$LIST_EXTENSIONS"
function extension_installed() {
if [ "${USE_CACHED_EXTENSIONS}" != true ]; then
return 1
fi
# shellcheck disable=SC2066
for _extension in "$${EXTENSIONS_ARRAY[@]}"; do
if [ "$_extension" == "$1" ]; then
echo "Extension $1 was already installed."
return 0
fi
done
return 1
}
# Install each extension...
IFS=',' read -r -a EXTENSIONLIST <<< "$${EXTENSIONS}"
# shellcheck disable=SC2066
for extension in "$${EXTENSIONLIST[@]}"; do
if [ -z "$extension" ]; then
continue
fi
if extension_installed "$extension"; then
continue
fi
printf "🧩 Installing extension $${CODE}$extension$${RESET}...\n"
output=$($CODE_SERVER "$EXTENSION_ARG" --force --install-extension "$extension")
if [ $? -ne 0 ]; then
echo "Failed to install extension: $extension: $output"
exit 1
fi
done
if [ "${AUTO_INSTALL_EXTENSIONS}" = true ]; then
if ! command -v jq > /dev/null; then
echo "jq is required to install extensions from a workspace file."
exit 0
fi
WORKSPACE_DIR="$HOME"
if [ -n "${FOLDER}" ]; then
WORKSPACE_DIR="${FOLDER}"
fi
if [ -f "$WORKSPACE_DIR/.vscode/extensions.json" ]; then
printf "🧩 Installing extensions from %s/.vscode/extensions.json...\n" "$WORKSPACE_DIR"
# Use sed to remove single-line comments before parsing with jq
extensions=$(sed 's|//.*||g' "$WORKSPACE_DIR"/.vscode/extensions.json | jq -r '.recommendations[]')
for extension in $extensions; do
if extension_installed "$extension"; then
continue
fi
$CODE_SERVER "$EXTENSION_ARG" --force --install-extension "$extension"
done
fi
fi
run_code_server