Compare commits

...

9 Commits

Author SHA1 Message Date
blink-so[bot] 2920f0517f fix: simplify to single snapshot per workspace
- Remove rotating slots, use single snapshot
- Snapshot name: {owner}-{workspace}-snapshot
- Overwrites on each workspace stop
- Remove snapshot_retention_count variable
- Simpler user choice: restore or fresh
2026-02-05 14:39:31 +00:00
blink-so[bot] 98c1767ffb fix: terraform fmt alignment 2026-02-05 06:51:32 +00:00
blink-so[bot] d6a96c3351 fix: rewrite GCP disk snapshot module with pure Terraform
- Remove external/gcloud CLI dependency
- Use rotating snapshot slots (1-3) for predictable naming
- Add fake credentials for CI testing
- Simplify design: slots are reused round-robin
- Update README with new approach
- Fix prettier formatting
2026-02-05 06:50:20 +00:00
blink-so[bot] 9085b30390 feat: add GCP disk snapshot module for Coder workspaces
This module provides disk snapshot functionality for Coder workspaces running on GCP:

- Automatic snapshots when workspaces are stopped
- Configurable retention (default: 3 snapshots)
- User parameter to select from available snapshots
- Defaults to newest snapshot on workspace start
- Automatic cleanup of old snapshots beyond retention
- Uses GCP labels for workspace/owner filtering

Co-authored-by: M Atif Ali <matifali@coder.com>
2026-02-05 06:45:28 +00:00
Steven Masley 6ac4d70405 chore: add placeholder to git config inputs (#694)
Shows a placeholder of default values in the parameter input box
2026-02-04 09:34:02 -06:00
Harsh Singh Panwar 49a7985bc6 fix(coder/modules/jupyterlab): fix a typo (#689)
Closes https://github.com/coder/registry/issues/685

---------

Co-authored-by: Atif Ali <atif@coder.com>
Co-authored-by: Muhammad Atif Ali <me@matifali.dev>
2026-02-04 09:10:27 +05:00
Andreas Skorczyk 08e68a2da4 Don't create CLAUDE_API_KEY coder_env if not set (#686)
## Description

At the moment, the `CLAUDE_API_KEY` coder_env will always be created,
even if the variable itself is not. This can lead to the environment
variable being unset if it has been set outside of Terraform.

With this PR, we make the `claude_api_key` coder_env conditional, so it
will only be created if an API key has been set.

## 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/claude-code/main.tf`  
**New version:** `v4.7.4`  
**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: 35C4n0r <70096901+35C4n0r@users.noreply.github.com>
2026-02-04 08:10:16 +05:30
Atif Ali 66662db5aa fix(claude-code): fix example for using AI Bridge (#691)
Co-authored-by: 35C4n0r <70096901+35C4n0r@users.noreply.github.com>
2026-02-02 16:02:06 +00:00
35C4n0r e25a972d7d fix(workflows/version-bump.yaml): fix typo in case statement (#687)
## Description
- Fix typo in version bump workflow

## Type of Change

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

## Testing & Validation

- [x] Tests pass (`bun test`)
- [x] Code formatted (`bun fmt`)
- [x] Changes tested locally
2026-02-02 15:33:56 +00:00
12 changed files with 418 additions and 30 deletions
+1 -1
View File
@@ -41,7 +41,7 @@ jobs:
LABEL_NAME: ${{ github.event.label.name }}
id: bump-type
run: |
case "$LABEL_NAME" in in
case "$LABEL_NAME" in
"version:patch")
echo "type=patch" >> $GITHUB_OUTPUT
;;
@@ -0,0 +1,100 @@
---
display_name: GCP Disk Snapshot
description: Create and manage disk snapshots for Coder workspaces on GCP
icon: ../../../../.icons/gcp.svg
verified: false
tags: [gcp, snapshot, disk, backup, persistence]
---
# GCP Disk Snapshot Module
This module provides disk snapshot functionality for Coder workspaces running on GCP Compute Engine. It automatically creates a snapshot when workspaces are stopped and allows users to restore from the snapshot when starting.
```tf
module "disk_snapshot" {
source = "registry.coder.com/coder-labs/gcp-disk-snapshot/coder"
version = "1.0.0"
disk_self_link = google_compute_disk.workspace.self_link
default_image = "debian-cloud/debian-12"
zone = var.zone
project = var.project_id
}
```
## Features
- **Automatic Snapshots**: Creates a disk snapshot when workspaces are stopped
- **Single Snapshot**: Maintains one snapshot per workspace (overwrites on each stop)
- **Restore Option**: Users can choose to restore from snapshot or start fresh
- **Default to Restore**: Automatically selects restore if a snapshot exists
- **Pure Terraform**: No external CLI dependencies
- **Workspace Isolation**: Snapshots are named and labeled by workspace and owner
## Usage
### Basic Usage
```hcl
module "disk_snapshot" {
source = "registry.coder.com/coder-labs/gcp-disk-snapshot/coder"
disk_self_link = google_compute_disk.workspace.self_link
default_image = "debian-cloud/debian-12"
zone = var.zone
project = var.project_id
}
# Create disk from snapshot or default image
resource "google_compute_disk" "workspace" {
name = "workspace-${data.coder_workspace.me.id}"
type = "pd-balanced"
zone = var.zone
size = 50
# Use snapshot if available, otherwise use default image
snapshot = module.disk_snapshot.snapshot_self_link
image = module.disk_snapshot.use_snapshot ? null : module.disk_snapshot.default_image
lifecycle {
ignore_changes = [snapshot, image]
}
}
```
### With Regional Storage
```hcl
module "disk_snapshot" {
source = "registry.coder.com/coder-labs/gcp-disk-snapshot/coder"
disk_self_link = google_compute_disk.workspace.self_link
default_image = "debian-cloud/debian-12"
zone = var.zone
project = var.project_id
storage_locations = ["us-central1"] # Store snapshot in specific region
labels = {
environment = "development"
team = "engineering"
}
}
```
## How It Works
1. When a workspace stops, a snapshot is created with a predictable name: `{owner}-{workspace}-snapshot`
2. The snapshot is overwritten each time the workspace stops
3. When starting, users can choose to restore from the snapshot or start fresh
4. If a snapshot exists, restore is selected by default
## Required IAM Permissions
The service account running Terraform needs:
- `compute.snapshots.create`
- `compute.snapshots.delete`
- `compute.snapshots.get`
- `compute.disks.createSnapshot`
Or use the predefined role: `roles/compute.storageAdmin`
@@ -0,0 +1,80 @@
import { describe, expect, it } from "bun:test";
import { runTerraformApply, runTerraformInit } from "~test";
describe("gcp-disk-snapshot", async () => {
await runTerraformInit(import.meta.dir);
it("required variables with test mode", async () => {
await runTerraformApply(import.meta.dir, {
disk_self_link:
"projects/test-project/zones/us-central1-a/disks/test-disk",
default_image: "debian-cloud/debian-12",
zone: "us-central1-a",
project: "test-project",
test_mode: true,
});
});
it("missing variable: disk_self_link", async () => {
await expect(
runTerraformApply(import.meta.dir, {
default_image: "debian-cloud/debian-12",
zone: "us-central1-a",
project: "test-project",
test_mode: true,
}),
).rejects.toThrow();
});
it("missing variable: default_image", async () => {
await expect(
runTerraformApply(import.meta.dir, {
disk_self_link:
"projects/test-project/zones/us-central1-a/disks/test-disk",
zone: "us-central1-a",
project: "test-project",
test_mode: true,
}),
).rejects.toThrow();
});
it("missing variable: zone", async () => {
await expect(
runTerraformApply(import.meta.dir, {
disk_self_link:
"projects/test-project/zones/us-central1-a/disks/test-disk",
default_image: "debian-cloud/debian-12",
project: "test-project",
test_mode: true,
}),
).rejects.toThrow();
});
it("missing variable: project", async () => {
await expect(
runTerraformApply(import.meta.dir, {
disk_self_link:
"projects/test-project/zones/us-central1-a/disks/test-disk",
default_image: "debian-cloud/debian-12",
zone: "us-central1-a",
test_mode: true,
}),
).rejects.toThrow();
});
it("supports optional variables", async () => {
await runTerraformApply(import.meta.dir, {
disk_self_link:
"projects/test-project/zones/us-central1-a/disks/test-disk",
default_image: "debian-cloud/debian-12",
zone: "us-central1-a",
project: "test-project",
test_mode: true,
storage_locations: JSON.stringify(["us-central1"]),
labels: JSON.stringify({
environment: "test",
team: "engineering",
}),
});
});
});
@@ -0,0 +1,178 @@
terraform {
required_version = ">= 1.0"
required_providers {
google = {
source = "hashicorp/google"
version = ">= 4.0"
}
coder = {
source = "coder/coder"
version = ">= 0.17"
}
}
}
# Provider configuration for testing only
# In production, the provider will be inherited from the calling module
provider "google" {
project = "test-project"
region = "us-central1"
# Fake credentials for testing - allows terraform plan/apply to run
# without actual GCP authentication in CI environments
credentials = jsonencode({
type = "service_account"
project_id = "test-project"
private_key_id = "key-id"
private_key = "-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEA0Z3VS5JJcds3xfn/ygWyF8PbnGy0ARL00FVaKUOclBo0vo9C\nWL23EQJ2dWLV5g8k8DjFYIrXvARQPIDs0d+6UgKNKFjHmcZrj9i+e9v8zhVLB2wc\nfU2xsf3AJzLWr7L/LN6GEfT6m7kqKvBB6mJhpFn9RSAZ6WNvnOv1IVVQEq5Tfjlw\nGiJI0q0T8JmEobVSAaRJa7ZKQH1tBjTxcbr+EajVh5F2n7E0VqJNVNT5c5s8MJW0\nrn6AKaEVwmr3SW/NKQX6LxHRgVLJoWcL9j9B9cQ5Mz7u6h/oTrKLLt1v5NKvO9d8\ng39z7cKd1O6kd8nE3hZD7w5d0ileH9u9wZNPFwIDAQABAoIBADvhw8GIB0/G7mFP\ntest-fake-key-data-for-ci-testing-only\n-----END RSA PRIVATE KEY-----\n"
client_email = "test@test-project.iam.gserviceaccount.com"
client_id = "123456789"
auth_uri = "https://accounts.google.com/o/oauth2/auth"
token_uri = "https://oauth2.googleapis.com/token"
auth_provider_x509_cert_url = "https://www.googleapis.com/oauth2/v1/certs"
client_x509_cert_url = "https://www.googleapis.com/robot/v1/metadata/x509/test%40test-project.iam.gserviceaccount.com"
})
}
# Variables
variable "test_mode" {
description = "Set to true when running tests to skip GCP API calls"
type = bool
default = false
}
variable "disk_self_link" {
description = "The self_link of the disk to create snapshots from"
type = string
}
variable "default_image" {
description = "The default image to use when not restoring from a snapshot (e.g., debian-cloud/debian-12)"
type = string
}
variable "zone" {
description = "The zone where the disk resides"
type = string
}
variable "project" {
description = "The GCP project ID"
type = string
}
variable "labels" {
description = "Additional labels to apply to snapshots"
type = map(string)
default = {}
}
variable "storage_locations" {
description = "Cloud Storage bucket location to store the snapshot (regional or multi-regional)"
type = list(string)
default = []
}
# Get workspace information
data "coder_workspace" "me" {}
data "coder_workspace_owner" "me" {}
# Locals for label normalization (GCP labels must be lowercase with hyphens/underscores)
locals {
normalized_workspace_name = lower(replace(replace(data.coder_workspace.me.name, "/[^a-z0-9-_]/", "-"), "--", "-"))
normalized_owner_name = lower(replace(replace(data.coder_workspace_owner.me.name, "/[^a-z0-9-_]/", "-"), "--", "-"))
normalized_template_name = lower(replace(replace(data.coder_workspace.me.template_name, "/[^a-z0-9-_]/", "-"), "--", "-"))
# Single snapshot name per workspace
snapshot_name = "${local.normalized_owner_name}-${local.normalized_workspace_name}-snapshot"
}
# Try to read existing snapshot for this workspace
data "google_compute_snapshot" "workspace_snapshot" {
count = var.test_mode ? 0 : 1
name = local.snapshot_name
project = var.project
}
locals {
# Check if snapshot exists
snapshot_exists = var.test_mode ? false : can(data.google_compute_snapshot.workspace_snapshot[0].self_link)
# Default to using snapshot if it exists
default_restore = local.snapshot_exists ? "snapshot" : "none"
}
# Parameter to choose whether to restore from snapshot
data "coder_parameter" "restore_snapshot" {
name = "restore_snapshot"
display_name = "Restore from Snapshot"
description = "Restore workspace from the last snapshot, or start fresh."
type = "string"
default = local.default_restore
mutable = true
order = 1
option {
name = "Fresh disk (no snapshot)"
value = "none"
description = "Start with a fresh disk using the default image"
}
dynamic "option" {
for_each = local.snapshot_exists ? [1] : []
content {
name = "Restore from snapshot"
value = "snapshot"
description = "Restore from: ${local.snapshot_name}"
}
}
}
locals {
use_snapshot = data.coder_parameter.restore_snapshot.value == "snapshot" && local.snapshot_exists
}
# Create/update snapshot when workspace is stopped
resource "google_compute_snapshot" "workspace_snapshot" {
count = !var.test_mode && data.coder_workspace.me.transition == "stop" ? 1 : 0
name = local.snapshot_name
source_disk = var.disk_self_link
zone = var.zone
project = var.project
storage_locations = length(var.storage_locations) > 0 ? var.storage_locations : null
labels = merge(var.labels, {
coder_workspace = local.normalized_workspace_name
coder_owner = local.normalized_owner_name
coder_template = local.normalized_template_name
workspace_id = data.coder_workspace.me.id
})
}
# Outputs
output "snapshot_self_link" {
description = "The self_link of the snapshot to restore from (null if not using snapshot)"
value = local.use_snapshot ? data.google_compute_snapshot.workspace_snapshot[0].self_link : null
}
output "use_snapshot" {
description = "Whether a snapshot is being used"
value = local.use_snapshot
}
output "default_image" {
description = "The default image to use when not using a snapshot"
value = var.default_image
}
output "snapshot_name" {
description = "The name of the workspace snapshot"
value = local.snapshot_name
}
output "snapshot_exists" {
description = "Whether a snapshot exists for this workspace"
value = local.snapshot_exists
}
+15 -16
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.7.2"
version = "4.7.4"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
claude_api_key = "xxxx-xxxxx-xxxx"
@@ -47,7 +47,7 @@ By default, when `enable_boundary = true`, the module uses `coder boundary` subc
```tf
module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder"
version = "4.7.2"
version = "4.7.4"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
enable_boundary = true
@@ -68,7 +68,7 @@ For tasks integration with AI Bridge, add `enable_aibridge = true` to the [Usage
```tf
module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder"
version = "4.7.2"
version = "4.7.4"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
enable_aibridge = true
@@ -96,12 +96,11 @@ resource "coder_ai_task" "task" {
data "coder_task" "me" {}
module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder"
version = "4.7.2"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
claude_api_key = "xxxx-xxxxx-xxxx"
ai_prompt = data.coder_task.me.prompt
source = "registry.coder.com/coder/claude-code/coder"
version = "4.7.4"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
ai_prompt = data.coder_task.me.prompt
# Optional: route through AI Bridge (Premium feature)
# enable_aibridge = true
@@ -121,7 +120,7 @@ This example shows additional configuration options for version pinning, custom
```tf
module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder"
version = "4.7.2"
version = "4.7.4"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
@@ -177,7 +176,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.7.2"
version = "4.7.4"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
install_claude_code = true
@@ -199,7 +198,7 @@ variable "claude_code_oauth_token" {
module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder"
version = "4.7.2"
version = "4.7.4"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
claude_code_oauth_token = var.claude_code_oauth_token
@@ -210,7 +209,7 @@ module "claude-code" {
#### Prerequisites
AWS account with Bedrock access, Claude models enabled in Bedrock console, appropriate IAM permissions.
AWS account with Bedrock access, Claude models enabled in Bedrock console, and appropriate IAM permissions.
Configure Claude Code to use AWS Bedrock for accessing Claude models through your AWS infrastructure.
@@ -272,7 +271,7 @@ resource "coder_env" "bedrock_api_key" {
module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder"
version = "4.7.2"
version = "4.7.4"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
model = "global.anthropic.claude-sonnet-4-5-20250929-v1:0"
@@ -286,7 +285,7 @@ module "claude-code" {
#### Prerequisites
GCP project with Vertex AI API enabled, Claude models enabled through Model Garden, service account with Vertex AI permissions, appropriate IAM permissions (Vertex AI User role).
GCP project with Vertex AI API enabled, Claude models enabled through Model Garden, service account with Vertex AI permissions, and appropriate IAM permissions (Vertex AI User role).
Configure Claude Code to use Google Vertex AI for accessing Claude models through Google Cloud Platform.
@@ -329,7 +328,7 @@ resource "coder_env" "google_application_credentials" {
module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder"
version = "4.7.2"
version = "4.7.4"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
model = "claude-sonnet-4@20250514"
+5 -2
View File
@@ -276,9 +276,11 @@ resource "coder_env" "claude_code_oauth_token" {
}
resource "coder_env" "claude_api_key" {
count = local.claude_api_key != "" ? 1 : 0
agent_id = var.agent_id
name = "CLAUDE_API_KEY"
value = var.enable_aibridge ? data.coder_workspace_owner.me.session_token : var.claude_api_key
value = local.claude_api_key
}
resource "coder_env" "disable_autoupdater" {
@@ -324,7 +326,8 @@ locals {
start_script = file("${path.module}/scripts/start.sh")
module_dir_name = ".claude-module"
# Extract hostname from access_url for boundary --allow flag
coder_host = replace(replace(data.coder_workspace.me.access_url, "https://", ""), "http://", "")
coder_host = replace(replace(data.coder_workspace.me.access_url, "https://", ""), "http://", "")
claude_api_key = var.enable_aibridge ? data.coder_workspace_owner.me.session_token : var.claude_api_key
# Required prompts for the module to properly report task status to Coder
report_tasks_system_prompt = <<-EOT
@@ -42,7 +42,7 @@ run "test_claude_code_with_api_key" {
}
assert {
condition = coder_env.claude_api_key.value == "test-api-key-123"
condition = coder_env.claude_api_key[0].value == "test-api-key-123"
error_message = "Claude API key value should match the input"
}
}
@@ -298,6 +298,13 @@ run "test_aibridge_enabled" {
enable_aibridge = true
}
override_data {
target = data.coder_workspace_owner.me
values = {
session_token = "mock-session-token"
}
}
assert {
condition = var.enable_aibridge == true
error_message = "AI Bridge should be enabled"
@@ -314,12 +321,12 @@ run "test_aibridge_enabled" {
}
assert {
condition = coder_env.claude_api_key.name == "CLAUDE_API_KEY"
condition = coder_env.claude_api_key[0].name == "CLAUDE_API_KEY"
error_message = "CLAUDE_API_KEY environment variable should be set"
}
assert {
condition = coder_env.claude_api_key.value == data.coder_workspace_owner.me.session_token
condition = coder_env.claude_api_key[0].value == data.coder_workspace_owner.me.session_token
error_message = "CLAUDE_API_KEY should use workspace owner's session token when aibridge is enabled"
}
}
@@ -370,7 +377,7 @@ run "test_aibridge_disabled_with_api_key" {
}
assert {
condition = coder_env.claude_api_key.value == "test-api-key-xyz"
condition = coder_env.claude_api_key[0].value == "test-api-key-xyz"
error_message = "CLAUDE_API_KEY should use the provided API key when aibridge is disabled"
}
@@ -379,3 +386,18 @@ run "test_aibridge_disabled_with_api_key" {
error_message = "ANTHROPIC_BASE_URL should not be set when aibridge is disabled"
}
}
run "test_no_api_key_no_env" {
command = plan
variables {
agent_id = "test-agent-no-key"
workdir = "/home/coder/test"
enable_aibridge = false
}
assert {
condition = length(coder_env.claude_api_key) == 0
error_message = "CLAUDE_API_KEY should not be created when no API key is provided and aibridge is disabled"
}
}
+3 -3
View File
@@ -14,7 +14,7 @@ Runs a script that updates git credentials in the workspace to match the user's
module "git-config" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/git-config/coder"
version = "1.0.32"
version = "1.0.33"
agent_id = coder_agent.main.id
}
```
@@ -29,7 +29,7 @@ TODO: Add screenshot
module "git-config" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/git-config/coder"
version = "1.0.32"
version = "1.0.33"
agent_id = coder_agent.main.id
allow_email_change = true
}
@@ -43,7 +43,7 @@ TODO: Add screenshot
module "git-config" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/git-config/coder"
version = "1.0.32"
version = "1.0.33"
agent_id = coder_agent.main.id
allow_username_change = false
allow_email_change = false
@@ -44,6 +44,9 @@ data "coder_parameter" "user_email" {
description = "Git user.email to be used for commits. Leave empty to default to Coder user's email."
display_name = "Git config user.email"
mutable = true
styling = jsonencode({
placeholder = data.coder_workspace_owner.me.email
})
}
data "coder_parameter" "username" {
@@ -55,6 +58,9 @@ data "coder_parameter" "username" {
description = "Git user.name to be used for commits. Leave empty to default to Coder user's Full Name."
display_name = "Full Name for Git config"
mutable = true
styling = jsonencode({
placeholder = coalesce(data.coder_workspace_owner.me.full_name, data.coder_workspace_owner.me.name)
})
}
resource "coder_env" "git_author_name" {
+2 -2
View File
@@ -16,7 +16,7 @@ A module that adds JupyterLab in your Coder template.
module "jupyterlab" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/jupyterlab/coder"
version = "1.2.1"
version = "1.2.2"
agent_id = coder_agent.main.id
}
```
@@ -29,7 +29,7 @@ JupyterLab is automatically configured to work with Coder's iframe embedding. Fo
module "jupyterlab" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/jupyterlab/coder"
version = "1.2.1"
version = "1.2.2"
agent_id = coder_agent.main.id
config = {
ServerApp = {
@@ -77,7 +77,7 @@ describe("jupyterlab", async () => {
expect(output.exitCode).toBe(1);
expect(output.stdout).toEqual([
"Checking for a supported installer",
"No valid installer is not installed",
"No supported installer found.",
"Please install pipx or uv in your Dockerfile/VM image before running this script",
]);
});
+1 -1
View File
@@ -14,7 +14,7 @@ check_available_installer() {
INSTALLER="uv"
return
fi
echo "No valid installer is not installed"
echo "No supported installer found."
echo "Please install pipx or uv in your Dockerfile/VM image before running this script"
exit 1
}