Compare commits

...

7 Commits

Author SHA1 Message Date
Danielle Maywood c551c4d84a refactor(coder-labs/cursor-cli): support terraform provider coder v2.12.0 (#491)
## Description

Updates the module to use the new version of the agentapi module for
Coder 2.28

## Type of Change

- [ ] New module
- [ ] New template
- [ ] Bug fix
- [x] Feature/enhancement
- [ ] Documentation
- [ ] Other

## Module Information

**Path:** `registry/coder-labs/modules/cursor-cli`  
**New version:** `v0.3.0`  
**Breaking change:** [x] Yes [ ] No

## Testing & Validation

- [x] Tests pass (`bun test`)
- [x] Code formatted (`bun fmt`)
- [ ] Changes tested locally

## Related Issues

- https://github.com/coder/internal/issues/1065

## Related PRs

- https://github.com/coder/registry/pull/485

---------

Co-authored-by: DevCats <christofer@coder.com>
2025-12-17 15:12:34 +00:00
imgbot[bot] 4b9da4036a [ImgBot] Optimize images (#608)
Co-authored-by: ImgBotApp <ImgBotHelp@gmail.com>
2025-12-16 23:25:21 +05:00
Excellencedev 96c5f3219d Add Hetzner Cloud server template example (#560)
## Description

This PR adds a template example for the Hetzner Cloud Sever. 


This video shows the provisioning of multiple hetzner instances in coder
with dynamic param enabled, running simultaneously and shown in the
Hetzner console, checking labels on both the OS Filesystem and on the
hetzner console and then shutting down through coder demonstrating clean
up on hetzner and deleting workspaces without errors
DEMO VIDEO:
https://drive.google.com/file/d/1JkhjszCRM9K27XDlMi_2nXtJosoflns_/view?usp=sharing

## Type of Change

- [ ] New module
- [x] New template
- [ ] Bug fix
- [ ] Feature/enhancement
- [ ] Documentation
- [ ] Other


## Template Information

**Path:** `registry/Excellencedev/templates/hetzner-linux`

## Testing & Validation

- [x] Tests pass (`bun test`)
- [x] Code formatted (`bun fmt`)
- [x] Changes tested locally

## Related Issues
/closes #209 
/claim #209

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: DevCats <christofer@coder.com>
2025-12-16 14:03:52 +00:00
Marcin Tojek 146540c1e9 feat: add perplexica module (#596) 2025-12-16 13:00:06 +00:00
35C4n0r a85436fdf4 chore(claude-code): use $HOME variable instead of hardcoded path and remove symlink (#592)
## Description

- Remove hardcoded `/home/coder` path.
- Remove symlink in favour of coder_env "PATH".

## Type of Change

- [ ] New module
- [ ] New template
- [x] Bug fix
- [ ] Feature/enhancement
- [ ] Documentation
- [ ] Other

## Module Information

**Path:** `registry/[coder/modules/claude-code`  
**New version:** `v4.2.7`  
**Breaking change:** [ ] Yes [x] No

## Testing & Validation

- [x] Tests pass (`bun test`)
- [x] Code formatted (`bun fmt`)
- [ ] Changes tested locally

## Related Issues

<!-- Link related issues or write "None" if not applicable -->

---------

Signed-off-by: 35C4n0r <work.jaykumar@gmail.com>
Co-authored-by: DevelopmentCats <christofer@coder.com>
2025-12-15 15:07:14 -06:00
netsgnut aa4890fe62 fix(kasmvnc): update kasmvnc desktop environment list to match upstream (#598)
## Description

This PR makes the following changes to `coder/modules/kasmvnc`:

- Update desktop environment list for the KasmVNC module

Currently upstream supports a number of [additional Desktop
Environments](https://github.com/kasmtech/KasmVNC/blob/v1.4.0/unix/vncserver.man#L56-L67).
The change updates the list so that DEs like Mate are supported. Do note
that `manual` is also supported if `$HOME/.vnc/xstartup` is supplied, so
this has been added as another option, too.

## Type of Change

- [ ] New module
- [ ] New template
- [X] Bug fix
- [ ] Feature/enhancement
- [ ] Documentation
- [ ] Other

## Module Information

<!-- Delete this section if not applicable -->

**Path:** `registry/coder/modules/kasmvnc`
**New version:** `v1.2.7`  
**Breaking change:** [ ] Yes [X] No

## Testing & Validation

- [X] Tests pass (`bun test`)
- [X] Code formatted (`bun fmt`)
- [X] Changes tested locally

## Related Issues

None

Co-authored-by: DevCats <christofer@coder.com>
2025-12-15 13:00:18 -06:00
blinkagent[bot] ab6799ac07 fix(git-clone): use unique temp file for post_clone_script to avoid race condition (#601)
## Summary

Fixes a race condition when multiple `git-clone` modules with
`post_clone_script` run concurrently.

## Problem

All instances of the git-clone module use the same hardcoded
`/tmp/post_clone.sh` path. When multiple modules run concurrently (or
overlap), they collide on the same temp file, causing:

```
rm: cannot remove '/tmp/post_clone.sh': No such file or directory
```

This results in a non-zero exit code, causing the workspace to appear
unhealthy.

## Solution

Use `mktemp` to generate a unique temporary filename for each module
instance:

```bash
POST_CLONE_TMP=$(mktemp /tmp/post_clone_XXXXXX.sh)
```

This ensures each concurrent execution uses its own temp file,
eliminating the race condition.

Fixes #600

---------

Co-authored-by: blink-so[bot] <211532188+blink-so[bot]@users.noreply.github.com>
Co-authored-by: Matyas Danter <mdanter@gmail.com>
2025-12-15 11:19:11 -06:00
38 changed files with 639 additions and 59 deletions
+1
View File
@@ -3,6 +3,7 @@ muc = "muc" # For Munich location code
tyo = "tyo" # For Tokyo location code
Hashi = "Hashi"
HashiCorp = "HashiCorp"
hel = "hel" # For Helsinki location code
mavrickrishi = "mavrickrishi" # Username
mavrick = "mavrick" # Username
inh = "inh" # Option in setpriv command
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.5 KiB

+8
View File
@@ -0,0 +1,8 @@
<svg width="256" height="256" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg">
<circle cx="128" cy="128" r="120" fill="black"/>
<polygon
points="128,70 178,170 78,170"
fill="white"
/>
</svg>

After

Width:  |  Height:  |  Size: 216 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 451 KiB

After

Width:  |  Height:  |  Size: 451 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

+7
View File
@@ -0,0 +1,7 @@
---
display_name: "Excellencedev"
bio: "Love to contribute"
avatar: "./.images/avatar.png"
support_email: "ademiluyisuccessandexcellence@gmail.com"
status: "community"
---
@@ -0,0 +1,32 @@
---
display_name: Hetzner Cloud Server
description: Provision Hetzner Cloud servers as Coder workspaces
icon: ../../../../.icons/hetzner.svg
tags: [vm, linux, hetzner]
---
# Remote Development on Hetzner Cloud (Linux)
Provision Hetzner Cloud servers as [Coder workspaces](https://coder.com/docs/workspaces) with this example template.
> [!WARNING]
> **Workspace Storage Persistence:** When a workspace is stopped, the Hetzner Cloud server instance is stopped but your home volume and stored data persist. This means your files and data remain intact when you resume the workspace.
> [!IMPORTANT]
> **Volume Management & Costs:** Hetzner Cloud volumes persist even when workspaces are stopped and will continue to incur storage costs (€0.0476/GB/month). Volumes are only automatically deleted when the workspace is completely deleted. Monitor your volumes in the [Hetzner Cloud Console](https://console.hetzner.cloud/) to manage costs effectively.
## Prerequisites
To deploy workspaces as Hetzner Cloud servers, you'll need:
- Hetzner Cloud [API token](https://console.hetzner.cloud/projects) (create under Security > API Tokens)
### Authentication
This template assumes that the Coder Provisioner is run in an environment that is authenticated with Hetzner Cloud.
Obtain a Hetzner Cloud API token from your [Hetzner Cloud Console](https://console.hetzner.cloud/projects) and provide it as the `hcloud_token` variable when creating a workspace.
For more authentication options, see the [Terraform provider documentation](https://registry.terraform.io/providers/hetznercloud/hcloud/latest/docs#authentication).
> [!NOTE]
> This template is designed to be a starting point. Edit the Terraform to extend the template to support your use case.
@@ -0,0 +1,62 @@
#cloud-config
users:
- name: ${username}
sudo: ["ALL=(ALL) NOPASSWD:ALL"]
groups: sudo
shell: /bin/bash
packages:
- git
%{ if home_volume_label != "" ~}
fs_setup:
- device: /dev/disk/by-id/scsi-0HC_Volume_${volume_id}
filesystem: ext4
label: ${home_volume_label}
overwrite: false # This prevents reformatting the disk on every boot
mounts:
- [
"/dev/disk/by-id/scsi-0HC_Volume_${volume_id}",
"/home/${username}",
ext4,
"defaults,uid=1000,gid=1000",
]
%{ endif ~}
write_files:
- path: /opt/coder/init
permissions: "0755"
encoding: b64
content: ${init_script}
- path: /etc/systemd/system/coder-agent.service
permissions: "0644"
content: |
[Unit]
Description=Coder Agent
After=network-online.target
Wants=network-online.target
[Service]
User=${username}
ExecStart=/opt/coder/init
Environment=CODER_AGENT_TOKEN=${coder_agent_token}
Restart=always
RestartSec=10
TimeoutStopSec=90
KillMode=process
OOMScoreAdjust=-900
SyslogIdentifier=coder-agent
[Install]
WantedBy=multi-user.target
runcmd:
%{ if home_volume_label != "" ~}
- |
until [ -e /dev/disk/by-id/scsi-0HC_Volume_${volume_id} ]; do
echo "Waiting for volume device..."
sleep 2
done
%{ endif ~}
- mount -a
- chown ${username}:${username} /home/${username}
- systemctl enable coder-agent
- systemctl start coder-agent
@@ -0,0 +1,27 @@
{
"type_meta": {
"cx22": { "cores": 2, "memory_gb": 4, "disk_gb": 40 },
"cx32": { "cores": 4, "memory_gb": 8, "disk_gb": 80 },
"cx42": { "cores": 8, "memory_gb": 16, "disk_gb": 160 },
"cx52": { "cores": 16, "memory_gb": 32, "disk_gb": 320 },
"cpx11": { "cores": 2, "memory_gb": 2, "disk_gb": 40 },
"cpx21": { "cores": 3, "memory_gb": 4, "disk_gb": 80 },
"cpx31": { "cores": 4, "memory_gb": 8, "disk_gb": 160 },
"cpx41": { "cores": 8, "memory_gb": 16, "disk_gb": 240 },
"cpx51": { "cores": 16, "memory_gb": 32, "disk_gb": 360 },
"ccx13": { "cores": 2, "memory_gb": 8, "disk_gb": 80 },
"ccx23": { "cores": 4, "memory_gb": 16, "disk_gb": 160 },
"ccx33": { "cores": 8, "memory_gb": 32, "disk_gb": 240 },
"ccx43": { "cores": 16, "memory_gb": 64, "disk_gb": 360 },
"ccx53": { "cores": 32, "memory_gb": 128, "disk_gb": 600 },
"ccx63": { "cores": 48, "memory_gb": 192, "disk_gb": 960 }
},
"availability": {
"fsn1": ["cpx11", "cpx21", "cpx31", "cpx41", "cpx51", "ccx13", "ccx23", "ccx33"],
"ash": ["cpx11", "cpx21", "cpx31", "cpx41", "cpx51", "ccx13", "ccx23", "ccx33"],
"hel1": ["cx22", "cpx11", "cpx21", "cpx31", "cpx41", "cpx51", "ccx13", "ccx23", "ccx33"],
"hil": ["cpx11", "cpx21", "cpx31", "cpx41", "ccx13", "ccx23", "ccx33"],
"nbg1": ["cx22", "cx32", "cx42", "cx52", "cpx11", "cpx21", "cpx31", "cpx41", "cpx51", "ccx13", "ccx23", "ccx33"],
"sin": ["cpx11", "cpx21", "cpx31", "cpx41", "cpx51", "ccx13", "ccx23", "ccx33"]
}
}
@@ -0,0 +1,183 @@
terraform {
required_providers {
hcloud = {
source = "hetznercloud/hcloud"
}
coder = {
source = "coder/coder"
}
}
}
variable "hcloud_token" {
sensitive = true
}
provider "hcloud" {
token = var.hcloud_token
}
# Available locations: https://docs.hetzner.com/cloud/general/locations/
data "coder_parameter" "hcloud_location" {
name = "hcloud_location"
display_name = "Hetzner Location"
description = "Select the Hetzner Cloud location for your workspace."
type = "string"
default = "fsn1"
option {
name = "DE Falkenstein"
value = "fsn1"
}
option {
name = "US Ashburn, VA"
value = "ash"
}
option {
name = "US Hillsboro, OR"
value = "hil"
}
option {
name = "SG Singapore"
value = "sin"
}
option {
name = "DE Nuremberg"
value = "nbg1"
}
option {
name = "FI Helsinki"
value = "hel1"
}
}
# Available server types: https://docs.hetzner.com/cloud/servers/overview/
data "coder_parameter" "hcloud_server_type" {
name = "hcloud_server_type"
display_name = "Hetzner Server Type"
description = "Select the Hetzner Cloud server type for your workspace."
type = "string"
dynamic "option" {
for_each = local.hcloud_server_type_options_for_selected_location
content {
name = option.value.name
value = option.value.value
}
}
}
resource "hcloud_server" "dev" {
count = data.coder_workspace.me.start_count
name = "coder-${data.coder_workspace.me.name}-dev"
image = "ubuntu-24.04"
server_type = data.coder_parameter.hcloud_server_type.value
location = data.coder_parameter.hcloud_location.value
public_net {
ipv4_enabled = true
ipv6_enabled = true
}
user_data = templatefile("cloud-config.yaml.tftpl", {
username = lower(data.coder_workspace_owner.me.name)
home_volume_label = "coder-${data.coder_workspace.me.id}-home"
volume_id = hcloud_volume.home_volume.id
init_script = base64encode(coder_agent.main.init_script)
coder_agent_token = coder_agent.main.token
})
labels = {
"coder_workspace_name" = data.coder_workspace.me.name,
"coder_workspace_owner" = data.coder_workspace_owner.me.name,
}
}
resource "hcloud_volume" "home_volume" {
name = "coder-${data.coder_workspace.me.id}-home"
size = data.coder_parameter.home_volume_size.value
location = data.coder_parameter.hcloud_location.value
labels = {
"coder_workspace_name" = data.coder_workspace.me.name,
"coder_workspace_owner" = data.coder_workspace_owner.me.name,
}
}
resource "hcloud_volume_attachment" "home_volume_attachment" {
count = data.coder_workspace.me.start_count
volume_id = hcloud_volume.home_volume.id
server_id = hcloud_server.dev[count.index].id
automount = false
}
locals {
username = lower(data.coder_workspace_owner.me.name)
# Data source: local JSON file under the module directory
# Check API for latest server types & availability: https://docs.hetzner.cloud/reference/cloud#server-types
hcloud_server_types_data = jsondecode(file("${path.module}/hetzner_server_types.json"))
hcloud_server_type_meta = local.hcloud_server_types_data.type_meta
hcloud_server_types_by_location = local.hcloud_server_types_data.availability
hcloud_server_type_options_for_selected_location = [
for type_name in lookup(local.hcloud_server_types_by_location, data.coder_parameter.hcloud_location.value, []) : {
name = format("%s (%d vCPU, %dGB RAM, %dGB)", upper(type_name), local.hcloud_server_type_meta[type_name].cores, local.hcloud_server_type_meta[type_name].memory_gb, local.hcloud_server_type_meta[type_name].disk_gb)
value = type_name
}
]
}
data "coder_provisioner" "me" {}
provider "coder" {}
data "coder_workspace" "me" {}
data "coder_workspace_owner" "me" {}
data "coder_parameter" "home_volume_size" {
name = "home_volume_size"
display_name = "Home volume size"
description = "How large would you like your home volume to be (in GB)?"
type = "number"
default = "20"
mutable = false
validation {
min = 1
max = 100 # Adjust the max size as needed
}
}
resource "coder_agent" "main" {
os = "linux"
arch = "amd64"
metadata {
key = "cpu"
display_name = "CPU Usage"
interval = 5
timeout = 5
script = "coder stat cpu"
}
metadata {
key = "memory"
display_name = "Memory Usage"
interval = 5
timeout = 5
script = "coder stat mem"
}
metadata {
key = "home"
display_name = "Home Usage"
interval = 600 # every 10 minutes
timeout = 30 # df can take a while on large filesystems
script = "coder stat disk --path /home/${local.username}"
}
}
module "code-server" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/code-server/coder"
# This ensures that the latest non-breaking version of the module gets downloaded, you can also pin the module version to prevent breaking changes in production.
version = "~> 1.0"
agent_id = coder_agent.main.id
order = 1
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.9 KiB

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 407 KiB

After

Width:  |  Height:  |  Size: 240 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 590 KiB

@@ -13,7 +13,7 @@ Run the Cursor Agent CLI in your workspace for interactive coding assistance and
```tf
module "cursor_cli" {
source = "registry.coder.com/coder-labs/cursor-cli/coder"
version = "0.2.2"
version = "0.3.0"
agent_id = coder_agent.main.id
folder = "/home/coder/project"
}
@@ -42,7 +42,7 @@ module "coder-login" {
module "cursor_cli" {
source = "registry.coder.com/coder-labs/cursor-cli/coder"
version = "0.2.2"
version = "0.3.0"
agent_id = coder_agent.main.id
folder = "/home/coder/project"
@@ -159,7 +159,7 @@ describe("cursor-cli", async () => {
"-c",
"cat /home/coder/.cursor-cli-module/agentapi-start.log || cat /home/coder/.cursor-cli-module/start.log || true",
]);
expect(startLog.stdout).toContain(`-m ${model}`);
expect(startLog.stdout).toContain(`--model ${model}`);
expect(startLog.stdout).toContain("-f");
expect(startLog.stdout).toContain("test prompt");
});
@@ -4,7 +4,7 @@ terraform {
required_providers {
coder = {
source = "coder/coder"
version = ">= 2.7"
version = ">= 2.12"
}
}
}
@@ -132,7 +132,7 @@ resource "coder_env" "cursor_api_key" {
module "agentapi" {
source = "registry.coder.com/coder/agentapi/coder"
version = "1.2.0"
version = "2.0.0"
agent_id = var.agent_id
folder = local.folder
@@ -179,3 +179,7 @@ module "agentapi" {
/tmp/install.sh
EOT
}
output "task_app_id" {
value = module.agentapi.task_app_id
}
@@ -50,7 +50,7 @@ ARGS=()
# global flags
if [ -n "$ARG_MODEL" ]; then
ARGS+=("-m" "$ARG_MODEL")
ARGS+=("--model" "$ARG_MODEL")
fi
if [ "$ARG_FORCE" = "true" ]; then
ARGS+=("-f")
@@ -0,0 +1,55 @@
---
display_name: Perplexica
description: Run Perplexica AI search engine in your workspace via Docker
icon: ../../../../.icons/perplexica.svg
verified: false
tags: [ai, search, docker]
---
# Perplexica
Run [Perplexica](https://github.com/ItzCrazyKns/Perplexica), a privacy-focused AI search engine, in your Coder workspace. Supports cloud providers (OpenAI, Anthropic Claude) and local LLMs via Ollama.
```tf
module "perplexica" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder-labs/perplexica/coder"
version = "1.0.0"
agent_id = coder_agent.main.id
}
```
This module uses the full Perplexica image with embedded SearXNG for simpler setup with no external dependencies.
![Perplexica](../../.images/perplexica.png)
## Prerequisites
This module requires Docker to be available on the host.
## Examples
### With API Keys
```tf
module "perplexica" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder-labs/perplexica/coder"
version = "1.0.0"
agent_id = coder_agent.main.id
openai_api_key = var.openai_api_key
anthropic_api_key = var.anthropic_api_key
}
```
### With Local Ollama
```tf
module "perplexica" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder-labs/perplexica/coder"
version = "1.0.0"
agent_id = coder_agent.main.id
ollama_api_url = "http://ollama-external-endpoint:11434"
}
```
@@ -0,0 +1,108 @@
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 "docker_socket" {
type = string
description = "(Optional) Docker socket URI"
default = ""
}
variable "port" {
type = number
description = "The port to run Perplexica on."
default = 3000
}
variable "data_path" {
type = string
description = "Host path to mount for Perplexica data persistence."
default = "./perplexica-data"
}
variable "uploads_path" {
type = string
description = "Host path to mount for Perplexica file uploads."
default = "./perplexica-uploads"
}
variable "openai_api_key" {
type = string
description = "OpenAI API key."
default = ""
sensitive = true
}
variable "anthropic_api_key" {
type = string
description = "Anthropic API key for Claude models."
default = ""
sensitive = true
}
variable "ollama_api_url" {
type = string
description = "Ollama API URL for local LLM support."
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
}
resource "coder_script" "perplexica" {
agent_id = var.agent_id
display_name = "Perplexica"
icon = "/icon/perplexica.svg"
script = templatefile("${path.module}/run.sh", {
DOCKER_HOST : var.docker_socket,
PORT : var.port,
DATA_PATH : var.data_path,
UPLOADS_PATH : var.uploads_path,
OPENAI_API_KEY : var.openai_api_key,
ANTHROPIC_API_KEY : var.anthropic_api_key,
OLLAMA_API_URL : var.ollama_api_url,
})
run_on_start = true
}
resource "coder_app" "perplexica" {
agent_id = var.agent_id
slug = "perplexica"
display_name = "Perplexica"
url = "http://localhost:${var.port}"
icon = "/icon/perplexica.svg"
subdomain = true
share = var.share
order = var.order
group = var.group
}
@@ -0,0 +1,26 @@
run "plan_basic" {
command = plan
variables {
agent_id = "test-agent"
}
assert {
condition = resource.coder_app.perplexica.url == "http://localhost:3000"
error_message = "Default port should be 3000"
}
}
run "plan_custom_port" {
command = plan
variables {
agent_id = "test-agent"
port = 8080
}
assert {
condition = resource.coder_app.perplexica.url == "http://localhost:8080"
error_message = "Should use custom port"
}
}
+67
View File
@@ -0,0 +1,67 @@
#!/usr/bin/env sh
set -eu
BOLD='\033[0;1m'
RESET='\033[0m'
printf "$${BOLD}Starting Perplexica...$${RESET}\n"
# Set Docker host if provided
if [ -n "${DOCKER_HOST}" ]; then
export DOCKER_HOST="${DOCKER_HOST}"
fi
# Wait for docker to become ready
max_attempts=10
delay=2
attempt=1
while ! docker ps; do
if [ $attempt -ge $max_attempts ]; then
echo "Failed to list containers after $${max_attempts} attempts."
exit 1
fi
echo "Attempt $${attempt} failed, retrying in $${delay}s..."
sleep $delay
attempt=$(expr "$attempt" + 1)
delay=$(expr "$delay" \* 2)
done
# Pull the image
IMAGE="itzcrazykns1337/perplexica:latest"
docker pull "$${IMAGE}"
# Build docker run command
DOCKER_ARGS="-d --rm --name perplexica -p ${PORT}:3000"
# Add mounts - convert relative paths to absolute
DATA_PATH="${DATA_PATH}"
UPLOADS_PATH="${UPLOADS_PATH}"
mkdir -p "$${DATA_PATH}"
mkdir -p "$${UPLOADS_PATH}"
DATA_PATH_ABS=$(cd "$${DATA_PATH}" && pwd)
UPLOADS_PATH_ABS=$(cd "$${UPLOADS_PATH}" && pwd)
DOCKER_ARGS="$${DOCKER_ARGS} -v $${DATA_PATH_ABS}:/home/perplexica/data"
DOCKER_ARGS="$${DOCKER_ARGS} -v $${UPLOADS_PATH_ABS}:/home/perplexica/uploads"
# Add environment variables if provided
if [ -n "${OPENAI_API_KEY}" ]; then
DOCKER_ARGS="$${DOCKER_ARGS} -e OPENAI_API_KEY=${OPENAI_API_KEY}"
fi
if [ -n "${ANTHROPIC_API_KEY}" ]; then
DOCKER_ARGS="$${DOCKER_ARGS} -e ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}"
fi
if [ -n "${OLLAMA_API_URL}" ]; then
DOCKER_ARGS="$${DOCKER_ARGS} -e OLLAMA_API_URL=${OLLAMA_API_URL}"
fi
# Run container
docker run $${DOCKER_ARGS} "$${IMAGE}"
printf "\n$${BOLD}Perplexica is running on port ${PORT}$${RESET}\n"
Binary file not shown.

Before

Width:  |  Height:  |  Size: 976 KiB

After

Width:  |  Height:  |  Size: 528 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 302 KiB

After

Width:  |  Height:  |  Size: 191 KiB

+8 -9
View File
@@ -13,7 +13,7 @@ Run the [Claude Code](https://docs.anthropic.com/en/docs/agents-and-tools/claude
```tf
module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder"
version = "4.2.6"
version = "4.2.7"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
claude_api_key = "xxxx-xxxxx-xxxx"
@@ -45,7 +45,7 @@ This example shows how to configure the Claude Code module to run the agent behi
```tf
module "claude-code" {
source = "dev.registry.coder.com/coder/claude-code/coder"
version = "4.2.6"
version = "4.2.7"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
enable_boundary = true
@@ -72,7 +72,7 @@ data "coder_parameter" "ai_prompt" {
module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder"
version = "4.2.6"
version = "4.2.7"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
@@ -92,10 +92,9 @@ module "claude-code" {
{
"mcpServers": {
"my-custom-tool": {
"command": "my-tool-server"
"command": "my-tool-server",
"args": ["--port", "8080"]
}
}
}
EOF
@@ -109,7 +108,7 @@ Run and configure Claude Code as a standalone CLI in your workspace.
```tf
module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder"
version = "4.2.6"
version = "4.2.7"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
install_claude_code = true
@@ -131,7 +130,7 @@ variable "claude_code_oauth_token" {
module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder"
version = "4.2.6"
version = "4.2.7"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
claude_code_oauth_token = var.claude_code_oauth_token
@@ -204,7 +203,7 @@ resource "coder_env" "bedrock_api_key" {
module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder"
version = "4.2.6"
version = "4.2.7"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
model = "global.anthropic.claude-sonnet-4-5-20250929-v1:0"
@@ -261,7 +260,7 @@ resource "coder_env" "google_application_credentials" {
module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder"
version = "4.2.6"
version = "4.2.7"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
model = "claude-sonnet-4@20250514"
@@ -39,9 +39,11 @@ interface SetupProps {
agentapiMockScript?: string;
}
const setup = async (props?: SetupProps): Promise<{ id: string }> => {
const setup = async (
props?: SetupProps,
): Promise<{ id: string; coderEnvVars: Record<string, string> }> => {
const projectDir = "/home/coder/project";
const { id } = await setupUtil({
const { id, coderEnvVars } = await setupUtil({
moduleDir: import.meta.dir,
moduleVariables: {
install_claude_code: props?.skipClaudeMock ? "true" : "false",
@@ -61,7 +63,7 @@ const setup = async (props?: SetupProps): Promise<{ id: string }> => {
content: await loadTestFile(import.meta.dir, "claude-mock.sh"),
});
}
return { id };
return { id, coderEnvVars };
};
setDefaultTimeout(60 * 1000);
@@ -79,14 +81,14 @@ describe("claude-code", async () => {
test("install-claude-code-version", async () => {
const version_to_install = "1.0.40";
const { id } = await setup({
const { id, coderEnvVars } = await setup({
skipClaudeMock: true,
moduleVariables: {
install_claude_code: "true",
claude_code_version: version_to_install,
},
});
await execModuleScript(id);
await execModuleScript(id, coderEnvVars);
const resp = await execContainer(id, [
"bash",
"-c",
@@ -96,14 +98,14 @@ describe("claude-code", async () => {
});
test("check-latest-claude-code-version-works", async () => {
const { id } = await setup({
const { id, coderEnvVars } = await setup({
skipClaudeMock: true,
skipAgentAPIMock: true,
moduleVariables: {
install_claude_code: "true",
},
});
await execModuleScript(id);
await execModuleScript(id, coderEnvVars);
await expectAgentAPIStarted(id);
});
@@ -133,13 +135,13 @@ describe("claude-code", async () => {
},
},
});
const { id } = await setup({
const { id, coderEnvVars } = await setup({
skipClaudeMock: true,
moduleVariables: {
mcp: mcpConfig,
},
});
await execModuleScript(id);
await execModuleScript(id, coderEnvVars);
const resp = await readFileContainer(id, "/home/coder/.claude.json");
expect(resp).toContain("test-cmd");
@@ -288,6 +288,12 @@ resource "coder_env" "disable_autoupdater" {
value = "1"
}
resource "coder_env" "claude_binary_path" {
agent_id = var.agent_id
name = "PATH"
value = "$HOME/.local/bin:$PATH"
}
locals {
# we have to trim the slash because otherwise coder exp mcp will
# set up an invalid claude config
@@ -1,10 +1,5 @@
#!/bin/bash
if [ -f "$HOME/.bashrc" ]; then
source "$HOME"/.bashrc
fi
# Set strict error handling AFTER sourcing bashrc to avoid unbound variable errors from user dotfiles
set -euo pipefail
BOLD='\033[0;1m'
@@ -45,11 +40,6 @@ function install_claude_code_cli() {
if [ $CURL_EXIT -ne 0 ]; then
echo "Claude Code installer failed with exit code $$CURL_EXIT"
fi
# Ensure binaries are discoverable.
echo "Creating a symlink for claude"
sudo ln -s /home/coder/.local/bin/claude /usr/local/bin/claude
echo "Installed Claude Code successfully. Version: $(claude --version || echo 'unknown')"
else
echo "Skipping Claude Code installation as per configuration."
@@ -1,14 +1,7 @@
#!/bin/bash
if [ -f "$HOME/.bashrc" ]; then
source "$HOME"/.bashrc
fi
# Set strict error handling AFTER sourcing bashrc to avoid unbound variable errors from user dotfiles
set -euo pipefail
export PATH="$HOME/.local/bin:$PATH"
command_exists() {
command -v "$1" > /dev/null 2>&1
}
+16 -12
View File
@@ -14,7 +14,7 @@ This module allows you to automatically clone a repository by URL and skip if it
module "git-clone" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/git-clone/coder"
version = "1.2.2"
version = "1.2.3"
agent_id = coder_agent.example.id
url = "https://github.com/coder/coder"
}
@@ -28,7 +28,7 @@ module "git-clone" {
module "git-clone" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/git-clone/coder"
version = "1.2.2"
version = "1.2.3"
agent_id = coder_agent.example.id
url = "https://github.com/coder/coder"
base_dir = "~/projects/coder"
@@ -43,11 +43,12 @@ To use with [Git Authentication](https://coder.com/docs/v2/latest/admin/git-prov
module "git-clone" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/git-clone/coder"
version = "1.2.2"
version = "1.2.3"
agent_id = coder_agent.example.id
url = "https://github.com/coder/coder"
}
data "coder_external_auth" "github" {
id = "github"
}
@@ -69,11 +70,12 @@ data "coder_parameter" "git_repo" {
module "git_clone" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/git-clone/coder"
version = "1.2.2"
version = "1.2.3"
agent_id = coder_agent.example.id
url = data.coder_parameter.git_repo.value
}
# Create a code-server instance for the cloned repository
module "code-server" {
count = data.coder_workspace.me.start_count
@@ -103,13 +105,14 @@ Configuring `git-clone` for a self-hosted GitHub Enterprise Server running at `g
module "git-clone" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/git-clone/coder"
version = "1.2.2"
version = "1.2.3"
agent_id = coder_agent.example.id
url = "https://github.example.com/coder/coder/tree/feat/example"
git_providers = {
"https://github.example.com/" = {
provider = "github"
}
}
}
```
@@ -122,7 +125,7 @@ To GitLab clone with a specific branch like `feat/example`
module "git-clone" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/git-clone/coder"
version = "1.2.2"
version = "1.2.3"
agent_id = coder_agent.example.id
url = "https://gitlab.com/coder/coder/-/tree/feat/example"
}
@@ -134,13 +137,14 @@ Configuring `git-clone` for a self-hosted GitLab running at `gitlab.example.com`
module "git-clone" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/git-clone/coder"
version = "1.2.2"
version = "1.2.3"
agent_id = coder_agent.example.id
url = "https://gitlab.example.com/coder/coder/-/tree/feat/example"
git_providers = {
"https://gitlab.example.com/" = {
provider = "gitlab"
}
}
}
```
@@ -155,7 +159,7 @@ For example, to clone the `feat/example` branch:
module "git-clone" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/git-clone/coder"
version = "1.2.2"
version = "1.2.3"
agent_id = coder_agent.example.id
url = "https://github.com/coder/coder"
branch_name = "feat/example"
@@ -173,7 +177,7 @@ For example, this will clone into the `~/projects/coder/coder-dev` folder:
module "git-clone" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/git-clone/coder"
version = "1.2.2"
version = "1.2.3"
agent_id = coder_agent.example.id
url = "https://github.com/coder/coder"
folder_name = "coder-dev"
@@ -191,8 +195,8 @@ If not defined, the default, `0`, performs a full clone.
```tf
module "git-clone" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/modules/git-clone/coder"
version = "1.2.2"
source = "registry.coder.com/coder/git-clone/coder"
version = "1.2.3"
agent_id = coder_agent.example.id
url = "https://github.com/coder/coder"
depth = 1
@@ -208,7 +212,7 @@ This is useful for running initialization tasks like installing dependencies or
module "git-clone" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/git-clone/coder"
version = "1.2.2"
version = "1.2.3"
agent_id = coder_agent.example.id
url = "https://github.com/coder/coder"
post_clone_script = <<-EOT
+5 -4
View File
@@ -58,9 +58,10 @@ fi
# Run post-clone script if provided
if [ -n "$POST_CLONE_SCRIPT" ]; then
echo "Running post-clone script..."
echo "$POST_CLONE_SCRIPT" | base64 -d > /tmp/post_clone.sh
chmod +x /tmp/post_clone.sh
POST_CLONE_TMP=$(mktemp)
echo "$POST_CLONE_SCRIPT" | base64 -d > "$POST_CLONE_TMP"
chmod +x "$POST_CLONE_TMP"
cd "$CLONE_PATH" || exit
/tmp/post_clone.sh
rm /tmp/post_clone.sh
$POST_CLONE_TMP
rm "$POST_CLONE_TMP"
fi
+1 -1
View File
@@ -14,7 +14,7 @@ Automatically install [KasmVNC](https://kasmweb.com/kasmvnc) in a workspace, and
module "kasmvnc" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/kasmvnc/coder"
version = "1.2.6"
version = "1.2.7"
agent_id = coder_agent.example.id
desktop_environment = "xfce"
subdomain = true
+1 -1
View File
@@ -31,7 +31,7 @@ variable "desktop_environment" {
description = "Specifies the desktop environment of the workspace. This should be pre-installed on the workspace."
validation {
condition = contains(["xfce", "kde", "gnome", "lxde", "lxqt"], var.desktop_environment)
condition = contains(["cinnamon", "mate", "lxde", "lxqt", "kde", "gnome", "xfce", "manual"], var.desktop_environment)
error_message = "Invalid desktop environment. Please specify a valid desktop environment."
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 460 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 182 KiB

After

Width:  |  Height:  |  Size: 181 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 461 KiB

After

Width:  |  Height:  |  Size: 330 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 632 KiB

After

Width:  |  Height:  |  Size: 631 KiB