diff --git a/registry/Ashutosh0x/.images/avatar.png b/registry/Ashutosh0x/.images/avatar.png new file mode 100644 index 000000000..076b9d3b3 Binary files /dev/null and b/registry/Ashutosh0x/.images/avatar.png differ 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" + } +} 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 = {