Set "extensions" and "theme" from `devcontainer.json`

I'm trying to use the default template Docker (Devcontainer) and I was hoping I'd be able to hold every piece of information of how to set up the environment in the repository and not the template itself. I have a template to just pull a GitHub repo and set it up however it wants, using it's .devcontainer/Dockerfile and .devcontainer/devcontainer.json. I'm having problems with pulling extensions and setting the theme for the code-server. I was hoping that I could just set it from the devcontainer.json but it doesn't work - neither settings or extensions are taken into consideration when building the Workspace. Here's my devcontainer.json:
{
"name": "Test Backend",
"build": {
"dockerfile": "./Dockerfile",
"context": "."
},
"features": {
"ghcr.io/devcontainers/features/common-utils:2": {
"installZsh": "true",
"username": "vscode",
"userUid": "1000",
"userGid": "1000",
"upgradePackages": "true"
},
"ghcr.io/devcontainers/features/rust:1": "latest",
"ghcr.io/devcontainers/features/git:1": {
"version": "latest",
"ppa": "false"
}
},
"customizations": {
"vscode": {
"settings": {
"workbench.colorTheme": "Visual Studio Dark"
},
"extensions": [
"rust-lang.rust-analyzer"
]
}
},
"forwardPorts": []
}
{
"name": "Test Backend",
"build": {
"dockerfile": "./Dockerfile",
"context": "."
},
"features": {
"ghcr.io/devcontainers/features/common-utils:2": {
"installZsh": "true",
"username": "vscode",
"userUid": "1000",
"userGid": "1000",
"upgradePackages": "true"
},
"ghcr.io/devcontainers/features/rust:1": "latest",
"ghcr.io/devcontainers/features/git:1": {
"version": "latest",
"ppa": "false"
}
},
"customizations": {
"vscode": {
"settings": {
"workbench.colorTheme": "Visual Studio Dark"
},
"extensions": [
"rust-lang.rust-analyzer"
]
}
},
"forwardPorts": []
}
18 Replies
Codercord
Codercord•2mo ago
<#1321267422367318066>
Category
Help needed
Product
code-server
Platform
Linux
Logs
Please post any relevant logs/error messages.
Phorcys
Phorcys•2mo ago
hey, this isn't really something we support out of the box at the moment
Phorcys
Phorcys•2mo ago
GitHub
Support for VS Code Extensions · Issue #68 · coder/envbuilder
The devcontainer spec supports pre-installing VS Code extensions. Could we support this with the code-server CLI (or even the official vscode-server for Remote SSH)? This came up from a prospect at...
Some Dinosaur
Some DinosaurOP•2mo ago
Oh I see there's a workaround to just rip out extensions using a regex and calling code-server --instal-extension. I was considering that as well, but the settings might not be that easy to do... ... or is it? If code-server stores settings in a file somewhere I should be able to do the same for the settings.
Phorcys
Phorcys•2mo ago
code-server does store the settings file like regular vscode does I think the path is different, let me find it for you otherwise if these workarounds don't work for you, you can use Docker in Docker and run your devcontainer inside of your coder workspace
Some Dinosaur
Some DinosaurOP•2mo ago
I dug it out! ~/.local/share/code-server/User/settings.json
Phorcys
Phorcys•2mo ago
settings should be in ~/.local/share/code-server/User/settings.json beat me to it!
Some Dinosaur
Some DinosaurOP•2mo ago
ok I think I will figure it out. I should be able to add this as a script in .tf file, and it should work. right? (I'm not familiar with terraform)
Phorcys
Phorcys•2mo ago
the way it works is that terraform provisions the resources (containers, disks/volumes, etc), and Coder's agent will run everything so you'll want to add a coder_script resource to your template so that the Coder agent picks it up and runs it
Some Dinosaur
Some DinosaurOP•2mo ago
It almost works. but my script has a @ character in it:
for extension in "${extensions[@]}"; do
for extension in "${extensions[@]}"; do
and terraform isn't happy about it:
Error: Error in function call
on main.tf line 306, in resource "coder_script" "vs-extensions":
306: script = templatefile("./vs-extensions.sh", {})
Call to function "templatefile" failed: ./vs-extensions.sh:11,36-37: Invalid character; This character is not used within the language., and 1 other diagnostic(s).
Error: Error in function call
on main.tf line 306, in resource "coder_script" "vs-extensions":
306: script = templatefile("./vs-extensions.sh", {})
Call to function "templatefile" failed: ./vs-extensions.sh:11,36-37: Invalid character; This character is not used within the language., and 1 other diagnostic(s).
yep - no idea what's happening:
/bin/bash: line 2: $'\r': command not found
/bin/bash: -c: line 16: syntax error near unexpected token `$'do\r''
'
/bin/bash: line 2: $'\r': command not found
/bin/bash: line 5: $'\r': command not found
/bin/bash: line 2: $'\r': command not found
/bin/bash: -c: line 16: syntax error near unexpected token `$'do\r''
'
/bin/bash: line 2: $'\r': command not found
/bin/bash: line 5: $'\r': command not found
I've added a new file to the template: vs-extensions.sh and here are the contents (copied from here):
#! /bin/bash

# install and start code-server
if ! which code-server > /dev/null; then
curl -fsSL https://code-server.dev/install.sh | sh | tee code-server-install.log
fi
CODE_CLI=code-server
if code; then
CODE_CLI=code
fi
mkdir -p ~/.vscode-server/extensions

set +e
extensions=( $(sed 's/\/\/.*$//g' */.devcontainer/devcontainer.json | jq -r -M '[.customizations.vscode.extensions[]?, .extensions[]?] | .[]' ) )
if [ "$${extensions[0]}" != "" ] && [ "$${extensions[0]}" != "null" ]; then
for extension in "$${extensions[@]}"; do
$CODE_CLI --extensions-dir ~/.vscode-server/extensions --install-extension "$extension"
done
fi
set -e
fi
#! /bin/bash

# install and start code-server
if ! which code-server > /dev/null; then
curl -fsSL https://code-server.dev/install.sh | sh | tee code-server-install.log
fi
CODE_CLI=code-server
if code; then
CODE_CLI=code
fi
mkdir -p ~/.vscode-server/extensions

set +e
extensions=( $(sed 's/\/\/.*$//g' */.devcontainer/devcontainer.json | jq -r -M '[.customizations.vscode.extensions[]?, .extensions[]?] | .[]' ) )
if [ "$${extensions[0]}" != "" ] && [ "$${extensions[0]}" != "null" ]; then
for extension in "$${extensions[@]}"; do
$CODE_CLI --extensions-dir ~/.vscode-server/extensions --install-extension "$extension"
done
fi
set -e
fi
Next I've added coder_script as follows:
resource "coder_script" "vs-extensions" {
agent_id = coder_agent.main.id
display_name = "VS Extensions"
icon = "/icon/code.svg"
run_on_start = true
script = templatefile("./vs-extensions.sh", {})
}
resource "coder_script" "vs-extensions" {
agent_id = coder_agent.main.id
display_name = "VS Extensions"
icon = "/icon/code.svg"
run_on_start = true
script = templatefile("./vs-extensions.sh", {})
}
And I keep getting these weird errors about $\r:
/bin/bash: line 2: $'\r': command not found
/bin/bash: -c: line 16: syntax error near unexpected token `$'do\r''
'
/bin/bash: line 2: $'\r': command not found
/bin/bash: -c: line 16: syntax error near unexpected token `$'do\r''
'
I just pasted the script into the startup_script in resource "coder_agent" "main"... but I wish there was a way to use a resource for it :c which should be the case with what I've done above according to:
startup_script (String) A script to run after the agent starts. The script should exit when it is done to signal that the agent is ready. This option is an alias for defining a coder_script resource with run_on_start set to true.
startup_script (String) A script to run after the agent starts. The script should exit when it is done to signal that the agent is ready. This option is an alias for defining a coder_script resource with run_on_start set to true.
but it just won't work Also because of this...:
When multiple scripts are assigned to the same agent, they are executed in parallel.
if I ever want to separate installing extensions from settings importing - I need to do some synchronization (can do file-system based) which sounds sketchy.
Phorcys
Phorcys•2mo ago
hey @Some Dinosaur, since you are not templating anything you can use file() instead of templatefile(), it won't complain about the @s these errors are due to windows line endings (CRLF -> \r\n), switch all your files to use LF (\n) to fix this Terraform does not play well w/ CRLF
Phorcys
Phorcys•2mo ago
GitHub
feat: allow execution ordering for coder_script · Issue #10352 · ...
Overview Currently, when building a workspace with multiple instances of coder_script, all are run concurrently, preventing any sequencing logic. To remedy this, some serialization should be implem...
Phorcys
Phorcys•2mo ago
we had multiple ideas but currently our init system is pretty simple and doesn't support ordering or anything else I'd recommend using something like supervisor if you need more precise control of the init sequence, or keep both grouped in the same script
Some Dinosaur
Some DinosaurOP•2mo ago
yeah I figured but I think I can't. I pasted my script to my Ubuntu remote, cleaned all line endings to LF, but it still had some leftovers 😦 feels very clunky to use these at the moment, I pasted my script raw into the TF
Phorcys
Phorcys•2mo ago
you don't have to do it manually you can either do it from within vscode or use the dos2unix command on the file
Some Dinosaur
Some DinosaurOP•2mo ago
(I don't have Discord Nitro, need to post my final solution in a couple messages. sorry!) Solution I think I've got this one relatively covered now. So here's what I've done. I edited the template I had by first removing the startup_script from the resource "coder_agent" "main" because documentation on coder_agent startup_script says:
This option is an alias for defining a coder_script resource with run_on_start set to true.
So I prefer to have the init script detached, you can give it an icon and all. Next I added the coder_script:
resource "coder_script" "vs-setup" {
  agent_id     = coder_agent.main.id
  display_name = "VS Setup"
  icon         = "/icon/code.svg"
  run_on_start = true
  script = <<EOF
    #!/bin/bash
    set -e

    # Install the latest code-server.
    # Append "--version x.x.x" to install a specific version of code-server.
    curl -fsSL https://code-server.dev/install.sh | sh -s -- --method=standalone --prefix=/tmp/code-server
    # Start code-server in the background.
    /tmp/code-server/bin/code-server --auth none --port 13337 >/tmp/code-server.log 2>&1 &

    # Import settings
    VSCODE_SETTINGS_PATH=~/.local/share/code-server/Machine/settings.json
    IMPORTED_SETTINGS_FILE=$(mktemp)
    echo "Importing settings..."
    sed 's/\/\/.*$//g' .devcontainer/devcontainer.json | jq -r -M '.customizations.vscode.settings?' > "$IMPORTED_SETTINGS_FILE"
    NEW_SETTINGS=$( jq -s '.[0] * (.[1] // {})' "$IMPORTED_SETTINGS_FILE" $VSCODE_SETTINGS_PATH )
    echo "$NEW_SETTINGS" > $VSCODE_SETTINGS_PATH
    rm "$IMPORTED_SETTINGS_FILE"

    # Install extensions
    echo "Installing extensions..."
    set +e
    extensions=( $(sed 's/\/\/.*$//g' .devcontainer/devcontainer.json | jq -r -M '[.customizations.vscode.extensions[]?, .extensions[]?] | .[]' ) )
    if [ "$${extensions[0]}" != "" ] && [ "$${extensions[0]}" != "null" ]; then
      for extension in "$${extensions[@]}"; do
        /tmp/code-server/bin/code-server --install-extension "$extension"
      done
    fi
    set -e
  EOF
}
resource "coder_script" "vs-setup" {
  agent_id     = coder_agent.main.id
  display_name = "VS Setup"
  icon         = "/icon/code.svg"
  run_on_start = true
  script = <<EOF
    #!/bin/bash
    set -e

    # Install the latest code-server.
    # Append "--version x.x.x" to install a specific version of code-server.
    curl -fsSL https://code-server.dev/install.sh | sh -s -- --method=standalone --prefix=/tmp/code-server
    # Start code-server in the background.
    /tmp/code-server/bin/code-server --auth none --port 13337 >/tmp/code-server.log 2>&1 &

    # Import settings
    VSCODE_SETTINGS_PATH=~/.local/share/code-server/Machine/settings.json
    IMPORTED_SETTINGS_FILE=$(mktemp)
    echo "Importing settings..."
    sed 's/\/\/.*$//g' .devcontainer/devcontainer.json | jq -r -M '.customizations.vscode.settings?' > "$IMPORTED_SETTINGS_FILE"
    NEW_SETTINGS=$( jq -s '.[0] * (.[1] // {})' "$IMPORTED_SETTINGS_FILE" $VSCODE_SETTINGS_PATH )
    echo "$NEW_SETTINGS" > $VSCODE_SETTINGS_PATH
    rm "$IMPORTED_SETTINGS_FILE"

    # Install extensions
    echo "Installing extensions..."
    set +e
    extensions=( $(sed 's/\/\/.*$//g' .devcontainer/devcontainer.json | jq -r -M '[.customizations.vscode.extensions[]?, .extensions[]?] | .[]' ) )
    if [ "$${extensions[0]}" != "" ] && [ "$${extensions[0]}" != "null" ]; then
      for extension in "$${extensions[@]}"; do
        /tmp/code-server/bin/code-server --install-extension "$extension"
      done
    fi
    set -e
  EOF
}
1. The first part Install the latest code-server is just copy-paste of what was initially in startup_script. 2. Second part Import settings I wrote myself - it'll take settings from .devcontainer/devcontainer.json and merge settings with what is already set on the machine. Notice that I assume the .devcontainer folder to be available at CWD - you might want to do instead */.devcontainer/devcontainer.json to dig one level lower if you go with default workspaces folder. Or even better what I should've done - define the path as env-variable. code-server doesn't provide any command to set the settings, so I update settings.json file manually - I'd much rather have something like --install-extension command. 3. Third part to install extensions is a copy-pase from here. I changed the path too for my own CWD setup. It will call code-server --install-extension for every extension listed in your devcontainer.json. It's a simple solution and it works - I hope it works for you as well. Componentisation - why not? I really wanted to keep things separate, to have like 3 scripts: vs-install.sh, vs-extensions.sh, vs-settings.sh but it's hard to do. There's no synchronization mechanism for separate script resources and as the documentation says:
When multiple scripts are assigned to the same agent, they are executed in parallel.
So if your scripts depend on each other - like in my case I can't install extensions before code-server is installed - you'll have to make scripts way more complex to add synchronization. You could do file-system based synchronization - locking a file as a mutex so that other scripts wait for it to be available - but for me it sounds like too much work for the simple setup im trying to achieve. Windows I've done this from my Windows PC so I could play games in-between builds - big mistake. I had a lot of errors due to CRLF line endings that caused both scripts to fail and whole Coder frontend to freeze - not reporting any logs until I restarted it. I think the frontend should just filter out CR on Build if it causes problem but it is what it is. I'd say just avoid Windows, Coder feels a bit clunky with it. /close (can't close it... how do I close the thread?)
Phorcys
Phorcys•5w ago
thanks for the really detailed answer @Some Dinosaur! yes, you should use /close
Codercord
Codercord•5w ago
@Phorcys closed the thread.

Did you find this page helpful?