From 0afb18ea4bb904eea8811b4639bf9500f369c343 Mon Sep 17 00:00:00 2001 From: Harsh panwar Date: Sat, 28 Feb 2026 09:02:54 +0530 Subject: [PATCH 1/3] feat: add pre-install plugins support --- registry/coder/modules/jetbrains/README.md | 39 ++++ .../modules/jetbrains/jetbrains.tftest.hcl | 34 ++++ registry/coder/modules/jetbrains/main.tf | 34 ++++ .../jetbrains/scripts/install_plugins.sh | 167 ++++++++++++++++++ 4 files changed, 274 insertions(+) create mode 100644 registry/coder/modules/jetbrains/scripts/install_plugins.sh diff --git a/registry/coder/modules/jetbrains/README.md b/registry/coder/modules/jetbrains/README.md index 7fa8f6747..d28c5b9c2 100644 --- a/registry/coder/modules/jetbrains/README.md +++ b/registry/coder/modules/jetbrains/README.md @@ -136,6 +136,45 @@ module "jetbrains" { } ``` +### Plugin Auto‑Installer + +This module now supports automatic JetBrains plugin installation inside your workspace. + +To get a plugin ID, open the plugin’s page on the JetBrains Marketplace. Scroll down to Additional Information and look for Plugin ID. Use that value in the configuration below. + +```tf +module "jetbrains" { + count = data.coder_workspace.me.start_count + source = "registry.coder.com/coder/jetbrains/coder" + version = "1.2.1" + agent_id = coder_agent.main.id + folder = "/home/coder/project" + default = ["IU", "PY"] + + jetbrains_plugins = { + "PY" = ["com.koxudaxi.pydantic", "com.intellij.kubernetes"] + "IU" = ["", ""] + "WS" = ["", ""] + "GO" = ["", ""] + "CL" = ["", ""] + "PS" = ["", ""] + "RD" = ["", ""] + "RM" = ["", ""] + "RR" = ["", ""] + } +} +``` + +> [!IMPORTANT] \ +> After installing the IDE, restart the workspace. +> When the workspace starts again, the scripts will detect the installed IDE and automatically install the configured plugins. +> +> This module prerequisites and limitations +> +> 1. Requires JetBrains Toolbox to be installed +> 2. Requires jq to be available +> 3. Only works on Debian/Ubuntu-based systems (due to apt-get usage) + ### Accessing the IDE Metadata You can now reference the output `ide_metadata` as a map. diff --git a/registry/coder/modules/jetbrains/jetbrains.tftest.hcl b/registry/coder/modules/jetbrains/jetbrains.tftest.hcl index dba9551da..973a74295 100644 --- a/registry/coder/modules/jetbrains/jetbrains.tftest.hcl +++ b/registry/coder/modules/jetbrains/jetbrains.tftest.hcl @@ -351,3 +351,37 @@ run "validate_output_schema" { error_message = "The ide_metadata output schema has changed. Please update the 'main.tf' and this test." } } + +run "no_plugin_script_when_plugins_empty" { + command = plan + + variables { + agent_id = "foo" + folder = "/home/coder" + default = ["PY"] + jetbrains_plugins = {} + } + + assert { + condition = length(resource.coder_script.install_jetbrains_plugins) == 0 + error_message = "Expected no plugin install script when plugins list is empty" + } +} + +run "plugin_script_created_when_plugins_provided" { + command = plan + + variables { + agent_id = "foo" + folder = "/home/coder" + default = ["PY"] + jetbrains_plugins = { + "PY" = ["com.koxudaxi.pydantic", "com.intellij.kubernetes"] + } + } + + assert { + condition = length(resource.coder_script.install_jetbrains_plugins) == 1 + error_message = "Expected script to be created when plugins are provided" + } +} \ No newline at end of file diff --git a/registry/coder/modules/jetbrains/main.tf b/registry/coder/modules/jetbrains/main.tf index 2fac060f1..e4290b181 100644 --- a/registry/coder/modules/jetbrains/main.tf +++ b/registry/coder/modules/jetbrains/main.tf @@ -173,6 +173,12 @@ variable "ide_config" { } } +variable "jetbrains_plugins" { + type = map(list(string)) + description = "Map of IDE product codes to plugin ID lists. Example: { IU = [\"com.foo\"], GO = [\"org.bar\"] }." + default = {} +} + locals { # Parse HTTP responses once with error handling for air-gapped environments parsed_responses = { @@ -214,6 +220,10 @@ locals { # Convert the parameter value to a set for for_each selected_ides = length(var.default) == 0 ? toset(jsondecode(coalesce(data.coder_parameter.jetbrains_ides[0].value, "[]"))) : toset(var.default) + + plugin_map_b64 = base64encode(jsonencode(var.jetbrains_plugins)) + + plugin_install_script = file("${path.module}/script/install_plugins.sh") } data "coder_parameter" "jetbrains_ides" { @@ -241,6 +251,30 @@ data "coder_parameter" "jetbrains_ides" { data "coder_workspace" "me" {} data "coder_workspace_owner" "me" {} +resource "coder_script" "install_jetbrains_plugins" { + count = length(var.jetbrains_plugins) > 0 ? 1 : 0 + agent_id = var.agent_id + display_name = "Install JetBrains Plugins" + run_on_start = true + + script = <<-EOT + #!/bin/bash + set -o errexit + set -o pipefail + + CONFIG_DIR="$HOME/.config/jetbrains" + + mkdir -p "$CONFIG_DIR" + echo -n "${local.plugin_map_b64}" | base64 -d > "$CONFIG_DIR/plugins.json" + chmod 600 "$CONFIG_DIR/plugins.json" + + echo -n '${base64encode(local.plugin_install_script)}' | base64 -d > /tmp/install_plugins.sh + chmod +x /tmp/install_plugins.sh + + /tmp/install_plugins.sh > /tmp/install_plugins.log 2>&1 & + EOT +} + resource "coder_app" "jetbrains" { for_each = local.selected_ides agent_id = var.agent_id diff --git a/registry/coder/modules/jetbrains/scripts/install_plugins.sh b/registry/coder/modules/jetbrains/scripts/install_plugins.sh new file mode 100644 index 000000000..868bbc74e --- /dev/null +++ b/registry/coder/modules/jetbrains/scripts/install_plugins.sh @@ -0,0 +1,167 @@ +#!/bin/bash +# set -euo pipefail + +LOGFILE="$HOME/.config/jetbrains/install_plugins.log" +TOOLBOX_BASE="$HOME/.local/share/JetBrains/Toolbox/apps" +PLUGIN_MAP_FILE="$HOME/.config/jetbrains/plugins.json" + +if command -v apt-get > /dev/null 2>&1; then + sudo apt-get update + sudo apt-get install -y libfreetype6 +else + echo "Warning: 'apt-get' not found. Please ensure 'libfreetype6' is installed manually for your distribution." >&2 +fi + +mkdir -p "$(dirname "$LOGFILE")" + +exec > >(tee -a "$LOGFILE") 2>&1 + +log() { + printf '%s %s\n' "$(date --iso-8601=seconds)" "$*" | tee -a "$LOGFILE" +} + +# -------- Read plugin JSON -------- +get_enabled_codes() { + jq -r 'keys[]' "$PLUGIN_MAP_FILE" +} + +get_plugins_for_code() { + jq -r --arg CODE "$1" '.[$CODE][]?' "$PLUGIN_MAP_FILE" 2> /dev/null || true +} + +# -------- Product code mapping -------- +map_folder_to_code() { + case "$1" in + *pycharm*) echo "PY" ;; + *idea*) echo "IU" ;; + *webstorm*) echo "WS" ;; + *goland*) echo "GO" ;; + *clion*) echo "CL" ;; + *phpstorm*) echo "PS" ;; + *rider*) echo "RD" ;; + *rubymine*) echo "RM" ;; + *rustrover*) echo "RR" ;; + *) echo "" ;; + esac +} + +# -------- CLI launcher names -------- +launcher_for_code() { + case "$1" in + PY) echo "pycharm" ;; + IU) echo "idea" ;; + WS) echo "webstorm" ;; + GO) echo "goland" ;; + CL) echo "clion" ;; + PS) echo "phpstorm" ;; + RD) echo "rider" ;; + RM) echo "rubymine" ;; + RR) echo "rustrover" ;; + *) return 1 ;; + esac +} + +find_cli_launcher() { + local exe + exe="$(launcher_for_code "$1")" || return 1 + + # Look for the newest version directory + local latest_version + latest_version=$(find "$2" -maxdepth 2 -type d -name "ch-*" 2> /dev/null | sort -V | tail -1) + + if [ -n "$latest_version" ] && [ -f "$latest_version/bin/$exe" ]; then + echo "$latest_version/bin/$exe" + elif [ -f "$2/bin/$exe" ]; then + echo "$2/bin/$exe" + else + return 1 + fi +} + +install_plugin() { + log "Installing plugin: $2" + if "$1" installPlugins "$2"; then + log "Successfully installed plugin: $2" + else + log "Failed to install plugin: $2" + return 1 + fi +} + +# -------- Main -------- +log "Plugin installer started" + +if [ ! -f "$PLUGIN_MAP_FILE" ]; then + log "No plugins.json found. Exiting." + exit 0 +fi + +if [ ! -d "$TOOLBOX_BASE" ]; then + log "Toolbox directory not found. Exiting." + exit 0 +fi + +# Load list of IDE codes user actually needs +mapfile -t pending_codes < <(get_enabled_codes) + +if [ ${#pending_codes[@]} -eq 0 ]; then + log "No plugin entries found. Exiting." + exit 0 +fi + +log "Waiting for IDE installation. Pending codes: ${pending_codes[*]}" + +MAX_ATTEMPTS=10 +attempt=0 + +# Loop until all plugins installed +while [ ${#pending_codes[@]} -gt 0 ] && [ $attempt -lt $MAX_ATTEMPTS ]; do + + for product_dir in "$TOOLBOX_BASE"/*; do + [ -d "$product_dir" ] || continue + + product_name="$(basename "$product_dir")" + code="$(map_folder_to_code "$product_name")" + + # Only process codes user requested + if [[ ! " ${pending_codes[*]} " =~ " $code " ]]; then + continue + fi + + cli_launcher_path="$(find_cli_launcher "$code" "$product_dir")" || continue + + log "Detected IDE $code at $product_dir" + + plugins="$(get_plugins_for_code "$code")" + if [ -z "$plugins" ]; then + log "No plugins for $code" + continue + fi + + while read -r plugin; do + install_plugin "$cli_launcher_path" "$plugin" + done <<< "$plugins" + + # remove code from pending list after success + tmp=() + for c in "${pending_codes[@]}"; do + [ "$c" != "$code" ] && tmp+=("$c") + done + pending_codes=("${tmp[@]}") + + log "Finished $code. Remaining: ${pending_codes[*]:-none}" + + done + # If still pending, wait and retry + if [ ${#pending_codes[@]} -gt 0 ]; then + sleep 5 + ((attempt++)) + fi +done + +if [ ${#pending_codes[@]} -gt 0 ]; then + log "Timeout: IDEs not found: ${pending_codes[*]}" + exit 1 +fi + +log "All plugins installed. Exiting." \ No newline at end of file From f0d55e1dffe9b4a14118efc84baa43688620e037 Mon Sep 17 00:00:00 2001 From: Harsh panwar Date: Sat, 28 Feb 2026 11:17:24 +0530 Subject: [PATCH 2/3] fix: typos --- registry/coder/modules/jetbrains/main.tf | 4 ++-- registry/coder/modules/jetbrains/scripts/install_plugins.sh | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/registry/coder/modules/jetbrains/main.tf b/registry/coder/modules/jetbrains/main.tf index e4290b181..0591fc65e 100644 --- a/registry/coder/modules/jetbrains/main.tf +++ b/registry/coder/modules/jetbrains/main.tf @@ -223,7 +223,7 @@ locals { plugin_map_b64 = base64encode(jsonencode(var.jetbrains_plugins)) - plugin_install_script = file("${path.module}/script/install_plugins.sh") + plugin_install_script = file("${path.module}/scripts/install_plugins.sh") } data "coder_parameter" "jetbrains_ides" { @@ -271,7 +271,7 @@ resource "coder_script" "install_jetbrains_plugins" { echo -n '${base64encode(local.plugin_install_script)}' | base64 -d > /tmp/install_plugins.sh chmod +x /tmp/install_plugins.sh - /tmp/install_plugins.sh > /tmp/install_plugins.log 2>&1 & + /tmp/install_plugins.sh > /tmp/install_plugins.log 2>&1 EOT } diff --git a/registry/coder/modules/jetbrains/scripts/install_plugins.sh b/registry/coder/modules/jetbrains/scripts/install_plugins.sh index 868bbc74e..4374d7ef0 100644 --- a/registry/coder/modules/jetbrains/scripts/install_plugins.sh +++ b/registry/coder/modules/jetbrains/scripts/install_plugins.sh @@ -164,4 +164,4 @@ if [ ${#pending_codes[@]} -gt 0 ]; then exit 1 fi -log "All plugins installed. Exiting." \ No newline at end of file +log "All plugins installed. Exiting." From d5852fdbc99aaf540b599185bec0aa4c51d974c2 Mon Sep 17 00:00:00 2001 From: Harsh panwar Date: Sat, 28 Feb 2026 11:44:00 +0530 Subject: [PATCH 3/3] fix: update version --- registry/coder/modules/jetbrains/README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/registry/coder/modules/jetbrains/README.md b/registry/coder/modules/jetbrains/README.md index d28c5b9c2..73bacfbb2 100644 --- a/registry/coder/modules/jetbrains/README.md +++ b/registry/coder/modules/jetbrains/README.md @@ -14,7 +14,7 @@ This module adds JetBrains IDE buttons to launch IDEs directly from the dashboar module "jetbrains" { count = data.coder_workspace.me.start_count source = "registry.coder.com/coder/jetbrains/coder" - version = "1.3.0" + version = "1.4.0" agent_id = coder_agent.main.id folder = "/home/coder/project" } @@ -39,7 +39,7 @@ When `default` contains IDE codes, those IDEs are created directly without user module "jetbrains" { count = data.coder_workspace.me.start_count source = "registry.coder.com/coder/jetbrains/coder" - version = "1.3.0" + version = "1.4.0" agent_id = coder_agent.main.id folder = "/home/coder/project" default = ["PY", "IU"] # Pre-configure PyCharm and IntelliJ IDEA @@ -52,7 +52,7 @@ module "jetbrains" { module "jetbrains" { count = data.coder_workspace.me.start_count source = "registry.coder.com/coder/jetbrains/coder" - version = "1.3.0" + version = "1.4.0" agent_id = coder_agent.main.id folder = "/home/coder/project" # Show parameter with limited options @@ -66,7 +66,7 @@ module "jetbrains" { module "jetbrains" { count = data.coder_workspace.me.start_count source = "registry.coder.com/coder/jetbrains/coder" - version = "1.3.0" + version = "1.4.0" agent_id = coder_agent.main.id folder = "/home/coder/project" default = ["IU", "PY"] @@ -81,7 +81,7 @@ module "jetbrains" { module "jetbrains" { count = data.coder_workspace.me.start_count source = "registry.coder.com/coder/jetbrains/coder" - version = "1.3.0" + version = "1.4.0" agent_id = coder_agent.main.id folder = "/workspace/project" @@ -108,7 +108,7 @@ module "jetbrains" { module "jetbrains_pycharm" { count = data.coder_workspace.me.start_count source = "registry.coder.com/coder/jetbrains/coder" - version = "1.3.0" + version = "1.4.0" agent_id = coder_agent.main.id folder = "/workspace/project" @@ -128,7 +128,7 @@ Add helpful tooltip text that appears when users hover over the IDE app buttons: module "jetbrains" { count = data.coder_workspace.me.start_count source = "registry.coder.com/coder/jetbrains/coder" - version = "1.3.0" + version = "1.4.0" agent_id = coder_agent.main.id folder = "/home/coder/project" default = ["IU", "PY"] @@ -165,7 +165,7 @@ module "jetbrains" { } ``` -> [!IMPORTANT] \ +> [!IMPORTANT] > After installing the IDE, restart the workspace. > When the workspace starts again, the scripts will detect the installed IDE and automatically install the configured plugins. >