mirror of
https://github.com/coder/registry.git
synced 2026-06-03 13:08:14 +00:00
Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2168360195 | |||
| da5a2ba6a8 | |||
| 63cad25954 | |||
| cd759bd9a1 | |||
| 54a7bb0001 | |||
| 50f4d5388b | |||
| 36943d1dfb | |||
| e7d705bf98 | |||
| 898219b16b | |||
| fc071e0930 | |||
| d516aff908 | |||
| ccdca6daf5 | |||
| ce039f64df | |||
| 8acda84dd7 | |||
| 76c1299968 | |||
| 60372ff797 | |||
| f28bcdb713 | |||
| cb553209a5 | |||
| 5d0504aef9 | |||
| c1c0dec90f | |||
| 59b67c2c98 | |||
| 7abe422e0a |
@@ -1,5 +1,3 @@
|
||||
Closes #
|
||||
|
||||
## Description
|
||||
|
||||
<!-- Briefly describe what this PR does and why -->
|
||||
@@ -7,6 +5,7 @@ Closes #
|
||||
## Type of Change
|
||||
|
||||
- [ ] New module
|
||||
- [ ] New template
|
||||
- [ ] Bug fix
|
||||
- [ ] Feature/enhancement
|
||||
- [ ] Documentation
|
||||
@@ -20,10 +19,16 @@ Closes #
|
||||
**New version:** `v1.0.0`
|
||||
**Breaking change:** [ ] Yes [ ] No
|
||||
|
||||
## Template Information
|
||||
|
||||
<!-- Delete this section if not applicable -->
|
||||
|
||||
**Path:** `registry/[namespace]/templates/[template-name]`
|
||||
|
||||
## Testing & Validation
|
||||
|
||||
- [ ] Tests pass (`bun test`)
|
||||
- [ ] Code formatted (`bun run fmt`)
|
||||
- [ ] Code formatted (`bun fmt`)
|
||||
- [ ] Changes tested locally
|
||||
|
||||
## Related Issues
|
||||
|
||||
@@ -28,6 +28,8 @@ jobs:
|
||||
run: bun install
|
||||
- name: Run TypeScript tests
|
||||
run: bun test
|
||||
- name: Run Terraform tests
|
||||
run: ./scripts/terraform_test_all.sh
|
||||
- name: Run Terraform Validate
|
||||
run: bun terraform-validate
|
||||
validate-style:
|
||||
@@ -48,7 +50,7 @@ jobs:
|
||||
- name: Validate formatting
|
||||
run: bun fmt:ci
|
||||
- name: Check for typos
|
||||
uses: crate-ci/typos@v1.37.2
|
||||
uses: crate-ci/typos@v1.38.1
|
||||
with:
|
||||
config: .github/typos.toml
|
||||
validate-readme-files:
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="512pt" height="512pt" version="1.1" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="m500.48 262.2-48.18 73.984c-0.73438 1.1367-2 1.8242-3.3555 1.8242-1.3516 0-2.6172-0.6875-3.3516-1.8242l-48.129-73.984c-0.78125-1.2227-0.83594-2.7773-0.14453-4.0547 0.69141-1.2734 2.0195-2.0742 3.4727-2.0898h24.781c-0.007813-29.523-7.7188-58.531-22.375-84.156-14.652-25.629-35.742-46.988-61.184-61.969-2.3711-1.3633-3.8633-3.8594-3.9453-6.5938-0.085937-2.7305 1.2539-5.3125 3.5352-6.8203l27.035-17.613c3.4766-2.3633 8.043-2.3633 11.52 0 28.473 19.934 51.723 46.441 67.773 77.27 16.051 30.828 24.434 65.074 24.438 99.832h24.781c1.4688 0 2.8203 0.80859 3.5156 2.1055 0.69531 1.293 0.62109 2.8633-0.1875 4.0898zm-85.043 79.359c-1.5078-2.2812-4.0898-3.6211-6.8203-3.5391-2.7344 0.085937-5.2305 1.5781-6.5938 3.9492-14.965 25.434-36.305 46.523-61.914 61.188-25.609 14.664-54.602 22.391-84.109 22.422v-24.781c-0.011719-1.4531-0.8125-2.7812-2.0898-3.4727-1.2773-0.69141-2.832-0.63672-4.0547 0.14453l-74.035 47.977c-1.1367 0.73438-1.8242 1.9961-1.8242 3.3516s0.6875 2.6172 1.8242 3.3555l73.984 48.18c1.2227 0.78125 2.7773 0.83594 4.0547 0.14453 1.2734-0.69141 2.0742-2.0234 2.0898-3.4727v-24.68c34.734-0.015624 68.957-8.3984 99.766-24.441 30.812-16.039 57.301-39.27 77.23-67.719 2.3672-3.4766 2.3672-8.043 0-11.52zm-245.45 60.52c-25.434-14.977-46.516-36.328-61.172-61.945-14.652-25.617-22.371-54.617-22.387-84.129h24.781c1.4531-0.011719 2.7812-0.8125 3.4727-2.0898 0.69141-1.2773 0.63672-2.832-0.14453-4.0547l-47.977-74.035c-0.73438-1.1367-1.9961-1.8242-3.3516-1.8242s-2.6172 0.6875-3.3555 1.8242l-48.332 73.984c-0.80859 1.2266-0.88281 2.7969-0.1875 4.0898 0.69531 1.2969 2.0469 2.1055 3.5156 2.1055h24.781c0.015625 34.734 8.3984 68.957 24.438 99.766 16.043 30.812 39.273 57.301 67.723 77.234 3.4766 2.3633 8.043 2.3633 11.52 0l27.086-17.664c2.2109-1.5195 3.4961-4.0625 3.4141-6.7422-0.082032-2.6836-1.5234-5.1406-3.8242-6.5195zm92.16-390.5c-1.2227-0.78125-2.7773-0.83594-4.0547-0.14453-1.2773 0.69141-2.0781 2.0195-2.0898 3.4727v24.73c-34.734 0.015625-68.957 8.3984-99.766 24.438-30.812 16.043-57.301 39.273-77.234 67.723-2.3633 3.4766-2.3633 8.043 0 11.52l17.664 27.086c1.5078 2.2812 4.0898 3.6211 6.8242 3.5352 2.7305-0.082032 5.2266-1.5742 6.5898-3.9453 14.965-25.41 36.289-46.48 61.879-61.133 25.59-14.652 54.555-22.383 84.043-22.426v24.781c0.011719 1.4531 0.8125 2.7812 2.0898 3.4727 1.2773 0.69141 2.832 0.63672 4.0547-0.14453l74.035-47.977c1.1367-0.73438 1.8242-1.9961 1.8242-3.3516s-0.6875-2.6172-1.8242-3.3555zm-6.1445 210.23c-9.0703 0-17.77 3.6055-24.184 10.02-6.4141 6.4141-10.02 15.113-10.02 24.184s3.6055 17.77 10.02 24.184c6.4141 6.4141 15.113 10.02 24.184 10.02s17.77-3.6055 24.184-10.02c6.4141-6.4141 10.02-15.113 10.02-24.184s-3.6055-17.77-10.02-24.184c-6.4141-6.4141-15.113-10.02-24.184-10.02zm90.727-26.828-10.344 14.953c4.0039 6.9414 7.0859 14.375 9.1641 22.117l17.973 2.9688c6.543 1.1445 11.316 6.8242 11.316 13.465v15.055c0 6.6406-4.7734 12.32-11.316 13.465l-17.766 3.125v-0.003907c-2.1562 7.6992-5.3086 15.082-9.3711 21.965l10.238 14.797h0.003906c3.8047 5.4375 3.1562 12.82-1.5352 17.512l-10.648 10.648h-0.003906c-4.6914 4.6953-12.074 5.3438-17.508 1.5391l-14.797-10.238v-0.003907c-6.9453 4.0039-14.379 7.0859-22.121 9.1641l-3.0195 18.023c-1.1445 6.543-6.8242 11.316-13.465 11.316h-15.055c-6.6406 0-12.32-4.7734-13.465-11.316l-3.125-17.766h0.003907c-7.7031-2.1758-15.086-5.3398-21.965-9.4219l-14.797 10.238v0.003907c-5.4375 3.8047-12.82 3.1562-17.512-1.5391l-10.648-10.648c-4.6953-4.6914-5.3438-12.074-1.5391-17.512l10.238-14.797h0.003907c-4.0039-6.9414-7.0859-14.375-9.1641-22.117l-18.023-2.9688c-6.543-1.1445-11.316-6.8242-11.316-13.465v-15.055c0-6.6406 4.7734-12.32 11.316-13.465l17.766-3.125v0.003907c2.1562-7.6992 5.3086-15.082 9.3711-21.965l-10.238-14.797h-0.003906c-3.8047-5.4375-3.1562-12.82 1.5352-17.512l10.648-10.648h0.003906c4.6914-4.6953 12.074-5.3438 17.508-1.5391l14.797 10.238v0.003907c6.9453-4.0039 14.379-7.0859 22.121-9.1641l3.0195-18.023c1.1445-6.543 6.8242-11.316 13.465-11.316h15.055c6.6406 0 12.32 4.7734 13.465 11.316l3.125 17.766h-0.003907c7.6992 2.1562 15.082 5.3086 21.965 9.3711l14.797-10.238v-0.003906c5.4375-3.8047 12.82-3.1562 17.512 1.5352l10.648 10.648v0.003906c4.6875 4.6367 5.3984 11.957 1.6914 17.406zm-36.047 61.031c0-14.504-5.7578-28.41-16.016-38.664-10.254-10.258-24.16-16.016-38.664-16.016s-28.41 5.7578-38.664 16.016c-10.258 10.254-16.016 24.16-16.016 38.664s5.7578 28.41 16.016 38.664c10.254 10.258 24.16 16.016 38.664 16.016 14.5-0.011719 28.398-5.7773 38.652-16.027 10.25-10.254 16.016-24.152 16.027-38.652z" fill="#fff"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.5 KiB |
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 27 KiB |
+15
-8
@@ -124,18 +124,23 @@ This script generates:
|
||||
- Accurate description and usage examples
|
||||
- Correct icon path (usually `../../../../.icons/your-icon.svg`)
|
||||
- Proper tags that describe your module
|
||||
3. **Create at least one `.tftest.hcl`** to test your module with `terraform test`
|
||||
3. **Create tests for your module:**
|
||||
- **Terraform tests**: Create a `*.tftest.hcl` file and test with `terraform test`
|
||||
- **TypeScript tests**: Create `main.test.ts` file if your module runs scripts or has business logic that Terraform tests can't cover
|
||||
4. **Add any scripts** or additional files your module needs
|
||||
|
||||
### 4. Test and Submit
|
||||
|
||||
```bash
|
||||
# Test your module (from the module directory)
|
||||
# Test your module
|
||||
cd registry/[namespace]/modules/[module-name]
|
||||
|
||||
# Required: Test Terraform functionality
|
||||
terraform init -upgrade
|
||||
terraform test -verbose
|
||||
|
||||
# Or run all tests in the repo
|
||||
./scripts/terraform_test_all.sh
|
||||
# Optional: Test TypeScript files if you have main.test.ts
|
||||
bun test main.test.ts
|
||||
|
||||
# Format code
|
||||
bun run fmt
|
||||
@@ -343,8 +348,8 @@ coder templates push test-[template-name] -d .
|
||||
terraform init -upgrade
|
||||
terraform test -verbose
|
||||
|
||||
# Test all modules
|
||||
./scripts/terraform_test_all.sh
|
||||
# Optional: If you have TypeScript tests
|
||||
bun test main.test.ts
|
||||
```
|
||||
|
||||
### 3. Maintain Backward Compatibility
|
||||
@@ -393,7 +398,9 @@ Example: `https://github.com/coder/registry/compare/main...your-branch?template=
|
||||
### Every Module Must Have
|
||||
|
||||
- `main.tf` - Terraform code
|
||||
- One or more `.tftest.hcl` files - Working tests with `terraform test`
|
||||
- **Tests**:
|
||||
- `*.tftest.hcl` files with `terraform test` (to test terraform specific logic)
|
||||
- `main.test.ts` file with `bun test` (to test business logic, i.e., `coder_script` to install a package.)
|
||||
- `README.md` - Documentation with frontmatter
|
||||
|
||||
### Every Template Must Have
|
||||
@@ -493,7 +500,7 @@ When reporting bugs, include:
|
||||
2. **No tests** or broken tests
|
||||
3. **Hardcoded values** instead of variables
|
||||
4. **Breaking changes** without defaults
|
||||
5. **Not running** formatting (`bun run fmt`) and tests (`terraform test`) before submitting
|
||||
5. **Not running** formatting (`bun run fmt`) and tests (`terraform test`, and `bun test main.test.ts` if applicable) before submitting
|
||||
|
||||
## For Maintainers
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ run "app_url_uses_port" {
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = resource.coder_app.MODULE_NAME.url == "http://localhost:19999"
|
||||
error_message = "Expected MODULE_NAME app URL to include configured port"
|
||||
condition = resource.coder_app.module_name.url == "http://localhost:19999"
|
||||
error_message = "Expected module-name app URL to include configured port"
|
||||
}
|
||||
}
|
||||
|
||||
+12
-12
@@ -35,13 +35,13 @@ variable "agent_id" {
|
||||
|
||||
variable "log_path" {
|
||||
type = string
|
||||
description = "The path to log MODULE_NAME to."
|
||||
default = "/tmp/MODULE_NAME.log"
|
||||
description = "The path to the module log file."
|
||||
default = "/tmp/module_name.log"
|
||||
}
|
||||
|
||||
variable "port" {
|
||||
type = number
|
||||
description = "The port to run MODULE_NAME on."
|
||||
description = "The port to run the application on."
|
||||
default = 19999
|
||||
}
|
||||
|
||||
@@ -59,9 +59,9 @@ variable "order" {
|
||||
# Add other variables here
|
||||
|
||||
|
||||
resource "coder_script" "MODULE_NAME" {
|
||||
resource "coder_script" "module_name" {
|
||||
agent_id = var.agent_id
|
||||
display_name = "MODULE_NAME"
|
||||
display_name = "Module Name"
|
||||
icon = local.icon_url
|
||||
script = templatefile("${path.module}/run.sh", {
|
||||
LOG_PATH : var.log_path,
|
||||
@@ -70,10 +70,10 @@ resource "coder_script" "MODULE_NAME" {
|
||||
run_on_stop = false
|
||||
}
|
||||
|
||||
resource "coder_app" "MODULE_NAME" {
|
||||
resource "coder_app" "module_name" {
|
||||
agent_id = var.agent_id
|
||||
slug = "MODULE_NAME"
|
||||
display_name = "MODULE_NAME"
|
||||
slug = "module-name"
|
||||
display_name = "Module Name"
|
||||
url = "http://localhost:${var.port}"
|
||||
icon = local.icon_url
|
||||
subdomain = false
|
||||
@@ -88,10 +88,10 @@ resource "coder_app" "MODULE_NAME" {
|
||||
}
|
||||
}
|
||||
|
||||
data "coder_parameter" "MODULE_NAME" {
|
||||
type = "list(string)"
|
||||
name = "MODULE_NAME"
|
||||
display_name = "MODULE_NAME"
|
||||
data "coder_parameter" "module_name" {
|
||||
type = "string"
|
||||
name = "module_name"
|
||||
display_name = "Module Name"
|
||||
icon = local.icon_url
|
||||
mutable = var.mutable
|
||||
default = local.options["Option 1"]["value"]
|
||||
|
||||
@@ -13,7 +13,7 @@ Run Auggie CLI in your workspace to access Augment's AI coding assistant with ad
|
||||
```tf
|
||||
module "auggie" {
|
||||
source = "registry.coder.com/coder-labs/auggie/coder"
|
||||
version = "0.1.0"
|
||||
version = "0.2.1"
|
||||
agent_id = coder_agent.example.id
|
||||
folder = "/home/coder/project"
|
||||
}
|
||||
@@ -47,7 +47,7 @@ module "coder-login" {
|
||||
|
||||
module "auggie" {
|
||||
source = "registry.coder.com/coder-labs/auggie/coder"
|
||||
version = "0.1.0"
|
||||
version = "0.2.1"
|
||||
agent_id = coder_agent.example.id
|
||||
folder = "/home/coder/project"
|
||||
|
||||
@@ -103,7 +103,7 @@ EOF
|
||||
```tf
|
||||
module "auggie" {
|
||||
source = "registry.coder.com/coder-labs/auggie/coder"
|
||||
version = "0.1.0"
|
||||
version = "0.2.1"
|
||||
agent_id = coder_agent.example.id
|
||||
folder = "/home/coder/project"
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ variable "install_agentapi" {
|
||||
variable "agentapi_version" {
|
||||
type = string
|
||||
description = "The version of AgentAPI to install."
|
||||
default = "v0.6.0"
|
||||
default = "v0.10.0"
|
||||
validation {
|
||||
condition = can(regex("^v[0-9]+\\.[0-9]+\\.[0-9]+", var.agentapi_version))
|
||||
error_message = "agentapi_version must be a valid semantic version starting with 'v', like 'v0.3.3'."
|
||||
@@ -174,13 +174,15 @@ locals {
|
||||
install_script = file("${path.module}/scripts/install.sh")
|
||||
start_script = file("${path.module}/scripts/start.sh")
|
||||
module_dir_name = ".auggie-module"
|
||||
folder = trimsuffix(var.folder, "/")
|
||||
}
|
||||
|
||||
module "agentapi" {
|
||||
source = "registry.coder.com/coder/agentapi/coder"
|
||||
version = "1.1.1"
|
||||
version = "1.2.0"
|
||||
|
||||
agent_id = var.agent_id
|
||||
folder = local.folder
|
||||
web_app_slug = local.app_slug
|
||||
web_app_order = var.order
|
||||
web_app_group = var.group
|
||||
|
||||
@@ -13,7 +13,7 @@ Run Codex CLI in your workspace to access OpenAI's models through the Codex inte
|
||||
```tf
|
||||
module "codex" {
|
||||
source = "registry.coder.com/coder-labs/codex/coder"
|
||||
version = "2.0.0"
|
||||
version = "2.1.1"
|
||||
agent_id = coder_agent.example.id
|
||||
openai_api_key = var.openai_api_key
|
||||
folder = "/home/coder/project"
|
||||
@@ -33,7 +33,7 @@ module "codex" {
|
||||
module "codex" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder-labs/codex/coder"
|
||||
version = "2.0.0"
|
||||
version = "2.1.1"
|
||||
agent_id = coder_agent.example.id
|
||||
openai_api_key = "..."
|
||||
folder = "/home/coder/project"
|
||||
@@ -60,7 +60,7 @@ module "coder-login" {
|
||||
|
||||
module "codex" {
|
||||
source = "registry.coder.com/coder-labs/codex/coder"
|
||||
version = "2.0.0"
|
||||
version = "2.1.1"
|
||||
agent_id = coder_agent.example.id
|
||||
openai_api_key = "..."
|
||||
ai_prompt = data.coder_parameter.ai_prompt.value
|
||||
@@ -106,7 +106,7 @@ For custom Codex configuration, use `base_config_toml` and/or `additional_mcp_se
|
||||
```tf
|
||||
module "codex" {
|
||||
source = "registry.coder.com/coder-labs/codex/coder"
|
||||
version = "2.0.0"
|
||||
version = "2.1.1"
|
||||
# ... other variables ...
|
||||
|
||||
# Override default configuration
|
||||
|
||||
@@ -80,7 +80,7 @@ variable "install_agentapi" {
|
||||
variable "agentapi_version" {
|
||||
type = string
|
||||
description = "The version of AgentAPI to install."
|
||||
default = "v0.5.0"
|
||||
default = "v0.10.0"
|
||||
}
|
||||
|
||||
variable "codex_model" {
|
||||
@@ -128,9 +128,10 @@ locals {
|
||||
|
||||
module "agentapi" {
|
||||
source = "registry.coder.com/coder/agentapi/coder"
|
||||
version = "1.1.1"
|
||||
version = "1.2.0"
|
||||
|
||||
agent_id = var.agent_id
|
||||
folder = var.folder
|
||||
web_app_slug = local.app_slug
|
||||
web_app_order = var.order
|
||||
web_app_group = var.group
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
display_name: Copilot
|
||||
display_name: Copilot CLI
|
||||
description: GitHub Copilot CLI agent for AI-powered terminal assistance
|
||||
icon: ../../../../.icons/github.svg
|
||||
verified: false
|
||||
@@ -13,7 +13,7 @@ Run [GitHub Copilot CLI](https://docs.github.com/copilot/concepts/agents/about-c
|
||||
```tf
|
||||
module "copilot" {
|
||||
source = "registry.coder.com/coder-labs/copilot/coder"
|
||||
version = "0.1.0"
|
||||
version = "0.2.1"
|
||||
agent_id = coder_agent.example.id
|
||||
workdir = "/home/coder/projects"
|
||||
}
|
||||
@@ -51,7 +51,7 @@ data "coder_parameter" "ai_prompt" {
|
||||
|
||||
module "copilot" {
|
||||
source = "registry.coder.com/coder-labs/copilot/coder"
|
||||
version = "0.1.0"
|
||||
version = "0.2.1"
|
||||
agent_id = coder_agent.example.id
|
||||
workdir = "/home/coder/projects"
|
||||
|
||||
@@ -71,7 +71,7 @@ Customize tool permissions, MCP servers, and Copilot settings:
|
||||
```tf
|
||||
module "copilot" {
|
||||
source = "registry.coder.com/coder-labs/copilot/coder"
|
||||
version = "0.1.0"
|
||||
version = "0.2.1"
|
||||
agent_id = coder_agent.example.id
|
||||
workdir = "/home/coder/projects"
|
||||
|
||||
@@ -142,7 +142,7 @@ variable "github_token" {
|
||||
|
||||
module "copilot" {
|
||||
source = "registry.coder.com/coder-labs/copilot/coder"
|
||||
version = "0.1.0"
|
||||
version = "0.2.1"
|
||||
agent_id = coder_agent.example.id
|
||||
workdir = "/home/coder/projects"
|
||||
github_token = var.github_token
|
||||
@@ -156,7 +156,7 @@ Run Copilot as a command-line tool without task reporting or web interface. This
|
||||
```tf
|
||||
module "copilot" {
|
||||
source = "registry.coder.com/coder-labs/copilot/coder"
|
||||
version = "0.1.0"
|
||||
version = "0.2.1"
|
||||
agent_id = coder_agent.example.id
|
||||
workdir = "/home/coder"
|
||||
report_tasks = false
|
||||
|
||||
@@ -242,7 +242,7 @@ resource "coder_env" "github_token" {
|
||||
|
||||
module "agentapi" {
|
||||
source = "registry.coder.com/coder/agentapi/coder"
|
||||
version = "1.1.1"
|
||||
version = "1.2.0"
|
||||
|
||||
agent_id = var.agent_id
|
||||
folder = local.workdir
|
||||
@@ -253,6 +253,7 @@ module "agentapi" {
|
||||
web_app_display_name = var.web_app_display_name
|
||||
cli_app = var.cli_app
|
||||
cli_app_slug = var.cli_app ? "${local.app_slug}-cli" : null
|
||||
cli_app_icon = var.cli_app ? var.icon : null
|
||||
cli_app_display_name = var.cli_app ? var.cli_app_display_name : null
|
||||
agentapi_subdomain = var.subdomain
|
||||
module_dir_name = local.module_dir_name
|
||||
@@ -295,6 +296,7 @@ module "agentapi" {
|
||||
ARG_COPILOT_CONFIG='${base64encode(local.final_copilot_config)}' \
|
||||
ARG_EXTERNAL_AUTH_ID='${var.external_auth_id}' \
|
||||
ARG_COPILOT_VERSION='${var.copilot_version}' \
|
||||
ARG_COPILOT_MODEL='${var.copilot_model}' \
|
||||
/tmp/install.sh
|
||||
EOT
|
||||
}
|
||||
@@ -14,6 +14,7 @@ ARG_MCP_CONFIG=$(echo -n "${ARG_MCP_CONFIG:-}" | base64 -d 2> /dev/null || echo
|
||||
ARG_COPILOT_CONFIG=$(echo -n "${ARG_COPILOT_CONFIG:-}" | base64 -d 2> /dev/null || echo "")
|
||||
ARG_EXTERNAL_AUTH_ID=${ARG_EXTERNAL_AUTH_ID:-github}
|
||||
ARG_COPILOT_VERSION=${ARG_COPILOT_VERSION:-0.0.334}
|
||||
ARG_COPILOT_MODEL=${ARG_COPILOT_MODEL:-claude-sonnet-4.5}
|
||||
|
||||
validate_prerequisites() {
|
||||
if ! command_exists node; then
|
||||
@@ -84,7 +85,6 @@ setup_copilot_configurations() {
|
||||
|
||||
local module_path="$HOME/.copilot-module"
|
||||
mkdir -p "$module_path"
|
||||
mkdir -p "$HOME/.config"
|
||||
|
||||
setup_copilot_config
|
||||
|
||||
@@ -92,7 +92,8 @@ setup_copilot_configurations() {
|
||||
}
|
||||
|
||||
setup_copilot_config() {
|
||||
local copilot_config_dir="$HOME/.copilot"
|
||||
export XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-$HOME/.config}"
|
||||
local copilot_config_dir="$XDG_CONFIG_HOME/.copilot"
|
||||
local copilot_config_file="$copilot_config_dir/config.json"
|
||||
local mcp_config_file="$copilot_config_dir/mcp-config.json"
|
||||
|
||||
|
||||
@@ -82,6 +82,7 @@ check_existing_session() {
|
||||
}
|
||||
|
||||
setup_github_authentication() {
|
||||
export XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-$HOME/.config}"
|
||||
echo "Setting up GitHub authentication..."
|
||||
|
||||
if [ -n "${GITHUB_TOKEN:-}" ]; then
|
||||
|
||||
@@ -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.1.1"
|
||||
version = "0.2.1"
|
||||
agent_id = coder_agent.example.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.1.1"
|
||||
version = "0.2.1"
|
||||
agent_id = coder_agent.example.id
|
||||
folder = "/home/coder/project"
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ variable "install_agentapi" {
|
||||
variable "agentapi_version" {
|
||||
type = string
|
||||
description = "The version of AgentAPI to install."
|
||||
default = "v0.5.0"
|
||||
default = "v0.10.0"
|
||||
}
|
||||
|
||||
variable "force" {
|
||||
@@ -113,6 +113,7 @@ locals {
|
||||
install_script = file("${path.module}/scripts/install.sh")
|
||||
start_script = file("${path.module}/scripts/start.sh")
|
||||
module_dir_name = ".cursor-cli-module"
|
||||
folder = trimsuffix(var.folder, "/")
|
||||
}
|
||||
|
||||
# Expose status slug and API key to the agent environment
|
||||
@@ -131,9 +132,10 @@ resource "coder_env" "cursor_api_key" {
|
||||
|
||||
module "agentapi" {
|
||||
source = "registry.coder.com/coder/agentapi/coder"
|
||||
version = "1.1.1"
|
||||
version = "1.2.0"
|
||||
|
||||
agent_id = var.agent_id
|
||||
folder = local.folder
|
||||
web_app_slug = local.app_slug
|
||||
web_app_order = var.order
|
||||
web_app_group = var.group
|
||||
|
||||
@@ -13,7 +13,7 @@ Run [Gemini CLI](https://github.com/google-gemini/gemini-cli) in your workspace
|
||||
```tf
|
||||
module "gemini" {
|
||||
source = "registry.coder.com/coder-labs/gemini/coder"
|
||||
version = "2.0.0"
|
||||
version = "2.1.1"
|
||||
agent_id = coder_agent.example.id
|
||||
folder = "/home/coder/project"
|
||||
}
|
||||
@@ -46,7 +46,7 @@ variable "gemini_api_key" {
|
||||
|
||||
module "gemini" {
|
||||
source = "registry.coder.com/coder-labs/gemini/coder"
|
||||
version = "2.0.0"
|
||||
version = "2.1.1"
|
||||
agent_id = coder_agent.example.id
|
||||
gemini_api_key = var.gemini_api_key
|
||||
folder = "/home/coder/project"
|
||||
@@ -94,7 +94,7 @@ data "coder_parameter" "ai_prompt" {
|
||||
module "gemini" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder-labs/gemini/coder"
|
||||
version = "2.0.0"
|
||||
version = "2.1.1"
|
||||
agent_id = coder_agent.example.id
|
||||
gemini_api_key = var.gemini_api_key
|
||||
gemini_model = "gemini-2.5-flash"
|
||||
@@ -118,7 +118,7 @@ For enterprise users who prefer Google's Vertex AI platform:
|
||||
```tf
|
||||
module "gemini" {
|
||||
source = "registry.coder.com/coder-labs/gemini/coder"
|
||||
version = "2.0.0"
|
||||
version = "2.1.1"
|
||||
agent_id = coder_agent.example.id
|
||||
gemini_api_key = var.gemini_api_key
|
||||
folder = "/home/coder/project"
|
||||
|
||||
@@ -81,7 +81,7 @@ variable "install_agentapi" {
|
||||
variable "agentapi_version" {
|
||||
type = string
|
||||
description = "The version of AgentAPI to install."
|
||||
default = "v0.2.3"
|
||||
default = "v0.10.0"
|
||||
}
|
||||
|
||||
variable "gemini_model" {
|
||||
@@ -172,13 +172,15 @@ EOT
|
||||
install_script = file("${path.module}/scripts/install.sh")
|
||||
start_script = file("${path.module}/scripts/start.sh")
|
||||
module_dir_name = ".gemini-module"
|
||||
folder = trimsuffix(var.folder, "/")
|
||||
}
|
||||
|
||||
module "agentapi" {
|
||||
source = "registry.coder.com/coder/agentapi/coder"
|
||||
version = "1.1.1"
|
||||
version = "1.2.0"
|
||||
|
||||
agent_id = var.agent_id
|
||||
folder = local.folder
|
||||
web_app_slug = local.app_slug
|
||||
web_app_order = var.order
|
||||
web_app_group = var.group
|
||||
|
||||
@@ -13,7 +13,7 @@ Run [Amp CLI](https://ampcode.com/) in your workspace to access Sourcegraph's AI
|
||||
```tf
|
||||
module "amp-cli" {
|
||||
source = "registry.coder.com/coder-labs/sourcegraph-amp/coder"
|
||||
version = "1.0.3"
|
||||
version = "1.1.1"
|
||||
agent_id = coder_agent.example.id
|
||||
sourcegraph_amp_api_key = var.sourcegraph_amp_api_key
|
||||
install_sourcegraph_amp = true
|
||||
@@ -60,7 +60,7 @@ variable "sourcegraph_amp_api_key" {
|
||||
module "amp-cli" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder-labs/sourcegraph-amp/coder"
|
||||
version = "1.0.3"
|
||||
version = "1.1.1"
|
||||
agent_id = coder_agent.example.id
|
||||
sourcegraph_amp_api_key = var.sourcegraph_amp_api_key # recommended for authenticated usage
|
||||
install_sourcegraph_amp = true
|
||||
|
||||
@@ -69,7 +69,7 @@ variable "install_agentapi" {
|
||||
variable "agentapi_version" {
|
||||
type = string
|
||||
description = "The version of AgentAPI to install."
|
||||
default = "v0.3.0"
|
||||
default = "v0.10.0"
|
||||
}
|
||||
|
||||
variable "pre_install_script" {
|
||||
@@ -147,13 +147,15 @@ locals {
|
||||
install_script = file("${path.module}/scripts/install.sh")
|
||||
start_script = file("${path.module}/scripts/start.sh")
|
||||
module_dir_name = ".sourcegraph-amp-module"
|
||||
folder = trimsuffix(var.folder, "/")
|
||||
}
|
||||
|
||||
module "agentapi" {
|
||||
source = "registry.coder.com/coder/agentapi/coder"
|
||||
version = "1.0.1"
|
||||
version = "1.2.0"
|
||||
|
||||
agent_id = var.agent_id
|
||||
folder = local.folder
|
||||
web_app_slug = local.app_slug
|
||||
web_app_order = var.order
|
||||
web_app_group = var.group
|
||||
|
||||
@@ -16,7 +16,7 @@ The AgentAPI module is a building block for modules that need to run an AgentAPI
|
||||
```tf
|
||||
module "agentapi" {
|
||||
source = "registry.coder.com/coder/agentapi/coder"
|
||||
version = "1.1.1"
|
||||
version = "1.2.0"
|
||||
|
||||
agent_id = var.agent_id
|
||||
web_app_slug = local.app_slug
|
||||
|
||||
@@ -117,7 +117,7 @@ variable "install_agentapi" {
|
||||
variable "agentapi_version" {
|
||||
type = string
|
||||
description = "The version of AgentAPI to install."
|
||||
default = "v0.3.3"
|
||||
default = "v0.10.0"
|
||||
}
|
||||
|
||||
variable "agentapi_port" {
|
||||
|
||||
@@ -13,7 +13,7 @@ Run [Amazon Q](https://aws.amazon.com/q/) in your workspace to access Amazon's A
|
||||
```tf
|
||||
module "amazon-q" {
|
||||
source = "registry.coder.com/coder/amazon-q/coder"
|
||||
version = "2.0.0"
|
||||
version = "2.1.1"
|
||||
agent_id = coder_agent.example.id
|
||||
workdir = "/home/coder"
|
||||
|
||||
@@ -102,7 +102,7 @@ data "coder_parameter" "ai_prompt" {
|
||||
|
||||
module "amazon-q" {
|
||||
source = "registry.coder.com/coder/amazon-q/coder"
|
||||
version = "2.0.0"
|
||||
version = "2.1.1"
|
||||
agent_id = coder_agent.example.id
|
||||
workdir = "/home/coder"
|
||||
auth_tarball = var.amazon_q_auth_tarball
|
||||
@@ -228,7 +228,7 @@ If no custom `agent_config` is provided, the default agent name "agent" is used.
|
||||
```tf
|
||||
module "amazon-q" {
|
||||
source = "registry.coder.com/coder/amazon-q/coder"
|
||||
version = "2.0.0"
|
||||
version = "2.1.1"
|
||||
agent_id = coder_agent.example.id
|
||||
workdir = "/home/coder"
|
||||
auth_tarball = var.amazon_q_auth_tarball
|
||||
@@ -258,7 +258,7 @@ This example will:
|
||||
```tf
|
||||
module "amazon-q" {
|
||||
source = "registry.coder.com/coder/amazon-q/coder"
|
||||
version = "2.0.0"
|
||||
version = "2.1.1"
|
||||
agent_id = coder_agent.example.id
|
||||
workdir = "/home/coder"
|
||||
auth_tarball = var.amazon_q_auth_tarball
|
||||
@@ -279,7 +279,7 @@ module "amazon-q" {
|
||||
```tf
|
||||
module "amazon-q" {
|
||||
source = "registry.coder.com/coder/amazon-q/coder"
|
||||
version = "2.0.0"
|
||||
version = "2.1.1"
|
||||
agent_id = coder_agent.example.id
|
||||
workdir = "/home/coder"
|
||||
auth_tarball = var.amazon_q_auth_tarball
|
||||
@@ -305,7 +305,7 @@ module "amazon-q" {
|
||||
```tf
|
||||
module "amazon-q" {
|
||||
source = "registry.coder.com/coder/amazon-q/coder"
|
||||
version = "2.0.0"
|
||||
version = "2.1.1"
|
||||
agent_id = coder_agent.example.id
|
||||
workdir = "/home/coder"
|
||||
auth_tarball = var.amazon_q_auth_tarball
|
||||
@@ -319,7 +319,7 @@ module "amazon-q" {
|
||||
```tf
|
||||
module "amazon-q" {
|
||||
source = "registry.coder.com/coder/amazon-q/coder"
|
||||
version = "2.0.0"
|
||||
version = "2.1.1"
|
||||
agent_id = coder_agent.example.id
|
||||
workdir = "/home/coder"
|
||||
auth_tarball = var.amazon_q_auth_tarball
|
||||
@@ -340,14 +340,14 @@ module "amazon-q" {
|
||||
```tf
|
||||
module "amazon-q" {
|
||||
source = "registry.coder.com/coder/amazon-q/coder"
|
||||
version = "2.0.0"
|
||||
version = "2.1.1"
|
||||
agent_id = coder_agent.example.id
|
||||
workdir = "/home/coder"
|
||||
auth_tarball = var.amazon_q_auth_tarball
|
||||
|
||||
# AgentAPI configuration for environments without wildcard access url. https://coder.com/docs/admin/setup#wildcard-access-url
|
||||
agentapi_chat_based_path = true
|
||||
agentapi_version = "v0.6.1"
|
||||
agentapi_version = "v0.10.0"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -358,7 +358,7 @@ For environments without direct internet access, you can host Amazon Q installat
|
||||
```tf
|
||||
module "amazon-q" {
|
||||
source = "registry.coder.com/coder/amazon-q/coder"
|
||||
version = "2.0.0"
|
||||
version = "2.1.1"
|
||||
agent_id = coder_agent.example.id
|
||||
workdir = "/home/coder"
|
||||
auth_tarball = var.amazon_q_auth_tarball
|
||||
|
||||
@@ -88,7 +88,7 @@ variable "post_install_script" {
|
||||
variable "agentapi_version" {
|
||||
type = string
|
||||
description = "The version of AgentAPI to install."
|
||||
default = "v0.6.1"
|
||||
default = "v0.10.0"
|
||||
}
|
||||
|
||||
variable "workdir" {
|
||||
@@ -96,8 +96,6 @@ variable "workdir" {
|
||||
description = "The folder to run Amazon Q in."
|
||||
}
|
||||
|
||||
# ---------------------------------------------
|
||||
|
||||
variable "install_amazon_q" {
|
||||
type = bool
|
||||
description = "Whether to install Amazon Q."
|
||||
@@ -190,6 +188,7 @@ resource "coder_env" "auth_tarball" {
|
||||
|
||||
locals {
|
||||
app_slug = "amazonq"
|
||||
workdir = trimsuffix(var.workdir, "/")
|
||||
install_script = file("${path.module}/scripts/install.sh")
|
||||
start_script = file("${path.module}/scripts/start.sh")
|
||||
module_dir_name = ".amazonq-module"
|
||||
@@ -215,9 +214,10 @@ locals {
|
||||
|
||||
module "agentapi" {
|
||||
source = "registry.coder.com/coder/agentapi/coder"
|
||||
version = "1.1.1"
|
||||
version = "1.2.0"
|
||||
|
||||
agent_id = var.agent_id
|
||||
folder = local.workdir
|
||||
web_app_slug = local.app_slug
|
||||
web_app_order = var.order
|
||||
web_app_group = var.group
|
||||
|
||||
@@ -94,6 +94,13 @@ function install_amazon_q() {
|
||||
function extract_auth_tarball() {
|
||||
if [ -n "$ARG_AUTH_TARBALL" ]; then
|
||||
echo "Extracting auth tarball..."
|
||||
|
||||
if ! command_exists zstd; then
|
||||
echo "Error: zstd is required to extract the authentication tarball but is not installed."
|
||||
echo "Please install zstd using the pre_install_script parameter."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
PREV_DIR="$PWD"
|
||||
echo "$ARG_AUTH_TARBALL" | base64 -d > /tmp/auth.tar.zst
|
||||
rm -rf ~/.local/share/amazon-q
|
||||
|
||||
@@ -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 = "3.0.3"
|
||||
version = "3.1.1"
|
||||
agent_id = coder_agent.example.id
|
||||
workdir = "/home/coder/project"
|
||||
claude_api_key = "xxxx-xxxxx-xxxx"
|
||||
@@ -49,7 +49,7 @@ data "coder_parameter" "ai_prompt" {
|
||||
|
||||
module "claude-code" {
|
||||
source = "registry.coder.com/coder/claude-code/coder"
|
||||
version = "3.0.3"
|
||||
version = "3.1.1"
|
||||
agent_id = coder_agent.example.id
|
||||
workdir = "/home/coder/project"
|
||||
|
||||
@@ -58,7 +58,7 @@ module "claude-code" {
|
||||
claude_code_oauth_token = "xxxxx-xxxx-xxxx"
|
||||
|
||||
claude_code_version = "1.0.82" # Pin to a specific version
|
||||
agentapi_version = "v0.6.1"
|
||||
agentapi_version = "v0.10.0"
|
||||
|
||||
ai_prompt = data.coder_parameter.ai_prompt.value
|
||||
model = "sonnet"
|
||||
@@ -85,7 +85,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 = "3.0.3"
|
||||
version = "3.1.1"
|
||||
agent_id = coder_agent.example.id
|
||||
workdir = "/home/coder"
|
||||
install_claude_code = true
|
||||
@@ -108,13 +108,168 @@ variable "claude_code_oauth_token" {
|
||||
|
||||
module "claude-code" {
|
||||
source = "registry.coder.com/coder/claude-code/coder"
|
||||
version = "3.0.3"
|
||||
version = "3.1.1"
|
||||
agent_id = coder_agent.example.id
|
||||
workdir = "/home/coder/project"
|
||||
claude_code_oauth_token = var.claude_code_oauth_token
|
||||
}
|
||||
```
|
||||
|
||||
### Usage with AWS Bedrock
|
||||
|
||||
#### Prerequisites
|
||||
|
||||
AWS account with Bedrock access, Claude models enabled in Bedrock console, appropriate IAM permissions.
|
||||
|
||||
Configure Claude Code to use AWS Bedrock for accessing Claude models through your AWS infrastructure.
|
||||
|
||||
```tf
|
||||
resource "coder_env" "bedrock_use" {
|
||||
agent_id = coder_agent.example.id
|
||||
name = "CLAUDE_CODE_USE_BEDROCK"
|
||||
value = "1"
|
||||
}
|
||||
|
||||
resource "coder_env" "aws_region" {
|
||||
agent_id = coder_agent.example.id
|
||||
name = "AWS_REGION"
|
||||
value = "us-east-1" # Choose your preferred region
|
||||
}
|
||||
|
||||
# Option 1: Using AWS credentials
|
||||
|
||||
variable "aws_access_key_id" {
|
||||
type = string
|
||||
description = "Your AWS access key ID. Create this in the AWS IAM console under 'Security credentials'."
|
||||
sensitive = true
|
||||
value = "xxxx-xxx-xxxx"
|
||||
}
|
||||
|
||||
variable "aws_secret_access_key" {
|
||||
type = string
|
||||
description = "Your AWS secret access key. This is shown once when you create an access key in the AWS IAM console."
|
||||
sensitive = true
|
||||
value = "xxxx-xxx-xxxx"
|
||||
}
|
||||
|
||||
resource "coder_env" "aws_access_key_id" {
|
||||
agent_id = coder_agent.example.id
|
||||
name = "AWS_ACCESS_KEY_ID"
|
||||
value = var.aws_access_key_id
|
||||
}
|
||||
|
||||
resource "coder_env" "aws_secret_access_key" {
|
||||
agent_id = coder_agent.example.id
|
||||
name = "AWS_SECRET_ACCESS_KEY"
|
||||
value = var.aws_secret_access_key
|
||||
}
|
||||
|
||||
# Option 2: Using Bedrock API key (simpler)
|
||||
|
||||
variable "aws_bearer_token_bedrock" {
|
||||
type = string
|
||||
description = "Your AWS Bedrock bearer token. This provides access to Bedrock without needing separate access key and secret key."
|
||||
sensitive = true
|
||||
value = "xxxx-xxx-xxxx"
|
||||
}
|
||||
|
||||
resource "coder_env" "bedrock_api_key" {
|
||||
agent_id = coder_agent.example.id
|
||||
name = "AWS_BEARER_TOKEN_BEDROCK"
|
||||
value = var.aws_bearer_token_bedrock
|
||||
}
|
||||
|
||||
module "claude-code" {
|
||||
source = "registry.coder.com/coder/claude-code/coder"
|
||||
version = "3.1.1"
|
||||
agent_id = coder_agent.example.id
|
||||
workdir = "/home/coder/project"
|
||||
model = "global.anthropic.claude-sonnet-4-5-20250929-v1:0"
|
||||
}
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> For additional Bedrock configuration options (model selection, token limits, region overrides, etc.), see the [Claude Code Bedrock documentation](https://docs.claude.com/en/docs/claude-code/amazon-bedrock).
|
||||
|
||||
### Usage with Google Vertex AI
|
||||
|
||||
#### 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).
|
||||
|
||||
Configure Claude Code to use Google Vertex AI for accessing Claude models through Google Cloud Platform.
|
||||
|
||||
```tf
|
||||
variable "vertex_sa_json" {
|
||||
type = string
|
||||
description = "The complete JSON content of your Google Cloud service account key file. Create a service account in the GCP Console under 'IAM & Admin > Service Accounts', then create and download a JSON key. Copy the entire JSON content into this variable."
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
resource "coder_env" "vertex_use" {
|
||||
agent_id = coder_agent.example.id
|
||||
name = "CLAUDE_CODE_USE_VERTEX"
|
||||
value = "1"
|
||||
}
|
||||
|
||||
resource "coder_env" "vertex_project_id" {
|
||||
agent_id = coder_agent.example.id
|
||||
name = "ANTHROPIC_VERTEX_PROJECT_ID"
|
||||
value = "your-gcp-project-id"
|
||||
}
|
||||
|
||||
resource "coder_env" "cloud_ml_region" {
|
||||
agent_id = coder_agent.example.id
|
||||
name = "CLOUD_ML_REGION"
|
||||
value = "global"
|
||||
}
|
||||
|
||||
resource "coder_env" "vertex_sa_json" {
|
||||
agent_id = coder_agent.example.id
|
||||
name = "VERTEX_SA_JSON"
|
||||
value = var.vertex_sa_json
|
||||
}
|
||||
|
||||
resource "coder_env" "google_application_credentials" {
|
||||
agent_id = coder_agent.example.id
|
||||
name = "GOOGLE_APPLICATION_CREDENTIALS"
|
||||
value = "/tmp/gcp-sa.json"
|
||||
}
|
||||
|
||||
module "claude-code" {
|
||||
source = "registry.coder.com/coder/claude-code/coder"
|
||||
version = "3.1.1"
|
||||
agent_id = coder_agent.example.id
|
||||
workdir = "/home/coder/project"
|
||||
model = "claude-sonnet-4@20250514"
|
||||
|
||||
pre_install_script = <<-EOT
|
||||
#!/bin/bash
|
||||
# Write the service account JSON to a file
|
||||
echo "$VERTEX_SA_JSON" > /tmp/gcp-sa.json
|
||||
|
||||
# Install prerequisite packages
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y apt-transport-https ca-certificates gnupg curl
|
||||
|
||||
# Add Google Cloud public key
|
||||
curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo gpg --dearmor -o /usr/share/keyrings/cloud.google.gpg
|
||||
|
||||
# Add Google Cloud SDK repo to apt sources
|
||||
echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] https://packages.cloud.google.com/apt cloud-sdk main" | sudo tee /etc/apt/sources.list.d/google-cloud-sdk.list
|
||||
|
||||
# Update and install the Google Cloud SDK
|
||||
sudo apt-get update && sudo apt-get install -y google-cloud-cli
|
||||
|
||||
# Authenticate gcloud with the service account
|
||||
gcloud auth activate-service-account --key-file=/tmp/gcp-sa.json
|
||||
EOT
|
||||
}
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> For additional Vertex AI configuration options (model selection, token limits, region overrides, etc.), see the [Claude Code Vertex AI documentation](https://docs.claude.com/en/docs/claude-code/google-vertex-ai).
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If you encounter any issues, check the log files in the `~/.claude-module` directory within your workspace for detailed information.
|
||||
|
||||
@@ -86,7 +86,7 @@ variable "install_agentapi" {
|
||||
variable "agentapi_version" {
|
||||
type = string
|
||||
description = "The version of AgentAPI to install."
|
||||
default = "v0.7.1"
|
||||
default = "v0.10.0"
|
||||
}
|
||||
|
||||
variable "ai_prompt" {
|
||||
@@ -265,7 +265,7 @@ locals {
|
||||
module "agentapi" {
|
||||
|
||||
source = "registry.coder.com/coder/agentapi/coder"
|
||||
version = "1.1.1"
|
||||
version = "1.2.0"
|
||||
|
||||
agent_id = var.agent_id
|
||||
web_app_slug = local.app_slug
|
||||
|
||||
@@ -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.1.1"
|
||||
version = "1.2.0"
|
||||
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.1.1"
|
||||
version = "1.2.0"
|
||||
agent_id = coder_agent.example.id
|
||||
url = "https://github.com/coder/coder"
|
||||
base_dir = "~/projects/coder"
|
||||
@@ -43,12 +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.1.1"
|
||||
version = "1.2.0"
|
||||
agent_id = coder_agent.example.id
|
||||
url = "https://github.com/coder/coder"
|
||||
}
|
||||
|
||||
data "coder_git_auth" "github" {
|
||||
data "coder_external_auth" "github" {
|
||||
id = "github"
|
||||
}
|
||||
```
|
||||
@@ -69,7 +69,7 @@ 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.1.1"
|
||||
version = "1.2.0"
|
||||
agent_id = coder_agent.example.id
|
||||
url = data.coder_parameter.git_repo.value
|
||||
}
|
||||
@@ -103,7 +103,7 @@ 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.1.1"
|
||||
version = "1.2.0"
|
||||
agent_id = coder_agent.example.id
|
||||
url = "https://github.example.com/coder/coder/tree/feat/example"
|
||||
git_providers = {
|
||||
@@ -122,7 +122,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.1.1"
|
||||
version = "1.2.0"
|
||||
agent_id = coder_agent.example.id
|
||||
url = "https://gitlab.com/coder/coder/-/tree/feat/example"
|
||||
}
|
||||
@@ -134,7 +134,7 @@ 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.1.1"
|
||||
version = "1.2.0"
|
||||
agent_id = coder_agent.example.id
|
||||
url = "https://gitlab.example.com/coder/coder/-/tree/feat/example"
|
||||
git_providers = {
|
||||
@@ -155,7 +155,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.1.1"
|
||||
version = "1.2.0"
|
||||
agent_id = coder_agent.example.id
|
||||
url = "https://github.com/coder/coder"
|
||||
branch_name = "feat/example"
|
||||
@@ -173,7 +173,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.1.1"
|
||||
version = "1.2.0"
|
||||
agent_id = coder_agent.example.id
|
||||
url = "https://github.com/coder/coder"
|
||||
folder_name = "coder-dev"
|
||||
@@ -192,9 +192,32 @@ If not defined, the default, `0`, performs a full clone.
|
||||
module "git-clone" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/git-clone/coder"
|
||||
version = "1.1.0"
|
||||
version = "1.2.0"
|
||||
agent_id = coder_agent.example.id
|
||||
url = "https://github.com/coder/coder"
|
||||
depth = 1
|
||||
}
|
||||
```
|
||||
|
||||
## Post-clone script
|
||||
|
||||
Run a custom script after cloning the repository by setting the `post_clone_script` variable.
|
||||
This is useful for running initialization tasks like installing dependencies or setting up the environment.
|
||||
|
||||
```tf
|
||||
module "git-clone" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/git-clone/coder"
|
||||
version = "1.2.0"
|
||||
agent_id = coder_agent.example.id
|
||||
url = "https://github.com/coder/coder"
|
||||
post_clone_script = <<-EOT
|
||||
#!/bin/bash
|
||||
echo "Repository cloned successfully!"
|
||||
# Install dependencies
|
||||
npm install
|
||||
# Run any other initialization tasks
|
||||
make setup
|
||||
EOT
|
||||
}
|
||||
```
|
||||
|
||||
@@ -30,11 +30,12 @@ describe("git-clone", async () => {
|
||||
url: "fake-url",
|
||||
});
|
||||
const output = await executeScriptInContainer(state, "alpine/git");
|
||||
expect(output.exitCode).toBe(128);
|
||||
expect(output.stdout).toEqual([
|
||||
"Creating directory ~/fake-url...",
|
||||
"Cloning fake-url to ~/fake-url...",
|
||||
]);
|
||||
expect(output.stderr.join(" ")).toContain("fatal");
|
||||
expect(output.stderr.join(" ")).toContain("fake-url");
|
||||
});
|
||||
|
||||
it("repo_dir should match repo name for https", async () => {
|
||||
@@ -244,4 +245,20 @@ describe("git-clone", async () => {
|
||||
"Cloning https://github.com/michaelbrewer/repo-tests.log to ~/repo-tests.log on branch feat/branch...",
|
||||
]);
|
||||
});
|
||||
|
||||
it("runs post-clone script", async () => {
|
||||
const state = await runTerraformApply(import.meta.dir, {
|
||||
agent_id: "foo",
|
||||
url: "fake-url",
|
||||
post_clone_script: "echo 'Post-clone script executed'",
|
||||
});
|
||||
const output = await executeScriptInContainer(
|
||||
state,
|
||||
"alpine/git",
|
||||
"sh",
|
||||
"mkdir -p ~/fake-url && echo 'existing' > ~/fake-url/file.txt",
|
||||
);
|
||||
expect(output.stdout).toContain("Running post-clone script...");
|
||||
expect(output.stdout).toContain("Post-clone script executed");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -62,6 +62,12 @@ variable "depth" {
|
||||
default = 0
|
||||
}
|
||||
|
||||
variable "post_clone_script" {
|
||||
description = "Custom script to run after cloning the repository. Runs always after git clone, even if the repository already exists."
|
||||
type = string
|
||||
default = null
|
||||
}
|
||||
|
||||
locals {
|
||||
# Remove query parameters and fragments from the URL
|
||||
url = replace(replace(var.url, "/\\?.*/", ""), "/#.*/", "")
|
||||
@@ -81,6 +87,8 @@ locals {
|
||||
clone_path = var.base_dir != "" ? join("/", [var.base_dir, local.folder_name]) : join("/", ["~", local.folder_name])
|
||||
# Construct the web URL
|
||||
web_url = startswith(local.clone_url, "git@") ? replace(replace(local.clone_url, ":", "/"), "git@", "https://") : local.clone_url
|
||||
# Encode the post_clone_script for passing to the shell script
|
||||
encoded_post_clone_script = var.post_clone_script != null ? base64encode(var.post_clone_script) : ""
|
||||
}
|
||||
|
||||
output "repo_dir" {
|
||||
@@ -120,6 +128,7 @@ resource "coder_script" "git_clone" {
|
||||
REPO_URL : local.clone_url,
|
||||
BRANCH_NAME : local.branch_name,
|
||||
DEPTH = var.depth,
|
||||
POST_CLONE_SCRIPT : local.encoded_post_clone_script,
|
||||
})
|
||||
display_name = "Git Clone"
|
||||
icon = "/icon/git.svg"
|
||||
|
||||
@@ -6,6 +6,7 @@ BRANCH_NAME="${BRANCH_NAME}"
|
||||
# Expand home if it's specified!
|
||||
CLONE_PATH="$${CLONE_PATH/#\~/$${HOME}}"
|
||||
DEPTH="${DEPTH}"
|
||||
POST_CLONE_SCRIPT="${POST_CLONE_SCRIPT}"
|
||||
|
||||
# Check if the variable is empty...
|
||||
if [ -z "$REPO_URL" ]; then
|
||||
@@ -52,5 +53,14 @@ if [ -z "$(ls -A "$CLONE_PATH")" ]; then
|
||||
fi
|
||||
else
|
||||
echo "$CLONE_PATH already exists and isn't empty, skipping clone!"
|
||||
exit 0
|
||||
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
|
||||
cd "$CLONE_PATH"
|
||||
/tmp/post_clone.sh
|
||||
rm /tmp/post_clone.sh
|
||||
fi
|
||||
|
||||
@@ -13,7 +13,7 @@ Run the [Goose](https://block.github.io/goose/) agent in your workspace to gener
|
||||
```tf
|
||||
module "goose" {
|
||||
source = "registry.coder.com/coder/goose/coder"
|
||||
version = "2.1.2"
|
||||
version = "2.2.1"
|
||||
agent_id = coder_agent.example.id
|
||||
folder = "/home/coder"
|
||||
install_goose = true
|
||||
@@ -79,7 +79,7 @@ resource "coder_agent" "main" {
|
||||
module "goose" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/goose/coder"
|
||||
version = "2.1.2"
|
||||
version = "2.2.1"
|
||||
agent_id = coder_agent.example.id
|
||||
folder = "/home/coder"
|
||||
install_goose = true
|
||||
|
||||
@@ -63,7 +63,7 @@ variable "install_agentapi" {
|
||||
variable "agentapi_version" {
|
||||
type = string
|
||||
description = "The version of AgentAPI to install."
|
||||
default = "v0.3.3"
|
||||
default = "v0.10.0"
|
||||
}
|
||||
|
||||
variable "subdomain" {
|
||||
@@ -135,11 +135,12 @@ EOT
|
||||
install_script = file("${path.module}/scripts/install.sh")
|
||||
start_script = file("${path.module}/scripts/start.sh")
|
||||
module_dir_name = ".goose-module"
|
||||
folder = trimsuffix(var.folder, "/")
|
||||
}
|
||||
|
||||
module "agentapi" {
|
||||
source = "registry.coder.com/coder/agentapi/coder"
|
||||
version = "1.1.1"
|
||||
version = "1.2.0"
|
||||
|
||||
agent_id = var.agent_id
|
||||
web_app_slug = local.app_slug
|
||||
@@ -156,6 +157,7 @@ module "agentapi" {
|
||||
pre_install_script = var.pre_install_script
|
||||
post_install_script = var.post_install_script
|
||||
start_script = local.start_script
|
||||
folder = local.folder
|
||||
install_script = <<-EOT
|
||||
#!/bin/bash
|
||||
set -o errexit
|
||||
|
||||
@@ -10,6 +10,7 @@ tags: [ide, jetbrains, parameter, gateway]
|
||||
|
||||
This module adds a JetBrains Gateway Button to open any workspace with a single click.
|
||||
|
||||
> [!TIP]
|
||||
> We recommend using the [Coder Toolbox module](https://registry.coder.com/modules/coder/jetbrains), which offers significant stability and connectivity benefits over Gateway. Reference our [documentation](https://coder.com/docs/user-guides/workspace-access/jetbrains/toolbox) for more information.
|
||||
|
||||
JetBrains recommends a minimum of 4 CPU cores and 8GB of RAM.
|
||||
@@ -19,7 +20,7 @@ Consult the [JetBrains documentation](https://www.jetbrains.com/help/idea/prereq
|
||||
module "jetbrains_gateway" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/jetbrains-gateway/coder"
|
||||
version = "1.2.4"
|
||||
version = "1.2.5"
|
||||
agent_id = coder_agent.example.id
|
||||
folder = "/home/coder/example"
|
||||
jetbrains_ides = ["CL", "GO", "IU", "PY", "WS"]
|
||||
@@ -37,7 +38,7 @@ module "jetbrains_gateway" {
|
||||
module "jetbrains_gateway" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/jetbrains-gateway/coder"
|
||||
version = "1.2.4"
|
||||
version = "1.2.5"
|
||||
agent_id = coder_agent.example.id
|
||||
folder = "/home/coder/example"
|
||||
jetbrains_ides = ["GO", "WS"]
|
||||
@@ -51,7 +52,7 @@ module "jetbrains_gateway" {
|
||||
module "jetbrains_gateway" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/jetbrains-gateway/coder"
|
||||
version = "1.2.4"
|
||||
version = "1.2.5"
|
||||
agent_id = coder_agent.example.id
|
||||
folder = "/home/coder/example"
|
||||
jetbrains_ides = ["IU", "PY"]
|
||||
@@ -66,7 +67,7 @@ module "jetbrains_gateway" {
|
||||
module "jetbrains_gateway" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/jetbrains-gateway/coder"
|
||||
version = "1.2.4"
|
||||
version = "1.2.5"
|
||||
agent_id = coder_agent.example.id
|
||||
folder = "/home/coder/example"
|
||||
jetbrains_ides = ["IU", "PY"]
|
||||
@@ -91,7 +92,7 @@ module "jetbrains_gateway" {
|
||||
module "jetbrains_gateway" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/jetbrains-gateway/coder"
|
||||
version = "1.2.4"
|
||||
version = "1.2.5"
|
||||
agent_id = coder_agent.example.id
|
||||
folder = "/home/coder/example"
|
||||
jetbrains_ides = ["GO", "WS"]
|
||||
@@ -109,7 +110,7 @@ Due to the highest priority of the `ide_download_link` parameter in the `(jetbra
|
||||
module "jetbrains_gateway" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/jetbrains-gateway/coder"
|
||||
version = "1.2.4"
|
||||
version = "1.2.5"
|
||||
agent_id = coder_agent.example.id
|
||||
folder = "/home/coder/example"
|
||||
jetbrains_ides = ["GO", "WS"]
|
||||
|
||||
@@ -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.3"
|
||||
version = "1.2.4"
|
||||
agent_id = coder_agent.example.id
|
||||
desktop_environment = "xfce"
|
||||
subdomain = true
|
||||
|
||||
@@ -60,6 +60,9 @@ install_deb() {
|
||||
sudo apt-get -o DPkg::Lock::Timeout=300 -qq update
|
||||
fi
|
||||
|
||||
echo "Installing required Perl DateTime module..."
|
||||
DEBIAN_FRONTEND=noninteractive sudo apt-get -o DPkg::Lock::Timeout=300 install --yes -qq --no-install-recommends --no-install-suggests libdatetime-perl
|
||||
|
||||
DEBIAN_FRONTEND=noninteractive sudo apt-get -o DPkg::Lock::Timeout=300 install --yes -qq --no-install-recommends --no-install-suggests "$kasmdeb"
|
||||
rm "$kasmdeb"
|
||||
}
|
||||
@@ -233,19 +236,17 @@ get_http_dir() {
|
||||
|
||||
# Check the system configuration path
|
||||
if [[ -e /etc/kasmvnc/kasmvnc.yaml ]]; then
|
||||
d=($(grep -E "^\s*httpd_directory:.*$" /etc/kasmvnc/kasmvnc.yaml))
|
||||
# If this grep is successful, it will return:
|
||||
# httpd_directory: /usr/share/kasmvnc/www
|
||||
if [[ $${#d[@]} -eq 2 && -d "$${d[1]}" ]]; then
|
||||
httpd_directory="$${d[1]}"
|
||||
d=$(grep -E '^\s*httpd_directory:.*$' "/etc/kasmvnc/kasmvnc.yaml" | awk '{print $$2}')
|
||||
if [[ -n "$d" && -d "$d" ]]; then
|
||||
httpd_directory=$d
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check the home directory for overriding values
|
||||
if [[ -e "$HOME/.vnc/kasmvnc.yaml" ]]; then
|
||||
d=($(grep -E "^\s*httpd_directory:.*$" "$HOME/.vnc/kasmvnc.yaml"))
|
||||
if [[ $${#d[@]} -eq 2 && -d "$${d[1]}" ]]; then
|
||||
httpd_directory="$${d[1]}"
|
||||
d=$(grep -E '^\s*httpd_directory:.*$' "$HOME/.vnc/kasmvnc.yaml" | awk '{print $$2}')
|
||||
if [[ -n "$d" && -d "$d" ]]; then
|
||||
httpd_directory=$d
|
||||
fi
|
||||
fi
|
||||
echo $httpd_directory
|
||||
|
||||
@@ -426,15 +426,14 @@ module "code-server" {
|
||||
# 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
|
||||
agent_name = "main"
|
||||
order = 1
|
||||
agent_id = coder_agent.main.id
|
||||
order = 1
|
||||
}
|
||||
|
||||
# See https://registry.coder.com/modules/coder/jetbrains
|
||||
module "jetbrains" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/coder/jetbrains/coder"
|
||||
source = "registry.coder.com/coder/jetbrains/coder"
|
||||
version = "~> 1.0"
|
||||
agent_id = coder_agent.main.id
|
||||
agent_name = "main"
|
||||
|
||||
@@ -19,3 +19,5 @@ participating in LFX CNCF programs, and helping the developer community grow.
|
||||
## Modules
|
||||
|
||||
- **aws-ami-snapshot**: Create and manage AMI snapshots for Coder workspaces with restore capabilities
|
||||
- [nexus-repository](./modules/nexus-repository/) - Configure package managers to use Sonatype Nexus Repository
|
||||
- [auto-start-dev-server](modules/auto-start-dev-server/README.md) - Automatically detect and start development servers for various project types
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
---
|
||||
display_name: Auto-Start Dev Servers
|
||||
description: Automatically detect and start development servers for various project types
|
||||
icon: ../../../../.icons/auto-dev-server.svg
|
||||
verified: false
|
||||
tags: [development, automation, servers]
|
||||
---
|
||||
|
||||
# Auto-Start Development Servers
|
||||
|
||||
Automatically detect and start development servers for various project types when a workspace starts. This module scans your workspace for common project structures and starts the appropriate development servers in the background without manual intervention.
|
||||
|
||||
```tf
|
||||
module "auto_start_dev_servers" {
|
||||
source = "registry.coder.com/mavrickrishi/auto-start-dev-server/coder"
|
||||
version = "1.0.1"
|
||||
agent_id = coder_agent.main.id
|
||||
}
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
- **Multi-language support**: Detects and starts servers for Node.js, Python (Django/Flask), Ruby (Rails), Java (Spring Boot), Go, PHP, Rust, and .NET projects
|
||||
- **Smart script prioritization**: Prioritizes `dev` scripts over `start` scripts for better development experience
|
||||
- **Intelligent frontend detection**: Automatically identifies frontend projects (React, Vue, Angular, Next.js, Nuxt, Svelte, Vite) and prioritizes them for preview apps
|
||||
- **Devcontainer integration**: Respects custom start commands defined in `.devcontainer/devcontainer.json`
|
||||
- **Configurable scanning**: Adjustable directory scan depth and project type toggles
|
||||
- **Non-blocking startup**: Servers start in the background with configurable startup delay
|
||||
- **Comprehensive logging**: All server output and detection results logged to a central file
|
||||
- **Smart detection**: Uses project-specific files and configurations to identify project types
|
||||
- **Integrated live preview**: Automatically creates a preview app for the primary frontend project
|
||||
|
||||
## Supported Project Types
|
||||
|
||||
| Framework/Language | Detection Files | Start Commands (in priority order) |
|
||||
| ------------------ | -------------------------------------------- | ----------------------------------------------------- |
|
||||
| **Node.js/npm** | `package.json` | `npm run dev`, `npm run serve`, `npm start` (or yarn) |
|
||||
| **Ruby on Rails** | `Gemfile` with rails gem | `bundle exec rails server` |
|
||||
| **Django** | `manage.py` | `python manage.py runserver` |
|
||||
| **Flask** | `requirements.txt` with Flask | `python app.py/main.py/run.py` |
|
||||
| **Spring Boot** | `pom.xml` or `build.gradle` with spring-boot | `mvn spring-boot:run`, `gradle bootRun` |
|
||||
| **Go** | `go.mod` | `go run main.go` |
|
||||
| **PHP** | `composer.json` | `php -S 0.0.0.0:8080` |
|
||||
| **Rust** | `Cargo.toml` | `cargo run` |
|
||||
| **.NET** | `*.csproj` | `dotnet run` |
|
||||
|
||||
## Examples
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```tf
|
||||
module "auto_start" {
|
||||
source = "./modules/auto-start-dev-server"
|
||||
version = "1.0.1"
|
||||
agent_id = coder_agent.main.id
|
||||
}
|
||||
```
|
||||
|
||||
### Advanced Usage
|
||||
|
||||
```tf
|
||||
module "auto_start_dev_servers" {
|
||||
source = "./modules/auto-start-dev-server"
|
||||
version = "1.0.1"
|
||||
agent_id = coder_agent.main.id
|
||||
|
||||
# Optional: Configure which project types to detect
|
||||
enable_npm = true
|
||||
enable_rails = true
|
||||
enable_django = true
|
||||
enable_flask = true
|
||||
enable_spring_boot = true
|
||||
enable_go = true
|
||||
enable_php = true
|
||||
enable_rust = true
|
||||
enable_dotnet = true
|
||||
|
||||
# Optional: Enable devcontainer.json integration
|
||||
enable_devcontainer = true
|
||||
|
||||
# Optional: Workspace directory to scan (supports environment variables)
|
||||
workspace_directory = "$HOME"
|
||||
|
||||
# Optional: Directory scan depth (1-5)
|
||||
scan_depth = 2
|
||||
|
||||
# Optional: Startup delay in seconds
|
||||
startup_delay = 10
|
||||
|
||||
# Optional: Log file path
|
||||
log_path = "/tmp/dev-servers.log"
|
||||
|
||||
# Optional: Enable automatic preview app (default: true)
|
||||
enable_preview_app = true
|
||||
}
|
||||
```
|
||||
|
||||
### Disable Preview App
|
||||
|
||||
```tf
|
||||
module "auto_start" {
|
||||
source = "./modules/auto-start-dev-server"
|
||||
version = "1.0.1"
|
||||
agent_id = coder_agent.main.id
|
||||
|
||||
# Disable automatic preview app creation
|
||||
enable_preview_app = false
|
||||
}
|
||||
```
|
||||
|
||||
### Selective Project Types
|
||||
|
||||
```tf
|
||||
module "auto_start" {
|
||||
source = "./modules/auto-start-dev-server"
|
||||
version = "1.0.1"
|
||||
agent_id = coder_agent.main.id
|
||||
|
||||
# Only enable web development projects
|
||||
enable_npm = true
|
||||
enable_rails = true
|
||||
enable_django = true
|
||||
enable_flask = true
|
||||
|
||||
# Disable other project types
|
||||
enable_spring_boot = false
|
||||
enable_go = false
|
||||
enable_php = false
|
||||
enable_rust = false
|
||||
enable_dotnet = false
|
||||
}
|
||||
```
|
||||
|
||||
### Deep Workspace Scanning
|
||||
|
||||
```tf
|
||||
module "auto_start" {
|
||||
source = "./modules/auto-start-dev-server"
|
||||
version = "1.0.1"
|
||||
agent_id = coder_agent.main.id
|
||||
|
||||
workspace_directory = "/workspaces"
|
||||
scan_depth = 3
|
||||
startup_delay = 5
|
||||
log_path = "/var/log/dev-servers.log"
|
||||
}
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
This module is provided under the same license as the Coder Registry.
|
||||
@@ -0,0 +1,109 @@
|
||||
import { describe, expect, it } from "bun:test";
|
||||
import {
|
||||
runTerraformApply,
|
||||
runTerraformInit,
|
||||
testRequiredVariables,
|
||||
} from "~test";
|
||||
|
||||
describe("auto-start-dev-server", async () => {
|
||||
await runTerraformInit(import.meta.dir);
|
||||
|
||||
testRequiredVariables(import.meta.dir, {
|
||||
agent_id: "test-agent-123",
|
||||
});
|
||||
|
||||
it("validates scan_depth range", () => {
|
||||
const t1 = async () => {
|
||||
await runTerraformApply(import.meta.dir, {
|
||||
agent_id: "test-agent-123",
|
||||
scan_depth: "0",
|
||||
});
|
||||
};
|
||||
expect(t1).toThrow("Scan depth must be between 1 and 5");
|
||||
|
||||
const t2 = async () => {
|
||||
await runTerraformApply(import.meta.dir, {
|
||||
agent_id: "test-agent-123",
|
||||
scan_depth: "6",
|
||||
});
|
||||
};
|
||||
expect(t2).toThrow("Scan depth must be between 1 and 5");
|
||||
});
|
||||
|
||||
it("applies successfully with default values", async () => {
|
||||
await runTerraformApply(import.meta.dir, {
|
||||
agent_id: "test-agent-123",
|
||||
});
|
||||
});
|
||||
|
||||
it("applies successfully with all project types enabled", async () => {
|
||||
await runTerraformApply(import.meta.dir, {
|
||||
agent_id: "test-agent-123",
|
||||
enable_npm: "true",
|
||||
enable_rails: "true",
|
||||
enable_django: "true",
|
||||
enable_flask: "true",
|
||||
enable_spring_boot: "true",
|
||||
enable_go: "true",
|
||||
enable_php: "true",
|
||||
enable_rust: "true",
|
||||
enable_dotnet: "true",
|
||||
enable_devcontainer: "true",
|
||||
});
|
||||
});
|
||||
|
||||
it("applies successfully with all project types disabled", async () => {
|
||||
await runTerraformApply(import.meta.dir, {
|
||||
agent_id: "test-agent-123",
|
||||
enable_npm: "false",
|
||||
enable_rails: "false",
|
||||
enable_django: "false",
|
||||
enable_flask: "false",
|
||||
enable_spring_boot: "false",
|
||||
enable_go: "false",
|
||||
enable_php: "false",
|
||||
enable_rust: "false",
|
||||
enable_dotnet: "false",
|
||||
enable_devcontainer: "false",
|
||||
});
|
||||
});
|
||||
|
||||
it("applies successfully with custom configuration", async () => {
|
||||
await runTerraformApply(import.meta.dir, {
|
||||
agent_id: "test-agent-123",
|
||||
workspace_directory: "/custom/workspace",
|
||||
scan_depth: "3",
|
||||
startup_delay: "5",
|
||||
log_path: "/var/log/custom-dev-servers.log",
|
||||
display_name: "Custom Dev Server Startup",
|
||||
});
|
||||
});
|
||||
|
||||
it("validates scan_depth boundary values", async () => {
|
||||
// Test valid boundary values
|
||||
await runTerraformApply(import.meta.dir, {
|
||||
agent_id: "test-agent-123",
|
||||
scan_depth: "1",
|
||||
});
|
||||
|
||||
await runTerraformApply(import.meta.dir, {
|
||||
agent_id: "test-agent-123",
|
||||
scan_depth: "5",
|
||||
});
|
||||
});
|
||||
|
||||
it("applies with selective project type configuration", async () => {
|
||||
await runTerraformApply(import.meta.dir, {
|
||||
agent_id: "test-agent-123",
|
||||
enable_npm: "true",
|
||||
enable_django: "true",
|
||||
enable_go: "true",
|
||||
enable_rails: "false",
|
||||
enable_flask: "false",
|
||||
enable_spring_boot: "false",
|
||||
enable_php: "false",
|
||||
enable_rust: "false",
|
||||
enable_dotnet: "false",
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,195 @@
|
||||
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 "workspace_directory" {
|
||||
type = string
|
||||
description = "The directory to scan for development projects."
|
||||
default = "$HOME"
|
||||
}
|
||||
|
||||
variable "project_detection" {
|
||||
type = bool
|
||||
description = "Enable automatic project detection for all supported types. When true, all project types are detected unless individually disabled. When false, only explicitly enabled project types are detected."
|
||||
default = true
|
||||
}
|
||||
|
||||
variable "enable_npm" {
|
||||
type = bool
|
||||
description = "Enable auto-detection and startup of npm projects."
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "enable_rails" {
|
||||
type = bool
|
||||
description = "Enable auto-detection and startup of Rails projects."
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "enable_django" {
|
||||
type = bool
|
||||
description = "Enable auto-detection and startup of Django projects."
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "enable_flask" {
|
||||
type = bool
|
||||
description = "Enable auto-detection and startup of Flask projects."
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "enable_spring_boot" {
|
||||
type = bool
|
||||
description = "Enable auto-detection and startup of Spring Boot projects."
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "enable_go" {
|
||||
type = bool
|
||||
description = "Enable auto-detection and startup of Go projects."
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "enable_php" {
|
||||
type = bool
|
||||
description = "Enable auto-detection and startup of PHP projects."
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "enable_rust" {
|
||||
type = bool
|
||||
description = "Enable auto-detection and startup of Rust projects."
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "enable_dotnet" {
|
||||
type = bool
|
||||
description = "Enable auto-detection and startup of .NET projects."
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "enable_devcontainer" {
|
||||
type = bool
|
||||
description = "Enable integration with devcontainer.json configuration."
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "log_path" {
|
||||
type = string
|
||||
description = "The path to log development server output to."
|
||||
default = "/tmp/dev-servers.log"
|
||||
}
|
||||
|
||||
variable "scan_depth" {
|
||||
type = number
|
||||
description = "Maximum directory depth to scan for projects (1-5)."
|
||||
default = 2
|
||||
validation {
|
||||
condition = var.scan_depth >= 1 && var.scan_depth <= 5
|
||||
error_message = "Scan depth must be between 1 and 5."
|
||||
}
|
||||
}
|
||||
|
||||
variable "startup_delay" {
|
||||
type = number
|
||||
description = "Delay in seconds before starting dev servers (allows other setup to complete)."
|
||||
default = 10
|
||||
}
|
||||
|
||||
variable "display_name" {
|
||||
type = string
|
||||
description = "Display name for the auto-start dev server script."
|
||||
default = "Auto-Start Dev Servers"
|
||||
}
|
||||
|
||||
variable "enable_preview_app" {
|
||||
type = bool
|
||||
description = "Enable automatic creation of a preview app for the first detected project."
|
||||
default = true
|
||||
}
|
||||
|
||||
# Read the detected port from the file written by the script
|
||||
locals {
|
||||
detected_port = var.enable_preview_app ? try(tonumber(trimspace(file("/tmp/detected-port.txt"))), 3000) : 3000
|
||||
# Attempt to read project information for better preview naming
|
||||
detected_projects = try(jsondecode(file("/tmp/detected-projects.json")), [])
|
||||
preview_project = length(local.detected_projects) > 0 ? local.detected_projects[0] : null
|
||||
}
|
||||
|
||||
resource "coder_script" "auto_start_dev_server" {
|
||||
agent_id = var.agent_id
|
||||
display_name = var.display_name
|
||||
icon = "/icon/auto-dev-server.svg"
|
||||
script = templatefile("${path.module}/run.sh", {
|
||||
WORKSPACE_DIR = var.workspace_directory
|
||||
ENABLE_NPM = coalesce(var.enable_npm, var.project_detection)
|
||||
ENABLE_RAILS = coalesce(var.enable_rails, var.project_detection)
|
||||
ENABLE_DJANGO = coalesce(var.enable_django, var.project_detection)
|
||||
ENABLE_FLASK = coalesce(var.enable_flask, var.project_detection)
|
||||
ENABLE_SPRING_BOOT = coalesce(var.enable_spring_boot, var.project_detection)
|
||||
ENABLE_GO = coalesce(var.enable_go, var.project_detection)
|
||||
ENABLE_PHP = coalesce(var.enable_php, var.project_detection)
|
||||
ENABLE_RUST = coalesce(var.enable_rust, var.project_detection)
|
||||
ENABLE_DOTNET = coalesce(var.enable_dotnet, var.project_detection)
|
||||
ENABLE_DEVCONTAINER = coalesce(var.enable_devcontainer, var.project_detection)
|
||||
LOG_PATH = var.log_path
|
||||
SCAN_DEPTH = var.scan_depth
|
||||
STARTUP_DELAY = var.startup_delay
|
||||
})
|
||||
run_on_start = true
|
||||
}
|
||||
|
||||
# Create preview app for first detected project
|
||||
resource "coder_app" "preview" {
|
||||
count = var.enable_preview_app ? 1 : 0
|
||||
agent_id = var.agent_id
|
||||
slug = "dev-preview"
|
||||
display_name = "Live Preview"
|
||||
url = "http://localhost:${local.detected_port}"
|
||||
icon = "/icon/auto-dev-server.svg"
|
||||
subdomain = true
|
||||
share = "owner"
|
||||
}
|
||||
|
||||
output "log_path" {
|
||||
value = var.log_path
|
||||
description = "Path to the log file for dev server output"
|
||||
}
|
||||
|
||||
# Example output values for common port mappings
|
||||
output "common_ports" {
|
||||
value = {
|
||||
nodejs = 3000
|
||||
rails = 3000
|
||||
django = 8000
|
||||
flask = 5000
|
||||
spring = 8080
|
||||
go = 8080
|
||||
php = 8080
|
||||
rust = 8000
|
||||
dotnet = 5000
|
||||
}
|
||||
description = "Common default ports for different project types"
|
||||
}
|
||||
|
||||
output "preview_url" {
|
||||
value = var.enable_preview_app ? try(coder_app.preview[0].url, null) : null
|
||||
description = "URL of the live preview app (if enabled)"
|
||||
}
|
||||
|
||||
output "detected_port" {
|
||||
value = local.detected_port
|
||||
description = "Port of the first detected development server"
|
||||
}
|
||||
+468
@@ -0,0 +1,468 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Color codes for output
|
||||
BOLD='\033[0;1m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[0;33m'
|
||||
BLUE='\033[0;34m'
|
||||
RED='\033[0;31m'
|
||||
RESET='\033[0m'
|
||||
|
||||
echo -e "$${BOLD}🚀 Auto-Start Development Servers$${RESET}"
|
||||
echo "Workspace Directory: ${WORKSPACE_DIR}"
|
||||
echo "Log Path: ${LOG_PATH}"
|
||||
echo "Scan Depth: ${SCAN_DEPTH}"
|
||||
|
||||
# Wait for startup delay to allow other setup to complete
|
||||
if [ "${STARTUP_DELAY}" -gt 0 ]; then
|
||||
echo -e "$${YELLOW}⏳ Waiting ${STARTUP_DELAY} seconds for system initialization...$${RESET}"
|
||||
sleep "${STARTUP_DELAY}"
|
||||
fi
|
||||
|
||||
# Initialize log file
|
||||
echo "=== Auto-Start Dev Servers Log ===" > "${LOG_PATH}"
|
||||
echo "Started at: $(date)" >> "${LOG_PATH}"
|
||||
|
||||
# Initialize detected projects JSON file
|
||||
DETECTED_PROJECTS_FILE="/tmp/detected-projects.json"
|
||||
echo '[]' > "$DETECTED_PROJECTS_FILE"
|
||||
|
||||
# Initialize detected port file for preview app
|
||||
DETECTED_PORT_FILE="/tmp/detected-port.txt"
|
||||
FIRST_PORT_DETECTED=false
|
||||
FRONTEND_PROJECT_DETECTED=false
|
||||
|
||||
# Function to log messages
|
||||
log_message() {
|
||||
echo -e "$1"
|
||||
echo "$1" >> "${LOG_PATH}"
|
||||
}
|
||||
|
||||
# Function to determine if a project is likely a frontend project
|
||||
is_frontend_project() {
|
||||
local project_dir="$1"
|
||||
local project_type="$2"
|
||||
|
||||
# Check for common frontend indicators
|
||||
if [ "$project_type" = "nodejs" ]; then
|
||||
# Check package.json for frontend dependencies
|
||||
if [ -f "$project_dir/package.json" ] && command -v jq &> /dev/null; then
|
||||
# Check for common frontend frameworks
|
||||
local has_react=$(jq '.dependencies.react // .devDependencies.react // empty' "$project_dir/package.json")
|
||||
local has_vue=$(jq '.dependencies.vue // .devDependencies.vue // empty' "$project_dir/package.json")
|
||||
local has_angular=$(jq '.dependencies["@angular/core"] // .devDependencies["@angular/core"] // empty' "$project_dir/package.json")
|
||||
local has_next=$(jq '.dependencies.next // .devDependencies.next // empty' "$project_dir/package.json")
|
||||
local has_nuxt=$(jq '.dependencies.nuxt // .devDependencies.nuxt // empty' "$project_dir/package.json")
|
||||
local has_svelte=$(jq '.dependencies.svelte // .devDependencies.svelte // empty' "$project_dir/package.json")
|
||||
local has_vite=$(jq '.dependencies.vite // .devDependencies.vite // empty' "$project_dir/package.json")
|
||||
|
||||
if [ -n "$has_react" ] || [ -n "$has_vue" ] || [ -n "$has_angular" ] \
|
||||
|| [ -n "$has_next" ] || [ -n "$has_nuxt" ] || [ -n "$has_svelte" ] \
|
||||
|| [ -n "$has_vite" ]; then
|
||||
return 0 # It's a frontend project
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check for common frontend directory structures
|
||||
if [ -d "$project_dir/src/components" ] || [ -d "$project_dir/components" ] \
|
||||
|| [ -d "$project_dir/pages" ] || [ -d "$project_dir/views" ] \
|
||||
|| [ -f "$project_dir/index.html" ] || [ -f "$project_dir/public/index.html" ]; then
|
||||
return 0 # It's likely a frontend project
|
||||
fi
|
||||
fi
|
||||
|
||||
# Rails projects with webpack/webpacker are frontend-enabled
|
||||
if [ "$project_type" = "rails" ]; then
|
||||
if [ -f "$project_dir/config/webpacker.yml" ] || [ -f "$project_dir/webpack.config.js" ]; then
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# Django projects with static/templates are frontend-enabled
|
||||
if [ "$project_type" = "django" ]; then
|
||||
if [ -d "$project_dir/static" ] || [ -d "$project_dir/templates" ]; then
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
return 1 # Not a frontend project
|
||||
}
|
||||
|
||||
# Function to add detected project to JSON
|
||||
add_detected_project() {
|
||||
local project_dir="$1"
|
||||
local project_type="$2"
|
||||
local port="$3"
|
||||
local command="$4"
|
||||
|
||||
# Check if this is a frontend project
|
||||
local is_frontend=false
|
||||
if is_frontend_project "$project_dir" "$project_type"; then
|
||||
is_frontend=true
|
||||
log_message "$${BLUE}🎨 Detected frontend project at $project_dir$${RESET}"
|
||||
fi
|
||||
|
||||
# Prioritize frontend projects for the preview app
|
||||
# Set port if: 1) No port set yet, OR 2) This is frontend and no frontend detected yet
|
||||
if [ "$FIRST_PORT_DETECTED" = false ] || ([ "$is_frontend" = true ] && [ "$FRONTEND_PROJECT_DETECTED" = false ]); then
|
||||
echo "$port" > "$DETECTED_PORT_FILE"
|
||||
FIRST_PORT_DETECTED=true
|
||||
if [ "$is_frontend" = true ]; then
|
||||
FRONTEND_PROJECT_DETECTED=true
|
||||
log_message "$${BLUE}🎯 Frontend project detected - Preview app will be available on port $port$${RESET}"
|
||||
else
|
||||
log_message "$${BLUE}🎯 Project detected - Preview app will be available on port $port$${RESET}"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Create JSON entry for this project
|
||||
local project_json=$(jq -n \
|
||||
--arg dir "$project_dir" \
|
||||
--arg type "$project_type" \
|
||||
--arg port "$port" \
|
||||
--arg cmd "$command" \
|
||||
--arg frontend "$is_frontend" \
|
||||
'{"directory": $dir, "type": $type, "port": $port, "command": $cmd, "is_frontend": ($frontend == "true")}')
|
||||
|
||||
# Append to the detected projects file
|
||||
jq ". += [$project_json]" "$DETECTED_PROJECTS_FILE" > "$DETECTED_PROJECTS_FILE.tmp" \
|
||||
&& mv "$DETECTED_PROJECTS_FILE.tmp" "$DETECTED_PROJECTS_FILE"
|
||||
}
|
||||
|
||||
# Function to detect and start npm/yarn projects
|
||||
detect_npm_projects() {
|
||||
if [ "${ENABLE_NPM}" != "true" ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
log_message "$${BLUE}🔍 Scanning for Node.js/npm projects...$${RESET}"
|
||||
|
||||
# Use find with maxdepth to respect scan depth
|
||||
while IFS= read -r -d '' package_json; do
|
||||
project_dir=$(dirname "$package_json")
|
||||
log_message "$${GREEN}📦 Found Node.js project: $project_dir$${RESET}"
|
||||
|
||||
cd "$project_dir"
|
||||
|
||||
# Check package.json for start script
|
||||
if [ -f "package.json" ] && command -v jq &> /dev/null; then
|
||||
start_script=$(jq -r '.scripts.start // empty' package.json)
|
||||
dev_script=$(jq -r '.scripts.dev // empty' package.json)
|
||||
serve_script=$(jq -r '.scripts.serve // empty' package.json)
|
||||
|
||||
# Determine port (check for common port configurations)
|
||||
local project_port=3000
|
||||
if [ -n "$dev_script" ] && echo "$dev_script" | grep -q "\-\-port"; then
|
||||
project_port=$(echo "$dev_script" | grep -oE "\-\-port[[:space:]]+[0-9]+" | grep -oE "[0-9]+$" || echo "3000")
|
||||
fi
|
||||
|
||||
# Use yarn if yarn.lock exists
|
||||
local pkg_manager="npm"
|
||||
local cmd_prefix=""
|
||||
if [ -f "yarn.lock" ] && command -v yarn &> /dev/null; then
|
||||
pkg_manager="yarn"
|
||||
cmd_prefix=""
|
||||
else
|
||||
cmd_prefix="run "
|
||||
fi
|
||||
|
||||
# Prioritize scripts: 'dev' > 'serve' > 'start' for development environments
|
||||
if [ -n "$dev_script" ]; then
|
||||
if [ "$pkg_manager" = "yarn" ]; then
|
||||
log_message "$${GREEN}🟢 Starting project with 'yarn dev' in $project_dir$${RESET}"
|
||||
nohup yarn dev >> "${LOG_PATH}" 2>&1 &
|
||||
add_detected_project "$project_dir" "nodejs" "$project_port" "yarn dev"
|
||||
else
|
||||
log_message "$${GREEN}🟢 Starting project with 'npm run dev' in $project_dir$${RESET}"
|
||||
nohup npm run dev >> "${LOG_PATH}" 2>&1 &
|
||||
add_detected_project "$project_dir" "nodejs" "$project_port" "npm run dev"
|
||||
fi
|
||||
elif [ -n "$serve_script" ]; then
|
||||
if [ "$pkg_manager" = "yarn" ]; then
|
||||
log_message "$${GREEN}🟢 Starting project with 'yarn serve' in $project_dir$${RESET}"
|
||||
nohup yarn serve >> "${LOG_PATH}" 2>&1 &
|
||||
add_detected_project "$project_dir" "nodejs" "$project_port" "yarn serve"
|
||||
else
|
||||
log_message "$${GREEN}🟢 Starting project with 'npm run serve' in $project_dir$${RESET}"
|
||||
nohup npm run serve >> "${LOG_PATH}" 2>&1 &
|
||||
add_detected_project "$project_dir" "nodejs" "$project_port" "npm run serve"
|
||||
fi
|
||||
elif [ -n "$start_script" ]; then
|
||||
if [ "$pkg_manager" = "yarn" ]; then
|
||||
log_message "$${GREEN}🟢 Starting project with 'yarn start' in $project_dir$${RESET}"
|
||||
nohup yarn start >> "${LOG_PATH}" 2>&1 &
|
||||
add_detected_project "$project_dir" "nodejs" "$project_port" "yarn start"
|
||||
else
|
||||
log_message "$${GREEN}🟢 Starting project with 'npm start' in $project_dir$${RESET}"
|
||||
nohup npm start >> "${LOG_PATH}" 2>&1 &
|
||||
add_detected_project "$project_dir" "nodejs" "$project_port" "npm start"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
done < <(find "${WORKSPACE_DIR}" -maxdepth "${SCAN_DEPTH}" -name "package.json" -type f -print0)
|
||||
}
|
||||
|
||||
# Function to detect and start Rails projects
|
||||
detect_rails_projects() {
|
||||
if [ "${ENABLE_RAILS}" != "true" ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
log_message "$${BLUE}🔍 Scanning for Ruby on Rails projects...$${RESET}"
|
||||
|
||||
while IFS= read -r -d '' gemfile; do
|
||||
project_dir=$(dirname "$gemfile")
|
||||
log_message "$${GREEN}💎 Found Rails project: $project_dir$${RESET}"
|
||||
|
||||
cd "$project_dir"
|
||||
|
||||
# Check if it's actually a Rails project
|
||||
if grep -q "gem ['\"]rails['\"]" Gemfile 2> /dev/null; then
|
||||
log_message "$${GREEN}🟢 Starting Rails server in $project_dir$${RESET}"
|
||||
nohup bundle exec rails server >> "${LOG_PATH}" 2>&1 &
|
||||
add_detected_project "$project_dir" "rails" "3000" "bundle exec rails server"
|
||||
fi
|
||||
|
||||
done < <(find "${WORKSPACE_DIR}" -maxdepth "${SCAN_DEPTH}" -name "Gemfile" -type f -print0)
|
||||
}
|
||||
|
||||
# Function to detect and start Django projects
|
||||
detect_django_projects() {
|
||||
if [ "${ENABLE_DJANGO}" != "true" ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
log_message "$${BLUE}🔍 Scanning for Django projects...$${RESET}"
|
||||
|
||||
while IFS= read -r -d '' manage_py; do
|
||||
project_dir=$(dirname "$manage_py")
|
||||
log_message "$${GREEN}🐍 Found Django project: $project_dir$${RESET}"
|
||||
|
||||
cd "$project_dir"
|
||||
log_message "$${GREEN}🟢 Starting Django development server in $project_dir$${RESET}"
|
||||
nohup python manage.py runserver 0.0.0.0:8000 >> "${LOG_PATH}" 2>&1 &
|
||||
add_detected_project "$project_dir" "django" "8000" "python manage.py runserver"
|
||||
|
||||
done < <(find "${WORKSPACE_DIR}" -maxdepth "${SCAN_DEPTH}" -name "manage.py" -type f -print0)
|
||||
}
|
||||
|
||||
# Function to detect and start Flask projects
|
||||
detect_flask_projects() {
|
||||
if [ "${ENABLE_FLASK}" != "true" ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
log_message "$${BLUE}🔍 Scanning for Flask projects...$${RESET}"
|
||||
|
||||
while IFS= read -r -d '' requirements_txt; do
|
||||
project_dir=$(dirname "$requirements_txt")
|
||||
|
||||
# Check if Flask is in requirements
|
||||
if grep -q -i "flask" "$requirements_txt" 2> /dev/null; then
|
||||
log_message "$${GREEN}🌶️ Found Flask project: $project_dir$${RESET}"
|
||||
|
||||
cd "$project_dir"
|
||||
|
||||
# Look for common Flask app files
|
||||
for app_file in app.py main.py run.py; do
|
||||
if [ -f "$app_file" ]; then
|
||||
log_message "$${GREEN}🟢 Starting Flask application ($app_file) in $project_dir$${RESET}"
|
||||
export FLASK_ENV=development
|
||||
nohup python "$app_file" >> "${LOG_PATH}" 2>&1 &
|
||||
add_detected_project "$project_dir" "flask" "5000" "python $app_file"
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
done < <(find "${WORKSPACE_DIR}" -maxdepth "${SCAN_DEPTH}" -name "requirements.txt" -type f -print0)
|
||||
}
|
||||
|
||||
# Function to detect and start Spring Boot projects
|
||||
detect_spring_boot_projects() {
|
||||
if [ "${ENABLE_SPRING_BOOT}" != "true" ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
log_message "$${BLUE}🔍 Scanning for Spring Boot projects...$${RESET}"
|
||||
|
||||
# Maven projects
|
||||
while IFS= read -r -d '' pom_xml; do
|
||||
project_dir=$(dirname "$pom_xml")
|
||||
|
||||
# Check if it's a Spring Boot project
|
||||
if grep -q "spring-boot" "$pom_xml" 2> /dev/null; then
|
||||
log_message "$${GREEN}🍃 Found Spring Boot Maven project: $project_dir$${RESET}"
|
||||
|
||||
cd "$project_dir"
|
||||
if command -v ./mvnw &> /dev/null; then
|
||||
log_message "$${GREEN}🟢 Starting Spring Boot application with Maven wrapper in $project_dir$${RESET}"
|
||||
nohup ./mvnw spring-boot:run >> "${LOG_PATH}" 2>&1 &
|
||||
add_detected_project "$project_dir" "spring-boot" "8080" "./mvnw spring-boot:run"
|
||||
elif command -v mvn &> /dev/null; then
|
||||
log_message "$${GREEN}🟢 Starting Spring Boot application with Maven in $project_dir$${RESET}"
|
||||
nohup mvn spring-boot:run >> "${LOG_PATH}" 2>&1 &
|
||||
add_detected_project "$project_dir" "spring-boot" "8080" "mvn spring-boot:run"
|
||||
fi
|
||||
fi
|
||||
|
||||
done < <(find "${WORKSPACE_DIR}" -maxdepth "${SCAN_DEPTH}" -name "pom.xml" -type f -print0)
|
||||
|
||||
# Gradle projects
|
||||
while IFS= read -r -d '' build_gradle; do
|
||||
project_dir=$(dirname "$build_gradle")
|
||||
|
||||
# Check if it's a Spring Boot project
|
||||
if grep -q "spring-boot" "$build_gradle" 2> /dev/null; then
|
||||
log_message "$${GREEN}🍃 Found Spring Boot Gradle project: $project_dir$${RESET}"
|
||||
|
||||
cd "$project_dir"
|
||||
if command -v ./gradlew &> /dev/null; then
|
||||
log_message "$${GREEN}🟢 Starting Spring Boot application with Gradle wrapper in $project_dir$${RESET}"
|
||||
nohup ./gradlew bootRun >> "${LOG_PATH}" 2>&1 &
|
||||
add_detected_project "$project_dir" "spring-boot" "8080" "./gradlew bootRun"
|
||||
elif command -v gradle &> /dev/null; then
|
||||
log_message "$${GREEN}🟢 Starting Spring Boot application with Gradle in $project_dir$${RESET}"
|
||||
nohup gradle bootRun >> "${LOG_PATH}" 2>&1 &
|
||||
add_detected_project "$project_dir" "spring-boot" "8080" "gradle bootRun"
|
||||
fi
|
||||
fi
|
||||
|
||||
done < <(find "${WORKSPACE_DIR}" -maxdepth "${SCAN_DEPTH}" -name "build.gradle" -type f -print0)
|
||||
}
|
||||
|
||||
# Function to detect and start Go projects
|
||||
detect_go_projects() {
|
||||
if [ "${ENABLE_GO}" != "true" ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
log_message "$${BLUE}🔍 Scanning for Go projects...$${RESET}"
|
||||
|
||||
while IFS= read -r -d '' go_mod; do
|
||||
project_dir=$(dirname "$go_mod")
|
||||
log_message "$${GREEN}🐹 Found Go project: $project_dir$${RESET}"
|
||||
|
||||
cd "$project_dir"
|
||||
|
||||
# Look for main.go or check if there's a main function
|
||||
if [ -f "main.go" ]; then
|
||||
log_message "$${GREEN}🟢 Starting Go application in $project_dir$${RESET}"
|
||||
nohup go run main.go >> "${LOG_PATH}" 2>&1 &
|
||||
add_detected_project "$project_dir" "go" "8080" "go run main.go"
|
||||
elif [ -f "cmd/main.go" ]; then
|
||||
log_message "$${GREEN}🟢 Starting Go application (cmd/main.go) in $project_dir$${RESET}"
|
||||
nohup go run cmd/main.go >> "${LOG_PATH}" 2>&1 &
|
||||
add_detected_project "$project_dir" "go" "8080" "go run cmd/main.go"
|
||||
fi
|
||||
|
||||
done < <(find "${WORKSPACE_DIR}" -maxdepth "${SCAN_DEPTH}" -name "go.mod" -type f -print0)
|
||||
}
|
||||
|
||||
# Function to detect and start PHP projects
|
||||
detect_php_projects() {
|
||||
if [ "${ENABLE_PHP}" != "true" ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
log_message "$${BLUE}🔍 Scanning for PHP projects...$${RESET}"
|
||||
|
||||
while IFS= read -r -d '' composer_json; do
|
||||
project_dir=$(dirname "$composer_json")
|
||||
log_message "$${GREEN}🐘 Found PHP project: $project_dir$${RESET}"
|
||||
|
||||
cd "$project_dir"
|
||||
|
||||
# Look for common PHP entry points
|
||||
for entry_file in index.php public/index.php; do
|
||||
if [ -f "$entry_file" ]; then
|
||||
log_message "$${GREEN}🟢 Starting PHP development server in $project_dir$${RESET}"
|
||||
nohup php -S 0.0.0.0:8080 -t "$(dirname "$entry_file")" >> "${LOG_PATH}" 2>&1 &
|
||||
add_detected_project "$project_dir" "php" "8080" "php -S 0.0.0.0:8080"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
done < <(find "${WORKSPACE_DIR}" -maxdepth "${SCAN_DEPTH}" -name "composer.json" -type f -print0)
|
||||
}
|
||||
|
||||
# Function to detect and start Rust projects
|
||||
detect_rust_projects() {
|
||||
if [ "${ENABLE_RUST}" != "true" ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
log_message "$${BLUE}🔍 Scanning for Rust projects...$${RESET}"
|
||||
|
||||
while IFS= read -r -d '' cargo_toml; do
|
||||
project_dir=$(dirname "$cargo_toml")
|
||||
log_message "$${GREEN}🦀 Found Rust project: $project_dir$${RESET}"
|
||||
|
||||
cd "$project_dir"
|
||||
|
||||
# Check if it's a binary project (has [[bin]] or default main.rs)
|
||||
if grep -q "\[\[bin\]\]" Cargo.toml 2> /dev/null || [ -f "src/main.rs" ]; then
|
||||
log_message "$${GREEN}🟢 Starting Rust application in $project_dir$${RESET}"
|
||||
nohup cargo run >> "${LOG_PATH}" 2>&1 &
|
||||
add_detected_project "$project_dir" "rust" "8000" "cargo run"
|
||||
fi
|
||||
|
||||
done < <(find "${WORKSPACE_DIR}" -maxdepth "${SCAN_DEPTH}" -name "Cargo.toml" -type f -print0)
|
||||
}
|
||||
|
||||
# Function to detect and start .NET projects
|
||||
detect_dotnet_projects() {
|
||||
if [ "${ENABLE_DOTNET}" != "true" ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
log_message "$${BLUE}🔍 Scanning for .NET projects...$${RESET}"
|
||||
|
||||
while IFS= read -r -d '' csproj; do
|
||||
project_dir=$(dirname "$csproj")
|
||||
log_message "$${GREEN}🔷 Found .NET project: $project_dir$${RESET}"
|
||||
|
||||
cd "$project_dir"
|
||||
log_message "$${GREEN}🟢 Starting .NET application in $project_dir$${RESET}"
|
||||
nohup dotnet run >> "${LOG_PATH}" 2>&1 &
|
||||
add_detected_project "$project_dir" "dotnet" "5000" "dotnet run"
|
||||
|
||||
done < <(find "${WORKSPACE_DIR}" -maxdepth "${SCAN_DEPTH}" -name "*.csproj" -type f -print0)
|
||||
}
|
||||
|
||||
log_message "Starting auto-detection of development projects..."
|
||||
|
||||
# Expand workspace directory if it contains variables
|
||||
WORKSPACE_DIR=$(eval echo "${WORKSPACE_DIR}")
|
||||
|
||||
# Check if workspace directory exists
|
||||
if [ ! -d "$WORKSPACE_DIR" ]; then
|
||||
log_message "$${RED}❌ Workspace directory does not exist: $WORKSPACE_DIR$${RESET}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cd "$WORKSPACE_DIR"
|
||||
|
||||
# Run all detection functions
|
||||
detect_npm_projects
|
||||
detect_rails_projects
|
||||
detect_django_projects
|
||||
detect_flask_projects
|
||||
detect_spring_boot_projects
|
||||
detect_go_projects
|
||||
detect_php_projects
|
||||
detect_rust_projects
|
||||
detect_dotnet_projects
|
||||
|
||||
log_message "$${GREEN}✅ Auto-start scan completed!$${RESET}"
|
||||
log_message "$${YELLOW}💡 Check running processes with 'ps aux | grep -E \"(npm|rails|python|java|go|php|cargo|dotnet)\"'$${RESET}"
|
||||
log_message "$${YELLOW}💡 View logs: tail -f ${LOG_PATH}$${RESET}"
|
||||
|
||||
# Set default port if no projects were detected
|
||||
if [ "$FIRST_PORT_DETECTED" = false ]; then
|
||||
echo "3000" > "$DETECTED_PORT_FILE"
|
||||
log_message "$${YELLOW}⚠️ No projects detected - Preview app will default to port 3000$${RESET}"
|
||||
fi
|
||||
@@ -0,0 +1,149 @@
|
||||
---
|
||||
display_name: Nexus Repository
|
||||
description: Configure package managers to use Sonatype Nexus Repository for Maven, npm, PyPI, and Docker registries.
|
||||
icon: ../../../../.icons/nexus-repository.svg
|
||||
verified: false
|
||||
tags: [integration, nexus-repository, maven, npm, pypi, docker]
|
||||
---
|
||||
|
||||
# Sonatype Nexus Repository
|
||||
|
||||
Configure package managers (Maven, npm, Go, PyPI, Docker) to use [Sonatype Nexus Repository](https://help.sonatype.com/en/sonatype-nexus-repository.html) with API token authentication. This module provides secure credential handling, multiple repository support per package manager, and flexible username configuration.
|
||||
|
||||
```tf
|
||||
module "nexus_repository" {
|
||||
source = "registry.coder.com/mavrickrishi/nexus-repository/coder"
|
||||
version = "1.0.1"
|
||||
agent_id = coder_agent.example.id
|
||||
nexus_url = "https://nexus.example.com"
|
||||
nexus_password = var.nexus_api_token
|
||||
package_managers = {
|
||||
maven = ["maven-public", "maven-releases"]
|
||||
npm = ["npm-public", "@scoped:npm-private"]
|
||||
go = ["go-public", "go-private"]
|
||||
pypi = ["pypi-public", "pypi-private"]
|
||||
docker = ["docker-public", "docker-private"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
- Nexus Repository Manager 3.x
|
||||
- Valid API token or user credentials
|
||||
- Package managers installed on the workspace (Maven, npm, Go, pip, Docker as needed)
|
||||
|
||||
> [!NOTE]
|
||||
> This module configures package managers but does not install them. You need to handle the installation of Maven, npm, Go, Python pip, and Docker yourself.
|
||||
|
||||
## Examples
|
||||
|
||||
### Configure Maven to use Nexus repositories
|
||||
|
||||
```tf
|
||||
module "nexus_repository" {
|
||||
source = "registry.coder.com/mavrickrishi/nexus-repository/coder"
|
||||
version = "1.0.1"
|
||||
agent_id = coder_agent.example.id
|
||||
nexus_url = "https://nexus.example.com"
|
||||
nexus_password = var.nexus_api_token
|
||||
package_managers = {
|
||||
maven = ["maven-public", "maven-releases", "maven-snapshots"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Configure npm with scoped packages
|
||||
|
||||
```tf
|
||||
module "nexus_repository" {
|
||||
source = "registry.coder.com/mavrickrishi/nexus-repository/coder"
|
||||
version = "1.0.1"
|
||||
agent_id = coder_agent.example.id
|
||||
nexus_url = "https://nexus.example.com"
|
||||
nexus_password = var.nexus_api_token
|
||||
package_managers = {
|
||||
npm = ["npm-public", "@mycompany:npm-private"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Configure Go module proxy
|
||||
|
||||
```tf
|
||||
module "nexus_repository" {
|
||||
source = "registry.coder.com/mavrickrishi/nexus-repository/coder"
|
||||
version = "1.0.1"
|
||||
agent_id = coder_agent.example.id
|
||||
nexus_url = "https://nexus.example.com"
|
||||
nexus_password = var.nexus_api_token
|
||||
package_managers = {
|
||||
go = ["go-public", "go-private"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Configure Python PyPI repositories
|
||||
|
||||
```tf
|
||||
module "nexus_repository" {
|
||||
source = "registry.coder.com/mavrickrishi/nexus-repository/coder"
|
||||
version = "1.0.1"
|
||||
agent_id = coder_agent.example.id
|
||||
nexus_url = "https://nexus.example.com"
|
||||
nexus_password = var.nexus_api_token
|
||||
package_managers = {
|
||||
pypi = ["pypi-public", "pypi-private"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Configure Docker registries
|
||||
|
||||
```tf
|
||||
module "nexus_repository" {
|
||||
source = "registry.coder.com/mavrickrishi/nexus-repository/coder"
|
||||
version = "1.0.1"
|
||||
agent_id = coder_agent.example.id
|
||||
nexus_url = "https://nexus.example.com"
|
||||
nexus_password = var.nexus_api_token
|
||||
package_managers = {
|
||||
docker = ["docker-public", "docker-private"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Use custom username
|
||||
|
||||
```tf
|
||||
module "nexus_repository" {
|
||||
source = "registry.coder.com/mavrickrishi/nexus-repository/coder"
|
||||
version = "1.0.1"
|
||||
agent_id = coder_agent.example.id
|
||||
nexus_url = "https://nexus.example.com"
|
||||
nexus_username = "custom-user"
|
||||
nexus_password = var.nexus_api_token
|
||||
package_managers = {
|
||||
maven = ["maven-public"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Complete configuration for all package managers
|
||||
|
||||
```tf
|
||||
module "nexus_repository" {
|
||||
source = "registry.coder.com/mavrickrishi/nexus-repository/coder"
|
||||
version = "1.0.1"
|
||||
agent_id = coder_agent.example.id
|
||||
nexus_url = "https://nexus.example.com"
|
||||
nexus_password = var.nexus_api_token
|
||||
package_managers = {
|
||||
maven = ["maven-public", "maven-releases"]
|
||||
npm = ["npm-public", "@company:npm-private"]
|
||||
go = ["go-public", "go-private"]
|
||||
pypi = ["pypi-public", "pypi-private"]
|
||||
docker = ["docker-public", "docker-private"]
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,147 @@
|
||||
import { describe, expect, it } from "bun:test";
|
||||
import {
|
||||
executeScriptInContainer,
|
||||
runTerraformApply,
|
||||
runTerraformInit,
|
||||
testRequiredVariables,
|
||||
} from "~test";
|
||||
|
||||
describe("nexus-repository", async () => {
|
||||
await runTerraformInit(import.meta.dir);
|
||||
|
||||
testRequiredVariables(import.meta.dir, {
|
||||
agent_id: "test-agent",
|
||||
nexus_url: "https://nexus.example.com",
|
||||
nexus_password: "test-password",
|
||||
});
|
||||
|
||||
it("configures Maven settings", async () => {
|
||||
const state = await runTerraformApply(import.meta.dir, {
|
||||
agent_id: "test-agent",
|
||||
nexus_url: "https://nexus.example.com",
|
||||
nexus_password: "test-token",
|
||||
package_managers: JSON.stringify({
|
||||
maven: ["maven-public"],
|
||||
}),
|
||||
});
|
||||
|
||||
const output = await executeScriptInContainer(state, "ubuntu:20.04");
|
||||
expect(output.stdout.join("\n")).toContain("☕ Configuring Maven...");
|
||||
expect(output.stdout.join("\n")).toContain("🥳 Configuration complete!");
|
||||
});
|
||||
|
||||
it("configures npm registry", async () => {
|
||||
const state = await runTerraformApply(import.meta.dir, {
|
||||
agent_id: "test-agent",
|
||||
nexus_url: "https://nexus.example.com",
|
||||
nexus_password: "test-token",
|
||||
package_managers: JSON.stringify({
|
||||
npm: ["npm-public"],
|
||||
}),
|
||||
});
|
||||
|
||||
const output = await executeScriptInContainer(state, "ubuntu:20.04");
|
||||
expect(output.stdout.join("\n")).toContain("📦 Configuring npm...");
|
||||
expect(output.stdout.join("\n")).toContain("🥳 Configuration complete!");
|
||||
});
|
||||
|
||||
it("configures PyPI repository", async () => {
|
||||
const state = await runTerraformApply(import.meta.dir, {
|
||||
agent_id: "test-agent",
|
||||
nexus_url: "https://nexus.example.com",
|
||||
nexus_password: "test-token",
|
||||
package_managers: JSON.stringify({
|
||||
pypi: ["pypi-public"],
|
||||
}),
|
||||
});
|
||||
|
||||
const output = await executeScriptInContainer(state, "ubuntu:20.04");
|
||||
expect(output.stdout.join("\n")).toContain("🐍 Configuring pip...");
|
||||
expect(output.stdout.join("\n")).toContain("🥳 Configuration complete!");
|
||||
});
|
||||
|
||||
it("configures multiple package managers", async () => {
|
||||
const state = await runTerraformApply(import.meta.dir, {
|
||||
agent_id: "test-agent",
|
||||
nexus_url: "https://nexus.example.com",
|
||||
nexus_password: "test-token",
|
||||
package_managers: JSON.stringify({
|
||||
maven: ["maven-public"],
|
||||
npm: ["npm-public"],
|
||||
pypi: ["pypi-public"],
|
||||
}),
|
||||
});
|
||||
|
||||
const output = await executeScriptInContainer(state, "ubuntu:20.04");
|
||||
expect(output.stdout.join("\n")).toContain("☕ Configuring Maven...");
|
||||
expect(output.stdout.join("\n")).toContain("📦 Configuring npm...");
|
||||
expect(output.stdout.join("\n")).toContain("🐍 Configuring pip...");
|
||||
expect(output.stdout.join("\n")).toContain(
|
||||
"✅ Nexus repository configuration completed!",
|
||||
);
|
||||
});
|
||||
|
||||
it("handles empty package managers", async () => {
|
||||
const state = await runTerraformApply(import.meta.dir, {
|
||||
agent_id: "test-agent",
|
||||
nexus_url: "https://nexus.example.com",
|
||||
nexus_password: "test-token",
|
||||
package_managers: JSON.stringify({}),
|
||||
});
|
||||
|
||||
const output = await executeScriptInContainer(state, "ubuntu:20.04");
|
||||
expect(output.stdout.join("\n")).toContain(
|
||||
"🤔 no maven repository is set, skipping maven configuration.",
|
||||
);
|
||||
expect(output.stdout.join("\n")).toContain(
|
||||
"🤔 no npm repository is set, skipping npm configuration.",
|
||||
);
|
||||
expect(output.stdout.join("\n")).toContain(
|
||||
"🤔 no pypi repository is set, skipping pypi configuration.",
|
||||
);
|
||||
expect(output.stdout.join("\n")).toContain(
|
||||
"🤔 no docker repository is set, skipping docker configuration.",
|
||||
);
|
||||
});
|
||||
|
||||
it("configures Go module proxy", async () => {
|
||||
const state = await runTerraformApply(import.meta.dir, {
|
||||
agent_id: "test-agent",
|
||||
nexus_url: "https://nexus.example.com",
|
||||
nexus_password: "test-token",
|
||||
package_managers: JSON.stringify({
|
||||
go: ["go-public", "go-private"],
|
||||
}),
|
||||
});
|
||||
|
||||
const output = await executeScriptInContainer(state, "ubuntu:20.04");
|
||||
expect(output.stdout.join("\n")).toContain("🐹 Configuring Go...");
|
||||
expect(output.stdout.join("\n")).toContain(
|
||||
"Go proxy configured via GOPROXY environment variable",
|
||||
);
|
||||
expect(output.stdout.join("\n")).toContain("🥳 Configuration complete!");
|
||||
});
|
||||
|
||||
it("validates nexus_url format", async () => {
|
||||
await expect(
|
||||
runTerraformApply(import.meta.dir, {
|
||||
agent_id: "test-agent",
|
||||
nexus_url: "invalid-url",
|
||||
nexus_password: "test-token",
|
||||
package_managers: JSON.stringify({}),
|
||||
}),
|
||||
).rejects.toThrow();
|
||||
});
|
||||
|
||||
it("validates username_field values", async () => {
|
||||
await expect(
|
||||
runTerraformApply(import.meta.dir, {
|
||||
agent_id: "test-agent",
|
||||
nexus_url: "https://nexus.example.com",
|
||||
nexus_password: "test-token",
|
||||
username_field: "invalid",
|
||||
package_managers: JSON.stringify({}),
|
||||
}),
|
||||
).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,137 @@
|
||||
terraform {
|
||||
required_version = ">= 1.0"
|
||||
|
||||
required_providers {
|
||||
coder = {
|
||||
source = "coder/coder"
|
||||
version = ">= 2.5"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
variable "nexus_url" {
|
||||
type = string
|
||||
description = "The base URL of your Nexus repository manager (e.g. https://nexus.example.com)"
|
||||
validation {
|
||||
condition = can(regex("^(https|http)://", var.nexus_url))
|
||||
error_message = "nexus_url must be a valid URL starting with either 'https://' or 'http://'"
|
||||
}
|
||||
}
|
||||
|
||||
variable "nexus_username" {
|
||||
type = string
|
||||
description = "Custom username for Nexus authentication. If not provided, defaults to the Coder username based on the username_field setting"
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "nexus_password" {
|
||||
type = string
|
||||
description = "API token or password for Nexus authentication. This value is sensitive and should be stored securely"
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
variable "agent_id" {
|
||||
type = string
|
||||
description = "The ID of a Coder agent."
|
||||
}
|
||||
|
||||
variable "package_managers" {
|
||||
type = object({
|
||||
maven = optional(list(string), [])
|
||||
npm = optional(list(string), [])
|
||||
go = optional(list(string), [])
|
||||
pypi = optional(list(string), [])
|
||||
docker = optional(list(string), [])
|
||||
})
|
||||
default = {
|
||||
maven = []
|
||||
npm = []
|
||||
go = []
|
||||
pypi = []
|
||||
docker = []
|
||||
}
|
||||
description = <<-EOF
|
||||
Configuration for package managers. Each key maps to a list of Nexus repository names:
|
||||
- maven: List of Maven repository names
|
||||
- npm: List of npm repository names (supports scoped packages with "@scope:repo-name")
|
||||
- go: List of Go proxy repository names
|
||||
- pypi: List of PyPI repository names
|
||||
- docker: List of Docker registry names
|
||||
Unused package managers can be omitted.
|
||||
Example:
|
||||
{
|
||||
maven = ["maven-public", "maven-releases"]
|
||||
npm = ["npm-public", "@scoped:npm-private"]
|
||||
go = ["go-public", "go-private"]
|
||||
pypi = ["pypi-public", "pypi-private"]
|
||||
docker = ["docker-public", "docker-private"]
|
||||
}
|
||||
EOF
|
||||
}
|
||||
|
||||
variable "username_field" {
|
||||
type = string
|
||||
description = "Field to use for username (\"username\" or \"email\"). Defaults to \"username\". Only used when nexus_username is not provided"
|
||||
default = "username"
|
||||
validation {
|
||||
condition = can(regex("^(email|username)$", var.username_field))
|
||||
error_message = "username_field must be either 'email' or 'username'"
|
||||
}
|
||||
}
|
||||
|
||||
data "coder_workspace" "me" {}
|
||||
data "coder_workspace_owner" "me" {}
|
||||
|
||||
locals {
|
||||
username = coalesce(var.nexus_username, var.username_field == "email" ? data.coder_workspace_owner.me.email : data.coder_workspace_owner.me.name)
|
||||
nexus_host = split("/", replace(replace(var.nexus_url, "https://", ""), "http://", ""))[0]
|
||||
}
|
||||
|
||||
locals {
|
||||
# Get first repository name or use default
|
||||
maven_repo = length(var.package_managers.maven) > 0 ? var.package_managers.maven[0] : "maven-public"
|
||||
npm_repo = length(var.package_managers.npm) > 0 ? var.package_managers.npm[0] : "npm-public"
|
||||
go_repo = length(var.package_managers.go) > 0 ? var.package_managers.go[0] : "go-public"
|
||||
pypi_repo = length(var.package_managers.pypi) > 0 ? var.package_managers.pypi[0] : "pypi-public"
|
||||
|
||||
npmrc = <<-EOF
|
||||
registry=${var.nexus_url}/repository/${local.npm_repo}/
|
||||
//${local.nexus_host}/repository/${local.npm_repo}/:username=${local.username}
|
||||
//${local.nexus_host}/repository/${local.npm_repo}/:_password=${base64encode(var.nexus_password)}
|
||||
//${local.nexus_host}/repository/${local.npm_repo}/:always-auth=true
|
||||
EOF
|
||||
}
|
||||
|
||||
resource "coder_script" "nexus" {
|
||||
agent_id = var.agent_id
|
||||
display_name = "nexus-repository"
|
||||
icon = "/icon/nexus-repository.svg"
|
||||
script = templatefile("${path.module}/run.sh", {
|
||||
NEXUS_URL = var.nexus_url
|
||||
NEXUS_HOST = local.nexus_host
|
||||
NEXUS_USERNAME = local.username
|
||||
NEXUS_PASSWORD = var.nexus_password
|
||||
HAS_MAVEN = length(var.package_managers.maven) == 0 ? "" : "YES"
|
||||
MAVEN_REPO = local.maven_repo
|
||||
HAS_NPM = length(var.package_managers.npm) == 0 ? "" : "YES"
|
||||
NPMRC = local.npmrc
|
||||
HAS_GO = length(var.package_managers.go) == 0 ? "" : "YES"
|
||||
GO_REPO = local.go_repo
|
||||
HAS_PYPI = length(var.package_managers.pypi) == 0 ? "" : "YES"
|
||||
PYPI_REPO = local.pypi_repo
|
||||
HAS_DOCKER = length(var.package_managers.docker) == 0 ? "" : "YES"
|
||||
REGISTER_DOCKER = join("\n ", formatlist("register_docker \"%s\"", var.package_managers.docker))
|
||||
})
|
||||
run_on_start = true
|
||||
}
|
||||
|
||||
resource "coder_env" "goproxy" {
|
||||
count = length(var.package_managers.go) == 0 ? 0 : 1
|
||||
agent_id = var.agent_id
|
||||
name = "GOPROXY"
|
||||
value = join(",", [
|
||||
for repo in var.package_managers.go :
|
||||
"https://${local.username}:${var.nexus_password}@${local.nexus_host}/repository/${repo}"
|
||||
])
|
||||
}
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
not_configured() {
|
||||
type=$1
|
||||
echo "🤔 no $type repository is set, skipping $type configuration."
|
||||
}
|
||||
|
||||
config_complete() {
|
||||
echo "🥳 Configuration complete!"
|
||||
}
|
||||
|
||||
register_docker() {
|
||||
repo=$1
|
||||
echo -n "${NEXUS_PASSWORD}" | docker login "${NEXUS_HOST}/repository/$${repo}" --username "${NEXUS_USERNAME}" --password-stdin
|
||||
}
|
||||
|
||||
echo "🚀 Configuring Nexus repository access..."
|
||||
|
||||
# Configure Maven
|
||||
if [ -n "${HAS_MAVEN}" ]; then
|
||||
echo "☕ Configuring Maven..."
|
||||
mkdir -p ~/.m2
|
||||
cat > ~/.m2/settings.xml << 'EOF'
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0">
|
||||
<servers>
|
||||
<server>
|
||||
<id>nexus</id>
|
||||
<username>${NEXUS_USERNAME}</username>
|
||||
<password>${NEXUS_PASSWORD}</password>
|
||||
</server>
|
||||
</servers>
|
||||
<mirrors>
|
||||
<mirror>
|
||||
<id>nexus-mirror</id>
|
||||
<mirrorOf>*</mirrorOf>
|
||||
<url>${NEXUS_URL}/repository/${MAVEN_REPO}</url>
|
||||
</mirror>
|
||||
</mirrors>
|
||||
</settings>
|
||||
EOF
|
||||
config_complete
|
||||
else
|
||||
not_configured maven
|
||||
fi
|
||||
|
||||
# Configure npm
|
||||
if [ -n "${HAS_NPM}" ]; then
|
||||
echo "📦 Configuring npm..."
|
||||
cat > ~/.npmrc << 'EOF'
|
||||
${NPMRC}
|
||||
EOF
|
||||
config_complete
|
||||
else
|
||||
not_configured npm
|
||||
fi
|
||||
|
||||
# Configure Go
|
||||
if [ -n "${HAS_GO}" ]; then
|
||||
echo "🐹 Configuring Go..."
|
||||
# Go configuration is handled via GOPROXY environment variable
|
||||
# which is set by the Terraform configuration
|
||||
echo "Go proxy configured via GOPROXY environment variable"
|
||||
config_complete
|
||||
else
|
||||
not_configured go
|
||||
fi
|
||||
|
||||
# Configure pip
|
||||
if [ -n "${HAS_PYPI}" ]; then
|
||||
echo "🐍 Configuring pip..."
|
||||
mkdir -p ~/.pip
|
||||
# Create .netrc file for secure credential storage
|
||||
cat > ~/.netrc << EOF
|
||||
machine ${NEXUS_HOST}
|
||||
login ${NEXUS_USERNAME}
|
||||
password ${NEXUS_PASSWORD}
|
||||
EOF
|
||||
chmod 600 ~/.netrc
|
||||
|
||||
# Update pip.conf to use index-url without embedded credentials
|
||||
cat > ~/.pip/pip.conf << 'EOF'
|
||||
[global]
|
||||
index-url = https://${NEXUS_HOST}/repository/${PYPI_REPO}/simple
|
||||
EOF
|
||||
config_complete
|
||||
else
|
||||
not_configured pypi
|
||||
fi
|
||||
|
||||
# Configure Docker
|
||||
if [ -n "${HAS_DOCKER}" ]; then
|
||||
if command -v docker > /dev/null 2>&1; then
|
||||
echo "🐳 Configuring Docker credentials..."
|
||||
mkdir -p ~/.docker
|
||||
${REGISTER_DOCKER}
|
||||
config_complete
|
||||
else
|
||||
echo "🤔 Docker is not installed, skipping Docker configuration."
|
||||
fi
|
||||
else
|
||||
not_configured docker
|
||||
fi
|
||||
|
||||
echo "✅ Nexus repository configuration completed!"
|
||||
Reference in New Issue
Block a user