From 04494ba262428e0cae6c57c44013b5c9096fb9b0 Mon Sep 17 00:00:00 2001 From: Ashutosh0x Date: Sat, 10 Jan 2026 01:22:59 +0530 Subject: [PATCH 1/3] feat: Add Parsec cloud gaming integration module Adds a new Coder module for Parsec remote desktop access. Features: - Automatic Parsec installation on workspace start - Headless mode support for virtual displays - Auto-start configuration - Documentation and tests included Fixes coder/registry#205 --- registry/Ashutosh0x/.images/avatar.png | Bin 0 -> 14589 bytes registry/Ashutosh0x/README.md | 11 ++ registry/Ashutosh0x/modules/parsec/README.md | 68 ++++++++ registry/Ashutosh0x/modules/parsec/main.tf | 159 ++++++++++++++++++ .../Ashutosh0x/modules/parsec/main.tftest.hcl | 45 +++++ 5 files changed, 283 insertions(+) create mode 100644 registry/Ashutosh0x/.images/avatar.png create mode 100644 registry/Ashutosh0x/README.md create mode 100644 registry/Ashutosh0x/modules/parsec/README.md create mode 100644 registry/Ashutosh0x/modules/parsec/main.tf create mode 100644 registry/Ashutosh0x/modules/parsec/main.tftest.hcl diff --git a/registry/Ashutosh0x/.images/avatar.png b/registry/Ashutosh0x/.images/avatar.png new file mode 100644 index 0000000000000000000000000000000000000000..076b9d3b3e624a9751224d7e165605fafa42a07b GIT binary patch literal 14589 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 literal 0 HcmV?d00001 diff --git a/registry/Ashutosh0x/README.md b/registry/Ashutosh0x/README.md new file mode 100644 index 000000000..0c47d4ef1 --- /dev/null +++ b/registry/Ashutosh0x/README.md @@ -0,0 +1,11 @@ +--- +display_name: "Ashutosh Kumar" +bio: "Open source developer passionate about cloud infrastructure and developer tools" +avatar: "./.images/avatar.png" +github: "Ashutosh0x" +status: "community" +--- + +# Ashutosh Kumar + +Open source developer passionate about cloud infrastructure and developer tools. diff --git a/registry/Ashutosh0x/modules/parsec/README.md b/registry/Ashutosh0x/modules/parsec/README.md new file mode 100644 index 000000000..4fba2fe1f --- /dev/null +++ b/registry/Ashutosh0x/modules/parsec/README.md @@ -0,0 +1,68 @@ +--- +display_name: Parsec +description: Low-latency remote desktop access using Parsec for cloud gaming and remote work +icon: ../../../../.icons/desktop.svg +verified: false +tags: [remote-desktop, parsec, gaming, streaming, low-latency] +--- + +# Parsec + +Enable low-latency remote desktop access to Coder workspaces using [Parsec](https://parsec.app/). + +Parsec is a remote desktop solution optimized for low-latency streaming, making it ideal for: +- Cloud gaming +- Remote development with GPU-accelerated workloads +- Video editing and design work +- Any task requiring responsive remote access + +```tf +module "parsec" { + source = "registry.coder.com/Ashutosh0x/parsec/coder" + version = "1.0.0" + agent_id = coder_agent.main.id +} +``` + +## Prerequisites + +- A Parsec account (free or paid) +- Linux workspace with desktop environment (for GUI access) +- GPU recommended for optimal performance + +## Examples + +### Basic Usage + +```tf +module "parsec" { + source = "registry.coder.com/Ashutosh0x/parsec/coder" + version = "1.0.0" + agent_id = coder_agent.main.id +} +``` + +### With Custom Configuration + +```tf +module "parsec" { + source = "registry.coder.com/Ashutosh0x/parsec/coder" + version = "1.0.0" + agent_id = coder_agent.main.id + display_name = "Remote Desktop" + headless = true +} +``` + +## Features + +- **Automatic Installation**: Parsec is installed automatically on workspace start +- **Headless Mode**: Run Parsec without a physical display attached +- **Auto-start**: Parsec service starts automatically with the workspace +- **Low Latency**: Optimized for responsive remote access + +## Notes + +- Users must authenticate with their Parsec account after first connection +- For best performance, use a workspace with a dedicated GPU +- Parsec supports both hardware and software encoding diff --git a/registry/Ashutosh0x/modules/parsec/main.tf b/registry/Ashutosh0x/modules/parsec/main.tf new file mode 100644 index 000000000..dbb3beca2 --- /dev/null +++ b/registry/Ashutosh0x/modules/parsec/main.tf @@ -0,0 +1,159 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + coder = { + source = "coder/coder" + version = ">= 2.0" + } + } +} + +variable "agent_id" { + type = string + description = "The ID of a Coder agent." +} + +variable "display_name" { + type = string + description = "The display name for the Parsec application." + default = "Parsec" +} + +variable "slug" { + type = string + description = "The slug for the Parsec application." + default = "parsec" +} + +variable "icon" { + type = string + description = "The icon for the Parsec application." + default = "/icon/desktop.svg" +} + +variable "order" { + type = number + description = "The order determines the position of app in the UI presentation." + default = null +} + +variable "group" { + type = string + description = "The name of a group that this app belongs to." + default = null +} + +variable "headless" { + type = bool + description = "Run Parsec in headless mode (without physical display)." + default = true +} + +variable "auto_start" { + type = bool + description = "Automatically start Parsec service on workspace start." + default = true +} + +resource "coder_script" "parsec" { + agent_id = var.agent_id + display_name = "Parsec Installation" + icon = var.icon + + script = <<-EOT + #!/bin/bash + set -e + + echo "=== Installing Parsec ===" + + # Check if Parsec is already installed + if command -v parsecd &> /dev/null; then + echo "Parsec is already installed" + else + echo "Downloading and installing Parsec..." + + # Detect architecture + ARCH=$(uname -m) + case $ARCH in + x86_64) + PARSEC_URL="https://builds.parsec.app/package/parsec-linux.deb" + ;; + aarch64) + echo "ARM64 architecture detected, using alternative package" + PARSEC_URL="https://builds.parsec.app/package/parsec-linux.deb" + ;; + *) + echo "Unsupported architecture: $ARCH" + exit 1 + ;; + esac + + # Download Parsec + cd /tmp + curl -fsSL -o parsec-linux.deb "$PARSEC_URL" + + # Install Parsec + if command -v apt-get &> /dev/null; then + sudo apt-get update + sudo apt-get install -y ./parsec-linux.deb + elif command -v dpkg &> /dev/null; then + sudo dpkg -i parsec-linux.deb || sudo apt-get install -f -y + else + echo "Unsupported package manager. Please install Parsec manually." + exit 1 + fi + + rm -f parsec-linux.deb + echo "Parsec installed successfully" + fi + + # Configure headless mode if enabled + if [ "${var.headless}" = "true" ]; then + echo "Configuring headless mode..." + mkdir -p ~/.parsec + + # Create config for headless operation + cat > ~/.parsec/config.txt < /dev/null; then + nohup parsecd app_daemon=1 &> /tmp/parsec.log & + echo "Parsec daemon started" + else + echo "Warning: parsecd not found in PATH" + fi + fi + + echo "=== Parsec setup complete ===" + echo "Open the Parsec app on your local machine to connect" + echo "You will need to log in with your Parsec account" + EOT + + run_on_start = true +} + +resource "coder_app" "parsec_docs" { + agent_id = var.agent_id + display_name = "Parsec Docs" + slug = "parsec-docs" + icon = var.icon + url = "https://support.parsec.app/hc/en-us" + external = true + order = var.order + group = var.group +} + +output "parsec_status" { + value = "Parsec is configured. Connect using the Parsec app." + description = "Status message for Parsec module" +} diff --git a/registry/Ashutosh0x/modules/parsec/main.tftest.hcl b/registry/Ashutosh0x/modules/parsec/main.tftest.hcl new file mode 100644 index 000000000..742d62648 --- /dev/null +++ b/registry/Ashutosh0x/modules/parsec/main.tftest.hcl @@ -0,0 +1,45 @@ +run "test_parsec_defaults" { + command = plan + + variables { + agent_id = "test-agent-id" + } + + assert { + condition = coder_script.parsec.display_name == "Parsec Installation" + error_message = "Display name should be 'Parsec Installation'" + } + + assert { + condition = coder_script.parsec.run_on_start == true + error_message = "Script should run on start" + } +} + +run "test_parsec_custom_display_name" { + command = plan + + variables { + agent_id = "test-agent-id" + display_name = "Custom Parsec" + } + + assert { + condition = coder_app.parsec_docs.display_name == "Parsec Docs" + error_message = "Docs app display name should be 'Parsec Docs'" + } +} + +run "test_parsec_headless_disabled" { + command = plan + + variables { + agent_id = "test-agent-id" + headless = false + } + + assert { + condition = var.headless == false + error_message = "Headless should be false" + } +} From fcc365d6a6d2e0e5fa6f62e2da47d7be11834890 Mon Sep 17 00:00:00 2001 From: Ashutosh0x Date: Sat, 10 Jan 2026 01:35:13 +0530 Subject: [PATCH 2/3] feat: Add JetBrains plugin pre-installation module Adds a module to automatically pre-install JetBrains IDE plugins on workspace start. Features: - Download plugins from JetBrains Marketplace - Support for multiple IDEs (IntelliJ, PyCharm, GoLand, etc.) - Configurable plugin list via Terraform variable - Works with JetBrains Toolbox and standalone installs Fixes coder/registry#208 --- .../modules/jetbrains-plugins/README.md | 110 ++++++++++ .../modules/jetbrains-plugins/main.tf | 198 ++++++++++++++++++ .../modules/jetbrains-plugins/main.tftest.hcl | 62 ++++++ 3 files changed, 370 insertions(+) create mode 100644 registry/Ashutosh0x/modules/jetbrains-plugins/README.md create mode 100644 registry/Ashutosh0x/modules/jetbrains-plugins/main.tf create mode 100644 registry/Ashutosh0x/modules/jetbrains-plugins/main.tftest.hcl diff --git a/registry/Ashutosh0x/modules/jetbrains-plugins/README.md b/registry/Ashutosh0x/modules/jetbrains-plugins/README.md new file mode 100644 index 000000000..946941730 --- /dev/null +++ b/registry/Ashutosh0x/modules/jetbrains-plugins/README.md @@ -0,0 +1,110 @@ +--- +display_name: JetBrains Plugins +description: Pre-install JetBrains IDE plugins in Coder workspaces automatically +icon: ../../../../.icons/jetbrains-toolbox.svg +verified: false +tags: [jetbrains, ide, plugins, development, intellij, pycharm, goland] +--- + +# JetBrains Plugins + +Automatically pre-install JetBrains IDE plugins in Coder workspaces on startup. + +This module downloads and installs plugins from the [JetBrains Marketplace](https://plugins.jetbrains.com/) to your IDE's plugins directory, so they're ready when you open the IDE. + +```tf +module "jetbrains_plugins" { + source = "registry.coder.com/Ashutosh0x/jetbrains-plugins/coder" + version = "1.0.0" + agent_id = coder_agent.main.id + + plugins = [ + "org.jetbrains.plugins.github", + "com.intellij.kubernetes" + ] + + ide_product_codes = ["IU", "GO"] +} +``` + +## Finding Plugin IDs + +1. Go to [JetBrains Marketplace](https://plugins.jetbrains.com/) +2. Search for your plugin +3. The plugin ID is in the URL or on the plugin page + +### Popular Plugin IDs + +| Plugin | ID | +|--------|-----| +| GitHub | `org.jetbrains.plugins.github` | +| Kubernetes | `com.intellij.kubernetes` | +| Docker | `Docker` | +| Rust | `org.rust.lang` | +| Go | `org.jetbrains.plugins.go` | +| Python | `Pythonid` | +| GitToolBox | `zielu.gittoolbox` | +| Rainbow Brackets | `izhangzhihao.rainbow.brackets` | +| Material Theme UI | `com.chrisrm.idea.MaterialThemeUI` | + +## IDE Product Codes + +| Code | IDE | +|------|-----| +| IU | IntelliJ IDEA Ultimate | +| IC | IntelliJ IDEA Community | +| PY | PyCharm Professional | +| PC | PyCharm Community | +| GO | GoLand | +| WS | WebStorm | +| PS | PhpStorm | +| RD | Rider | +| CL | CLion | +| RM | RubyMine | +| RR | RustRover | + +## Examples + +### Install plugins for multiple IDEs + +```tf +module "jetbrains_plugins" { + source = "registry.coder.com/Ashutosh0x/jetbrains-plugins/coder" + version = "1.0.0" + agent_id = coder_agent.main.id + + plugins = [ + "zielu.gittoolbox", + "izhangzhihao.rainbow.brackets" + ] + + ide_product_codes = ["IU", "PY", "GO", "WS"] +} +``` + +### With custom plugins directory + +```tf +module "jetbrains_plugins" { + source = "registry.coder.com/Ashutosh0x/jetbrains-plugins/coder" + version = "1.0.0" + agent_id = coder_agent.main.id + + plugins = ["Docker"] + plugins_dir = "/home/coder/.jetbrains/plugins" +} +``` + +## How It Works + +1. On workspace start, the module runs a script that: + - Creates plugin directories for each target IDE + - Downloads plugins from JetBrains Marketplace + - Extracts them to the IDE's plugins folder +2. When you open the IDE, plugins are already available + +## Notes + +- Works with JetBrains Toolbox and standalone IDE installations +- Plugins are downloaded only if not already present +- Supports both Linux and macOS workspaces diff --git a/registry/Ashutosh0x/modules/jetbrains-plugins/main.tf b/registry/Ashutosh0x/modules/jetbrains-plugins/main.tf new file mode 100644 index 000000000..42653c4a6 --- /dev/null +++ b/registry/Ashutosh0x/modules/jetbrains-plugins/main.tf @@ -0,0 +1,198 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + coder = { + source = "coder/coder" + version = ">= 2.0" + } + } +} + +variable "agent_id" { + type = string + description = "The ID of a Coder agent." +} + +variable "plugins" { + type = list(string) + description = "List of JetBrains plugin IDs to pre-install. Find plugin IDs at https://plugins.jetbrains.com/" + default = [] + # Example plugin IDs: + # - "org.jetbrains.plugins.github" (GitHub) + # - "com.intellij.kubernetes" (Kubernetes) + # - "org.rust.lang" (Rust) + # - "Pythonid" (Python) +} + +variable "ide_product_codes" { + type = list(string) + description = "List of IDE product codes to configure plugins for. e.g. ['IU', 'PY', 'GO']" + default = ["IU"] + validation { + condition = alltrue([ + for code in var.ide_product_codes : contains( + ["CL", "GO", "IU", "IC", "PS", "PY", "PC", "RD", "RM", "RR", "WS"], code + ) + ]) + error_message = "Invalid product code. Valid codes: CL, GO, IU, IC, PS, PY, PC, RD, RM, RR, WS" + } +} + +variable "plugins_dir" { + type = string + description = "Custom plugins directory. If empty, uses default JetBrains Toolbox location." + default = "" +} + +variable "download_timeout" { + type = number + description = "Timeout in seconds for downloading plugins." + default = 300 +} + +# Map of IDE product codes to their config directory names +locals { + ide_config_dirs = { + "CL" = "CLion" + "GO" = "GoLand" + "IU" = "IntelliJIdea" + "IC" = "IdeaIC" + "PS" = "PhpStorm" + "PY" = "PyCharm" + "PC" = "PyCharmCE" + "RD" = "Rider" + "RM" = "RubyMine" + "RR" = "RustRover" + "WS" = "WebStorm" + } + + # Generate plugin install script + plugins_json = jsonencode(var.plugins) + ides_json = jsonencode(var.ide_product_codes) +} + +resource "coder_script" "jetbrains_plugins" { + agent_id = var.agent_id + display_name = "JetBrains Plugin Installer" + icon = "/icon/jetbrains-toolbox.svg" + + script = <<-EOT + #!/bin/bash + set -e + + echo "=== JetBrains Plugin Pre-installer ===" + + PLUGINS='${local.plugins_json}' + IDES='${local.ides_json}' + CUSTOM_DIR='${var.plugins_dir}' + TIMEOUT=${var.download_timeout} + + # Check if any plugins specified + if [ "$PLUGINS" = "[]" ] || [ -z "$PLUGINS" ]; then + echo "No plugins specified, skipping installation." + exit 0 + fi + + echo "Plugins to install: $PLUGINS" + echo "Target IDEs: $IDES" + + # Determine plugins directory + if [ -n "$CUSTOM_DIR" ]; then + PLUGINS_BASE="$CUSTOM_DIR" + else + # Default JetBrains Toolbox locations + if [ -d "$HOME/.local/share/JetBrains/Toolbox" ]; then + PLUGINS_BASE="$HOME/.local/share/JetBrains/Toolbox/apps" + elif [ -d "$HOME/Library/Application Support/JetBrains/Toolbox" ]; then + PLUGINS_BASE="$HOME/Library/Application Support/JetBrains/Toolbox/apps" + else + # Fallback to config directory for standalone installs + PLUGINS_BASE="$HOME/.config/JetBrains" + fi + fi + + echo "Plugins base directory: $PLUGINS_BASE" + + # Create IDE config directories for each target IDE + declare -A IDE_NAMES=( + ["CL"]="CLion" + ["GO"]="GoLand" + ["IU"]="IntelliJIdea" + ["IC"]="IdeaIC" + ["PS"]="PhpStorm" + ["PY"]="PyCharm" + ["PC"]="PyCharmCE" + ["RD"]="Rider" + ["RM"]="RubyMine" + ["RR"]="RustRover" + ["WS"]="WebStorm" + ) + + # Parse IDE list + IDE_LIST=$(echo "$IDES" | tr -d '[]"' | tr ',' ' ') + + for IDE_CODE in $IDE_LIST; do + IDE_NAME="$${IDE_NAMES[$IDE_CODE]}" + if [ -z "$IDE_NAME" ]; then + echo "Warning: Unknown IDE code $IDE_CODE, skipping" + continue + fi + + # Find or create plugins directory for this IDE + IDE_PLUGINS_DIR="$PLUGINS_BASE/$IDE_NAME/plugins" + mkdir -p "$IDE_PLUGINS_DIR" + echo "Created plugins directory: $IDE_PLUGINS_DIR" + + # Parse plugin list and download each + PLUGIN_LIST=$(echo "$PLUGINS" | tr -d '[]"' | tr ',' ' ') + + for PLUGIN_ID in $PLUGIN_LIST; do + PLUGIN_ID=$(echo "$PLUGIN_ID" | xargs) # trim whitespace + + if [ -z "$PLUGIN_ID" ]; then + continue + fi + + echo "Installing plugin: $PLUGIN_ID for $IDE_NAME" + + # Check if plugin already exists + if [ -d "$IDE_PLUGINS_DIR/$PLUGIN_ID" ]; then + echo " Plugin $PLUGIN_ID already installed, skipping" + continue + fi + + # Download plugin from JetBrains Marketplace + PLUGIN_URL="https://plugins.jetbrains.com/pluginManager?action=download&id=$PLUGIN_ID" + PLUGIN_ZIP="/tmp/$${PLUGIN_ID}.zip" + + echo " Downloading from JetBrains Marketplace..." + if curl -fsSL --max-time $TIMEOUT -o "$PLUGIN_ZIP" "$PLUGIN_URL" 2>/dev/null; then + # Extract plugin + if unzip -q -o "$PLUGIN_ZIP" -d "$IDE_PLUGINS_DIR" 2>/dev/null; then + echo " ✓ Plugin $PLUGIN_ID installed successfully" + else + echo " ⚠ Failed to extract plugin $PLUGIN_ID" + fi + rm -f "$PLUGIN_ZIP" + else + echo " ⚠ Failed to download plugin $PLUGIN_ID" + fi + done + done + + echo "=== Plugin installation complete ===" + EOT + + run_on_start = true +} + +output "installed_plugins" { + description = "List of plugins configured for installation" + value = var.plugins +} + +output "target_ides" { + description = "List of IDEs configured for plugin installation" + value = var.ide_product_codes +} diff --git a/registry/Ashutosh0x/modules/jetbrains-plugins/main.tftest.hcl b/registry/Ashutosh0x/modules/jetbrains-plugins/main.tftest.hcl new file mode 100644 index 000000000..4ee100e13 --- /dev/null +++ b/registry/Ashutosh0x/modules/jetbrains-plugins/main.tftest.hcl @@ -0,0 +1,62 @@ +run "test_no_plugins" { + command = plan + + variables { + agent_id = "test-agent-id" + plugins = [] + } + + assert { + condition = coder_script.jetbrains_plugins.display_name == "JetBrains Plugin Installer" + error_message = "Display name should be 'JetBrains Plugin Installer'" + } + + assert { + condition = coder_script.jetbrains_plugins.run_on_start == true + error_message = "Script should run on start" + } +} + +run "test_with_plugins" { + command = plan + + variables { + agent_id = "test-agent-id" + plugins = ["org.jetbrains.plugins.github", "Docker"] + } + + assert { + condition = length(var.plugins) == 2 + error_message = "Should have 2 plugins configured" + } +} + +run "test_multiple_ides" { + command = plan + + variables { + agent_id = "test-agent-id" + plugins = ["Docker"] + ide_product_codes = ["IU", "GO", "PY"] + } + + assert { + condition = length(var.ide_product_codes) == 3 + error_message = "Should have 3 IDEs configured" + } +} + +run "test_custom_plugins_dir" { + command = plan + + variables { + agent_id = "test-agent-id" + plugins = ["Docker"] + plugins_dir = "/custom/plugins/path" + } + + assert { + condition = var.plugins_dir == "/custom/plugins/path" + error_message = "Custom plugins dir should be set" + } +} From cdb7f17a44e0b3838bdf9f6f98a3a5c7bf1e558f Mon Sep 17 00:00:00 2001 From: Ashutosh0x Date: Sat, 10 Jan 2026 01:48:24 +0530 Subject: [PATCH 3/3] feat: integrate JetBrains plugin pre-installation into existing module This change adds the ability to pre-install plugins directly within the official JetBrains module by adding 'plugins' and 'plugins_dir' variables. A coder_script is now used to handle the downloading and extraction of plugins from the JetBrains Marketplace on workspace start. As requested by maintainer @matifali, this integrates the logic into the existing module instead of creating a separate one. Closes #208 --- .../modules/jetbrains-plugins/README.md | 110 ---------- .../modules/jetbrains-plugins/main.tf | 198 ------------------ .../modules/jetbrains-plugins/main.tftest.hcl | 62 ------ registry/coder/modules/jetbrains/README.md | 21 ++ .../modules/jetbrains/jetbrains.tftest.hcl | 32 +++ registry/coder/modules/jetbrains/main.tf | 113 ++++++++++ 6 files changed, 166 insertions(+), 370 deletions(-) delete mode 100644 registry/Ashutosh0x/modules/jetbrains-plugins/README.md delete mode 100644 registry/Ashutosh0x/modules/jetbrains-plugins/main.tf delete mode 100644 registry/Ashutosh0x/modules/jetbrains-plugins/main.tftest.hcl diff --git a/registry/Ashutosh0x/modules/jetbrains-plugins/README.md b/registry/Ashutosh0x/modules/jetbrains-plugins/README.md deleted file mode 100644 index 946941730..000000000 --- a/registry/Ashutosh0x/modules/jetbrains-plugins/README.md +++ /dev/null @@ -1,110 +0,0 @@ ---- -display_name: JetBrains Plugins -description: Pre-install JetBrains IDE plugins in Coder workspaces automatically -icon: ../../../../.icons/jetbrains-toolbox.svg -verified: false -tags: [jetbrains, ide, plugins, development, intellij, pycharm, goland] ---- - -# JetBrains Plugins - -Automatically pre-install JetBrains IDE plugins in Coder workspaces on startup. - -This module downloads and installs plugins from the [JetBrains Marketplace](https://plugins.jetbrains.com/) to your IDE's plugins directory, so they're ready when you open the IDE. - -```tf -module "jetbrains_plugins" { - source = "registry.coder.com/Ashutosh0x/jetbrains-plugins/coder" - version = "1.0.0" - agent_id = coder_agent.main.id - - plugins = [ - "org.jetbrains.plugins.github", - "com.intellij.kubernetes" - ] - - ide_product_codes = ["IU", "GO"] -} -``` - -## Finding Plugin IDs - -1. Go to [JetBrains Marketplace](https://plugins.jetbrains.com/) -2. Search for your plugin -3. The plugin ID is in the URL or on the plugin page - -### Popular Plugin IDs - -| Plugin | ID | -|--------|-----| -| GitHub | `org.jetbrains.plugins.github` | -| Kubernetes | `com.intellij.kubernetes` | -| Docker | `Docker` | -| Rust | `org.rust.lang` | -| Go | `org.jetbrains.plugins.go` | -| Python | `Pythonid` | -| GitToolBox | `zielu.gittoolbox` | -| Rainbow Brackets | `izhangzhihao.rainbow.brackets` | -| Material Theme UI | `com.chrisrm.idea.MaterialThemeUI` | - -## IDE Product Codes - -| Code | IDE | -|------|-----| -| IU | IntelliJ IDEA Ultimate | -| IC | IntelliJ IDEA Community | -| PY | PyCharm Professional | -| PC | PyCharm Community | -| GO | GoLand | -| WS | WebStorm | -| PS | PhpStorm | -| RD | Rider | -| CL | CLion | -| RM | RubyMine | -| RR | RustRover | - -## Examples - -### Install plugins for multiple IDEs - -```tf -module "jetbrains_plugins" { - source = "registry.coder.com/Ashutosh0x/jetbrains-plugins/coder" - version = "1.0.0" - agent_id = coder_agent.main.id - - plugins = [ - "zielu.gittoolbox", - "izhangzhihao.rainbow.brackets" - ] - - ide_product_codes = ["IU", "PY", "GO", "WS"] -} -``` - -### With custom plugins directory - -```tf -module "jetbrains_plugins" { - source = "registry.coder.com/Ashutosh0x/jetbrains-plugins/coder" - version = "1.0.0" - agent_id = coder_agent.main.id - - plugins = ["Docker"] - plugins_dir = "/home/coder/.jetbrains/plugins" -} -``` - -## How It Works - -1. On workspace start, the module runs a script that: - - Creates plugin directories for each target IDE - - Downloads plugins from JetBrains Marketplace - - Extracts them to the IDE's plugins folder -2. When you open the IDE, plugins are already available - -## Notes - -- Works with JetBrains Toolbox and standalone IDE installations -- Plugins are downloaded only if not already present -- Supports both Linux and macOS workspaces diff --git a/registry/Ashutosh0x/modules/jetbrains-plugins/main.tf b/registry/Ashutosh0x/modules/jetbrains-plugins/main.tf deleted file mode 100644 index 42653c4a6..000000000 --- a/registry/Ashutosh0x/modules/jetbrains-plugins/main.tf +++ /dev/null @@ -1,198 +0,0 @@ -terraform { - required_version = ">= 1.0" - - required_providers { - coder = { - source = "coder/coder" - version = ">= 2.0" - } - } -} - -variable "agent_id" { - type = string - description = "The ID of a Coder agent." -} - -variable "plugins" { - type = list(string) - description = "List of JetBrains plugin IDs to pre-install. Find plugin IDs at https://plugins.jetbrains.com/" - default = [] - # Example plugin IDs: - # - "org.jetbrains.plugins.github" (GitHub) - # - "com.intellij.kubernetes" (Kubernetes) - # - "org.rust.lang" (Rust) - # - "Pythonid" (Python) -} - -variable "ide_product_codes" { - type = list(string) - description = "List of IDE product codes to configure plugins for. e.g. ['IU', 'PY', 'GO']" - default = ["IU"] - validation { - condition = alltrue([ - for code in var.ide_product_codes : contains( - ["CL", "GO", "IU", "IC", "PS", "PY", "PC", "RD", "RM", "RR", "WS"], code - ) - ]) - error_message = "Invalid product code. Valid codes: CL, GO, IU, IC, PS, PY, PC, RD, RM, RR, WS" - } -} - -variable "plugins_dir" { - type = string - description = "Custom plugins directory. If empty, uses default JetBrains Toolbox location." - default = "" -} - -variable "download_timeout" { - type = number - description = "Timeout in seconds for downloading plugins." - default = 300 -} - -# Map of IDE product codes to their config directory names -locals { - ide_config_dirs = { - "CL" = "CLion" - "GO" = "GoLand" - "IU" = "IntelliJIdea" - "IC" = "IdeaIC" - "PS" = "PhpStorm" - "PY" = "PyCharm" - "PC" = "PyCharmCE" - "RD" = "Rider" - "RM" = "RubyMine" - "RR" = "RustRover" - "WS" = "WebStorm" - } - - # Generate plugin install script - plugins_json = jsonencode(var.plugins) - ides_json = jsonencode(var.ide_product_codes) -} - -resource "coder_script" "jetbrains_plugins" { - agent_id = var.agent_id - display_name = "JetBrains Plugin Installer" - icon = "/icon/jetbrains-toolbox.svg" - - script = <<-EOT - #!/bin/bash - set -e - - echo "=== JetBrains Plugin Pre-installer ===" - - PLUGINS='${local.plugins_json}' - IDES='${local.ides_json}' - CUSTOM_DIR='${var.plugins_dir}' - TIMEOUT=${var.download_timeout} - - # Check if any plugins specified - if [ "$PLUGINS" = "[]" ] || [ -z "$PLUGINS" ]; then - echo "No plugins specified, skipping installation." - exit 0 - fi - - echo "Plugins to install: $PLUGINS" - echo "Target IDEs: $IDES" - - # Determine plugins directory - if [ -n "$CUSTOM_DIR" ]; then - PLUGINS_BASE="$CUSTOM_DIR" - else - # Default JetBrains Toolbox locations - if [ -d "$HOME/.local/share/JetBrains/Toolbox" ]; then - PLUGINS_BASE="$HOME/.local/share/JetBrains/Toolbox/apps" - elif [ -d "$HOME/Library/Application Support/JetBrains/Toolbox" ]; then - PLUGINS_BASE="$HOME/Library/Application Support/JetBrains/Toolbox/apps" - else - # Fallback to config directory for standalone installs - PLUGINS_BASE="$HOME/.config/JetBrains" - fi - fi - - echo "Plugins base directory: $PLUGINS_BASE" - - # Create IDE config directories for each target IDE - declare -A IDE_NAMES=( - ["CL"]="CLion" - ["GO"]="GoLand" - ["IU"]="IntelliJIdea" - ["IC"]="IdeaIC" - ["PS"]="PhpStorm" - ["PY"]="PyCharm" - ["PC"]="PyCharmCE" - ["RD"]="Rider" - ["RM"]="RubyMine" - ["RR"]="RustRover" - ["WS"]="WebStorm" - ) - - # Parse IDE list - IDE_LIST=$(echo "$IDES" | tr -d '[]"' | tr ',' ' ') - - for IDE_CODE in $IDE_LIST; do - IDE_NAME="$${IDE_NAMES[$IDE_CODE]}" - if [ -z "$IDE_NAME" ]; then - echo "Warning: Unknown IDE code $IDE_CODE, skipping" - continue - fi - - # Find or create plugins directory for this IDE - IDE_PLUGINS_DIR="$PLUGINS_BASE/$IDE_NAME/plugins" - mkdir -p "$IDE_PLUGINS_DIR" - echo "Created plugins directory: $IDE_PLUGINS_DIR" - - # Parse plugin list and download each - PLUGIN_LIST=$(echo "$PLUGINS" | tr -d '[]"' | tr ',' ' ') - - for PLUGIN_ID in $PLUGIN_LIST; do - PLUGIN_ID=$(echo "$PLUGIN_ID" | xargs) # trim whitespace - - if [ -z "$PLUGIN_ID" ]; then - continue - fi - - echo "Installing plugin: $PLUGIN_ID for $IDE_NAME" - - # Check if plugin already exists - if [ -d "$IDE_PLUGINS_DIR/$PLUGIN_ID" ]; then - echo " Plugin $PLUGIN_ID already installed, skipping" - continue - fi - - # Download plugin from JetBrains Marketplace - PLUGIN_URL="https://plugins.jetbrains.com/pluginManager?action=download&id=$PLUGIN_ID" - PLUGIN_ZIP="/tmp/$${PLUGIN_ID}.zip" - - echo " Downloading from JetBrains Marketplace..." - if curl -fsSL --max-time $TIMEOUT -o "$PLUGIN_ZIP" "$PLUGIN_URL" 2>/dev/null; then - # Extract plugin - if unzip -q -o "$PLUGIN_ZIP" -d "$IDE_PLUGINS_DIR" 2>/dev/null; then - echo " ✓ Plugin $PLUGIN_ID installed successfully" - else - echo " ⚠ Failed to extract plugin $PLUGIN_ID" - fi - rm -f "$PLUGIN_ZIP" - else - echo " ⚠ Failed to download plugin $PLUGIN_ID" - fi - done - done - - echo "=== Plugin installation complete ===" - EOT - - run_on_start = true -} - -output "installed_plugins" { - description = "List of plugins configured for installation" - value = var.plugins -} - -output "target_ides" { - description = "List of IDEs configured for plugin installation" - value = var.ide_product_codes -} diff --git a/registry/Ashutosh0x/modules/jetbrains-plugins/main.tftest.hcl b/registry/Ashutosh0x/modules/jetbrains-plugins/main.tftest.hcl deleted file mode 100644 index 4ee100e13..000000000 --- a/registry/Ashutosh0x/modules/jetbrains-plugins/main.tftest.hcl +++ /dev/null @@ -1,62 +0,0 @@ -run "test_no_plugins" { - command = plan - - variables { - agent_id = "test-agent-id" - plugins = [] - } - - assert { - condition = coder_script.jetbrains_plugins.display_name == "JetBrains Plugin Installer" - error_message = "Display name should be 'JetBrains Plugin Installer'" - } - - assert { - condition = coder_script.jetbrains_plugins.run_on_start == true - error_message = "Script should run on start" - } -} - -run "test_with_plugins" { - command = plan - - variables { - agent_id = "test-agent-id" - plugins = ["org.jetbrains.plugins.github", "Docker"] - } - - assert { - condition = length(var.plugins) == 2 - error_message = "Should have 2 plugins configured" - } -} - -run "test_multiple_ides" { - command = plan - - variables { - agent_id = "test-agent-id" - plugins = ["Docker"] - ide_product_codes = ["IU", "GO", "PY"] - } - - assert { - condition = length(var.ide_product_codes) == 3 - error_message = "Should have 3 IDEs configured" - } -} - -run "test_custom_plugins_dir" { - command = plan - - variables { - agent_id = "test-agent-id" - plugins = ["Docker"] - plugins_dir = "/custom/plugins/path" - } - - assert { - condition = var.plugins_dir == "/custom/plugins/path" - error_message = "Custom plugins dir should be set" - } -} diff --git a/registry/coder/modules/jetbrains/README.md b/registry/coder/modules/jetbrains/README.md index cf97d127e..0138b75bb 100644 --- a/registry/coder/modules/jetbrains/README.md +++ b/registry/coder/modules/jetbrains/README.md @@ -136,6 +136,27 @@ module "jetbrains" { } ``` +### Plugin Pre-installation + +Automatically pre-install JetBrains IDE plugins on workspace start. Find plugin IDs at [JetBrains Marketplace](https://plugins.jetbrains.com/): + +```tf +module "jetbrains" { + count = data.coder_workspace.me.start_count + source = "registry.coder.com/coder/jetbrains/coder" + version = "1.3.0" + agent_id = coder_agent.main.id + folder = "/home/coder/project" + default = ["IU"] + + # Pre-install GitHub and Kubernetes plugins + plugins = [ + "org.jetbrains.plugins.github", + "com.intellij.kubernetes" + ] +} +``` + ### 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..3275e0e1f 100644 --- a/registry/coder/modules/jetbrains/jetbrains.tftest.hcl +++ b/registry/coder/modules/jetbrains/jetbrains.tftest.hcl @@ -351,3 +351,35 @@ run "validate_output_schema" { error_message = "The ide_metadata output schema has changed. Please update the 'main.tf' and this test." } } + +run "plugins_script_created_when_plugins_provided" { + command = plan + + variables { + agent_id = "foo" + folder = "/home/coder" + default = ["IU"] + plugins = ["org.jetbrains.plugins.github"] + } + + assert { + condition = length(resource.coder_script.jetbrains_plugins) == 1 + error_message = "Expected coder_script.jetbrains_plugins to be created when plugins are provided" + } +} + +run "plugins_script_not_created_when_no_plugins" { + command = plan + + variables { + agent_id = "foo" + folder = "/home/coder" + default = ["IU"] + plugins = [] + } + + assert { + condition = length(resource.coder_script.jetbrains_plugins) == 0 + error_message = "Expected coder_script.jetbrains_plugins not to be created when no plugins are provided" + } +} diff --git a/registry/coder/modules/jetbrains/main.tf b/registry/coder/modules/jetbrains/main.tf index 2fac060f1..658c94676 100644 --- a/registry/coder/modules/jetbrains/main.tf +++ b/registry/coder/modules/jetbrains/main.tf @@ -104,6 +104,18 @@ variable "options" { } } +variable "plugins" { + type = list(string) + description = "A list of JetBrains plugin IDs to pre-install. Find plugin IDs at https://plugins.jetbrains.com/" + default = [] +} + +variable "plugins_dir" { + type = string + description = "A custom directory to install plugins into. If empty, the module will attempt to find the correct directory for each IDE." + default = "" +} + variable "releases_base_link" { type = string description = "URL of the JetBrains releases base link." @@ -270,6 +282,107 @@ resource "coder_app" "jetbrains" { ]) } +resource "coder_script" "jetbrains_plugins" { + count = length(var.plugins) > 0 ? 1 : 0 + agent_id = var.agent_id + display_name = "JetBrains Plugin Installer" + icon = "/icon/jetbrains-toolbox.svg" + run_on_start = true + + script = <<-EOT + #!/bin/bash + set -e + + PLUGINS='${jsonencode(var.plugins)}' + CUSTOM_DIR='${var.plugins_dir}' + IDES='${jsonencode(var.options)}' + + echo "=== JetBrains Plugin Pre-installer ===" + echo "Plugins to install: $PLUGINS" + + # Determine base plugins directory + if [ -n "$CUSTOM_DIR" ]; then + PLUGINS_BASE="$CUSTOM_DIR" + else + if [[ "$OSTYPE" == "linux-gnu"* ]]; then + PLUGINS_BASE="$HOME/.local/share/JetBrains/Toolbox/apps" + elif [[ "$OSTYPE" == "darwin"* ]]; then + PLUGINS_BASE="$HOME/Library/Application Support/JetBrains/Toolbox/apps" + else + PLUGINS_BASE="$HOME/.config/JetBrains" + fi + fi + + # Map of IDE product codes to directory names used by Toolbox + declare -A IDE_DIRS=( + ["CL"]="CLion" + ["GO"]="GoLand" + ["IU"]="IntelliJIdea" + ["IC"]="IdeaIC" + ["PS"]="PhpStorm" + ["PY"]="PyCharm" + ["PC"]="PyCharmCE" + ["RD"]="Rider" + ["RM"]="RubyMine" + ["RR"]="RustRover" + ["WS"]="WebStorm" + ) + + # Convert JSON lists to space-separated strings + PLUGIN_LIST=$(echo "$PLUGINS" | tr -d '[]"' | tr ',' ' ') + IDE_LIST=$(echo "$IDES" | tr -d '[]"' | tr ',' ' ') + + for IDE_CODE in $IDE_LIST; do + IDE_DIR_NAME="$${IDE_DIRS[$IDE_CODE]}" + if [ -z "$IDE_DIR_NAME" ]; then + continue + fi + + # Attempt to find the plugins directory for this IDE version + # Usually: $PLUGINS_BASE/$IDE_DIR_NAME/ch-0/$VERSION/plugins + # Since we don't know the exact version path, we search for the 'plugins' folder + + TARGET_PLUGINS_DIR="" + if [ -d "$PLUGINS_BASE/$IDE_DIR_NAME" ]; then + # Search for plugins subdirectory within the IDE app folder + TARGET_PLUGINS_DIR=$(find "$PLUGINS_BASE/$IDE_DIR_NAME" -type d -name "plugins" | head -n 1) + fi + + # Fallback to standard config-based location if Toolbox structure not found + if [ -z "$TARGET_PLUGINS_DIR" ]; then + TARGET_PLUGINS_DIR="$HOME/.config/JetBrains/$IDE_DIR_NAME/plugins" + fi + + mkdir -p "$TARGET_PLUGINS_DIR" + echo "Targeting plugins directory: $TARGET_PLUGINS_DIR" + + for PLUGIN_ID in $PLUGIN_LIST; do + PLUGIN_ID=$(echo "$PLUGIN_ID" | xargs) # trim whitespace + if [ -z "$PLUGIN_ID" ]; then continue; fi + + if [ -d "$TARGET_PLUGINS_DIR/$PLUGIN_ID" ]; then + echo " ✓ Plugin $PLUGIN_ID already installed for $IDE_CODE" + continue + fi + + echo " Downloading $PLUGIN_ID..." + URL="https://plugins.jetbrains.com/pluginManager?action=download&id=$PLUGIN_ID" + ZIP_FILE="/tmp/$${PLUGIN_ID}.zip" + + if curl -fsSL "$URL" -o "$ZIP_FILE"; then + unzip -q -o "$ZIP_FILE" -d "$TARGET_PLUGINS_DIR" + echo " ✓ Plugin $PLUGIN_ID installed successfully" + rm "$ZIP_FILE" + else + echo " ⚠ Failed to download plugin $PLUGIN_ID" + fi + done + done + + echo "=== Plugin installation complete ===" + EOT +} + output "ide_metadata" { description = "A map of the metadata for each selected JetBrains IDE." value = {