Merge pull request 'master' (#1) from master into main

Reviewed-on: http://git.lab/vasceannie/code-tools/pulls/1
This commit was merged in pull request #1.
This commit is contained in:
2025-09-06 01:44:16 +00:00
19 changed files with 3145 additions and 0 deletions

115
coder-compose.yaml Normal file
View File

@@ -0,0 +1,115 @@
services:
coder:
user: root
group_add:
- 988
image: 'ghcr.io/coder/coder:${CODER_VERSION:-latest}'
expose:
- '7080'
environment:
CODER_PG_CONNECTION_URL: 'postgresql://${POSTGRES_USER:-username}:${POSTGRES_PASSWORD:-password}@database/${POSTGRES_DB:-coder}?sslmode=disable'
CODER_HTTP_ADDRESS: '0.0.0.0:7080'
CODER_ACCESS_URL: '${CODER_ACCESS_URL}'
CODER_WILDCARD_ACCESS_URL: '${CODER_WILDCARD_ACCESS_URL}'
CODER_ADDRESS: '${CODER_ADDRESS}'
COOLIFY_RESOURCE_UUID: bwk8ckcok8o84cc0o4os4sso
COOLIFY_CONTAINER_NAME: coder-bwk8ckcok8o84cc0o4os4sso
COOLIFY_URL: 'http://dev.lab'
COOLIFY_FQDN: dev.lab
volumes:
- '/var/run/docker.sock:/var/run/docker.sock'
- 'bwk8ckcok8o84cc0o4os4sso_coder-home:/home/coder'
- /home/trav/code-tools:/home/coder/resources
depends_on:
database:
condition: service_healthy
healthcheck:
test:
- CMD-SHELL
- 'curl -f -s http://localhost:7080/healthz || exit 1'
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
labels:
- glance.name=Coder
- 'glance.icon=https://cdn.jsdelivr.net/gh/selfhst/icons/webp/coder.webp'
- 'glance.url=http://dev.lab'
- 'glance.description=Mad Science Lab'
- glance.id=coder
- glance.category=dev
- glance.hide=false
- coolify.managed=true
- coolify.version=4.0.0-beta.420.6
- coolify.serviceId=41
- coolify.type=service
- coolify.name=coder-bwk8ckcok8o84cc0o4os4sso
- coolify.resourceName=coder
- coolify.projectName=development
- coolify.serviceName=coder
- coolify.environmentName=production
- coolify.pullRequestId=0
- coolify.service.subId=308
- coolify.service.subType=application
- coolify.service.subName=coder
- traefik.enable=true
- traefik.http.middlewares.gzip.compress=true
- traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https
- traefik.http.routers.http-0-bwk8ckcok8o84cc0o4os4sso-coder.entryPoints=http
- traefik.http.routers.http-0-bwk8ckcok8o84cc0o4os4sso-coder.middlewares=gzip
- 'traefik.http.routers.http-0-bwk8ckcok8o84cc0o4os4sso-coder.rule=Host(`dev.lab`) && PathPrefix(`/`)'
- traefik.http.routers.http-0-bwk8ckcok8o84cc0o4os4sso-coder.service=http-0-bwk8ckcok8o84cc0o4os4sso-coder
- traefik.http.services.http-0-bwk8ckcok8o84cc0o4os4sso-coder.loadbalancer.server.port=7080
container_name: coder-bwk8ckcok8o84cc0o4os4sso
restart: unless-stopped
networks:
bwk8ckcok8o84cc0o4os4sso: null
database:
image: 'postgres:17'
environment:
POSTGRES_USER: '${POSTGRES_USER:-username}'
POSTGRES_PASSWORD: '${POSTGRES_PASSWORD:-password}'
POSTGRES_DB: '${POSTGRES_DB:-coder}'
COOLIFY_RESOURCE_UUID: bwk8ckcok8o84cc0o4os4sso
COOLIFY_CONTAINER_NAME: database-bwk8ckcok8o84cc0o4os4sso
volumes:
- 'bwk8ckcok8o84cc0o4os4sso_coder-data:/var/lib/postgresql/data'
healthcheck:
test:
- CMD-SHELL
- 'pg_isready -U ${POSTGRES_USER:-username} -d ${POSTGRES_DB:-coder}'
interval: 5s
timeout: 5s
retries: 5
labels:
- glance.name=Postgres-Coder
- glance.parent=coder
- glance.hide=false
- coolify.managed=true
- coolify.version=4.0.0-beta.420.6
- coolify.serviceId=41
- coolify.type=service
- coolify.name=database-bwk8ckcok8o84cc0o4os4sso
- coolify.resourceName=coder
- coolify.projectName=development
- coolify.serviceName=database
- coolify.environmentName=production
- coolify.pullRequestId=0
- coolify.service.subId=38
- coolify.service.subType=database
- coolify.service.subName=database
container_name: database-bwk8ckcok8o84cc0o4os4sso
restart: unless-stopped
networks:
bwk8ckcok8o84cc0o4os4sso: null
volumes:
bwk8ckcok8o84cc0o4os4sso_coder-home:
name: bwk8ckcok8o84cc0o4os4sso_coder-home
bwk8ckcok8o84cc0o4os4sso_coder-data:
name: bwk8ckcok8o84cc0o4os4sso_coder-data
networks:
bwk8ckcok8o84cc0o4os4sso:
name: bwk8ckcok8o84cc0o4os4sso
external: true
configs: { }
secrets: { }

6
main.py Normal file
View File

@@ -0,0 +1,6 @@
def main():
print("Hello from code-tools!")
if __name__ == "__main__":
main()

7
pyproject.toml Normal file
View File

@@ -0,0 +1,7 @@
[project]
name = "code-tools"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = []

View File

@@ -0,0 +1,133 @@
---
display_name: JetBrains Gateway
description: Add a one-click button to launch JetBrains Gateway IDEs in the dashboard.
icon: ../.icons/gateway.svg
maintainer_github: coder
verified: true
tags: [ide, jetbrains, helper, parameter]
---
# JetBrains Gateway
This module adds a JetBrains Gateway Button to open any workspace with a single click.
JetBrains recommends a minimum of 4 CPU cores and 8GB of RAM.
Consult the [JetBrains documentation](https://www.jetbrains.com/help/idea/prerequisites.html#min_requirements) to confirm other system requirements.
```tf
module "jetbrains_gateway" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/modules/jetbrains-gateway/coder"
version = "1.0.28"
agent_id = coder_agent.example.id
folder = "/home/coder/example"
jetbrains_ides = ["CL", "GO", "IU", "PY", "WS"]
default = "GO"
}
```
![JetBrains Gateway IDes list](../.images/jetbrains-gateway.png)
## Examples
### Add GoLand and WebStorm as options with the default set to GoLand
```tf
module "jetbrains_gateway" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/modules/jetbrains-gateway/coder"
version = "1.0.28"
agent_id = coder_agent.example.id
folder = "/home/coder/example"
jetbrains_ides = ["GO", "WS"]
default = "GO"
}
```
### Use the latest version of each IDE
```tf
module "jetbrains_gateway" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/modules/jetbrains-gateway/coder"
version = "1.0.28"
agent_id = coder_agent.example.id
folder = "/home/coder/example"
jetbrains_ides = ["IU", "PY"]
default = "IU"
latest = true
}
```
### Use fixed versions set by `jetbrains_ide_versions`
```tf
module "jetbrains_gateway" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/modules/jetbrains-gateway/coder"
version = "1.0.28"
agent_id = coder_agent.example.id
folder = "/home/coder/example"
jetbrains_ides = ["IU", "PY"]
default = "IU"
latest = false
jetbrains_ide_versions = {
"IU" = {
build_number = "243.21565.193"
version = "2024.3"
}
"PY" = {
build_number = "243.21565.199"
version = "2024.3"
}
}
}
```
### Use the latest EAP version
```tf
module "jetbrains_gateway" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/modules/jetbrains-gateway/coder"
version = "1.0.28"
agent_id = coder_agent.example.id
folder = "/home/coder/example"
jetbrains_ides = ["GO", "WS"]
default = "GO"
latest = true
channel = "eap"
}
```
### Custom base link
Due to the highest priority of the `ide_download_link` parameter in the `(jetbrains-gateway://...` within IDEA, the pre-configured download address will be overridden when using [IDEA's offline mode](https://www.jetbrains.com/help/idea/fully-offline-mode.html). Therefore, it is necessary to configure the `download_base_link` parameter for the `jetbrains_gateway` module to change the value of `ide_download_link`.
```tf
module "jetbrains_gateway" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/modules/jetbrains-gateway/coder"
version = "1.0.28"
agent_id = coder_agent.example.id
folder = "/home/coder/example"
jetbrains_ides = ["GO", "WS"]
releases_base_link = "https://releases.internal.site/"
download_base_link = "https://download.internal.site/"
default = "GO"
}
```
## Supported IDEs
This module and JetBrains Gateway support the following JetBrains IDEs:
- [GoLand (`GO`)](https://www.jetbrains.com/go/)
- [WebStorm (`WS`)](https://www.jetbrains.com/webstorm/)
- [IntelliJ IDEA Ultimate (`IU`)](https://www.jetbrains.com/idea/)
- [PyCharm Professional (`PY`)](https://www.jetbrains.com/pycharm/)
- [PhpStorm (`PS`)](https://www.jetbrains.com/phpstorm/)
- [CLion (`CL`)](https://www.jetbrains.com/clion/)
- [RubyMine (`RM`)](https://www.jetbrains.com/ruby/)
- [Rider (`RD`)](https://www.jetbrains.com/rider/)
- [RustRover (`RR`)](https://www.jetbrains.com/rust/)

View File

@@ -0,0 +1,43 @@
import { it, expect, describe } from "bun:test";
import {
runTerraformInit,
testRequiredVariables,
runTerraformApply,
} from "../test";
describe("jetbrains-gateway", async () => {
await runTerraformInit(import.meta.dir);
await testRequiredVariables(import.meta.dir, {
agent_id: "foo",
folder: "/home/foo",
});
it("should create a link with the default values", async () => {
const state = await runTerraformApply(import.meta.dir, {
// These are all required.
agent_id: "foo",
folder: "/home/coder",
});
expect(state.outputs.url.value).toBe(
"jetbrains-gateway://connect#type=coder&workspace=default&owner=default&folder=/home/coder&url=https://mydeployment.coder.com&token=$SESSION_TOKEN&ide_product_code=IU&ide_build_number=243.21565.193&ide_download_link=https://download.jetbrains.com/idea/ideaIU-2024.3.tar.gz",
);
const coder_app = state.resources.find(
(res) => res.type === "coder_app" && res.name === "gateway",
);
expect(coder_app).not.toBeNull();
expect(coder_app?.instances.length).toBe(1);
expect(coder_app?.instances[0].attributes.order).toBeNull();
});
it("default to first ide", async () => {
const state = await runTerraformApply(import.meta.dir, {
agent_id: "foo",
folder: "/home/foo",
jetbrains_ides: '["IU", "GO", "PY"]',
});
expect(state.outputs.identifier.value).toBe("IU");
});
});

View File

@@ -0,0 +1,341 @@
terraform {
required_version = ">= 1.0"
required_providers {
coder = {
source = "coder/coder"
version = ">= 0.17"
}
http = {
source = "hashicorp/http"
version = ">= 3.0"
}
}
}
variable "agent_id" {
type = string
description = "The ID of a Coder agent."
}
variable "slug" {
type = string
description = "The slug for the coder_app. Allows resuing the module with the same template."
default = "gateway"
}
variable "agent_name" {
type = string
description = "Agent name. (unused). Will be removed in a future version"
default = ""
}
variable "folder" {
type = string
description = "The directory to open in the IDE. e.g. /home/coder/project"
validation {
condition = can(regex("^(?:/[^/]+)+$", var.folder))
error_message = "The folder must be a full path and must not start with a ~."
}
}
variable "default" {
default = ""
type = string
description = "Default IDE"
}
variable "order" {
type = number
description = "The order determines the position of app in the UI presentation. The lowest order is shown first and apps with equal order are sorted by name (ascending order)."
default = null
}
variable "coder_parameter_order" {
type = number
description = "The order determines the position of a template parameter in the UI/CLI presentation. The lowest order is shown first and parameters with equal order are sorted by name (ascending order)."
default = null
}
variable "latest" {
type = bool
description = "Whether to fetch the latest version of the IDE."
default = false
}
variable "channel" {
type = string
description = "JetBrains IDE release channel. Valid values are release and eap."
default = "release"
validation {
condition = can(regex("^(release|eap)$", var.channel))
error_message = "The channel must be either release or eap."
}
}
variable "jetbrains_ide_versions" {
type = map(object({
build_number = string
version = string
}))
description = "The set of versions for each jetbrains IDE"
default = {
"IU" = {
build_number = "243.21565.193"
version = "2024.3"
}
"PS" = {
build_number = "243.21565.202"
version = "2024.3"
}
"WS" = {
build_number = "243.21565.180"
version = "2024.3"
}
"PY" = {
build_number = "243.21565.199"
version = "2024.3"
}
"CL" = {
build_number = "243.21565.238"
version = "2024.1"
}
"GO" = {
build_number = "243.21565.208"
version = "2024.3"
}
"RM" = {
build_number = "243.21565.197"
version = "2024.3"
}
"RD" = {
build_number = "243.21565.191"
version = "2024.3"
}
"RR" = {
build_number = "243.22562.230"
version = "2024.3"
}
}
validation {
condition = (
alltrue([
for code in keys(var.jetbrains_ide_versions) : contains(["IU", "PS", "WS", "PY", "CL", "GO", "RM", "RD", "RR"], code)
])
)
error_message = "The jetbrains_ide_versions must contain a map of valid product codes. Valid product codes are ${join(",", ["IU", "PS", "WS", "PY", "CL", "GO", "RM", "RD", "RR"])}."
}
}
variable "jetbrains_ides" {
type = list(string)
description = "The list of IDE product codes."
default = ["IU", "PS", "WS", "PY", "CL", "GO", "RM", "RD", "RR"]
validation {
condition = (
alltrue([
for code in var.jetbrains_ides : contains(["IU", "PS", "WS", "PY", "CL", "GO", "RM", "RD", "RR"], code)
])
)
error_message = "The jetbrains_ides must be a list of valid product codes. Valid product codes are ${join(",", ["IU", "PS", "WS", "PY", "CL", "GO", "RM", "RD", "RR"])}."
}
# check if the list is empty
validation {
condition = length(var.jetbrains_ides) > 0
error_message = "The jetbrains_ides must not be empty."
}
# check if the list contains duplicates
validation {
condition = length(var.jetbrains_ides) == length(toset(var.jetbrains_ides))
error_message = "The jetbrains_ides must not contain duplicates."
}
}
variable "releases_base_link" {
type = string
description = ""
default = "https://data.services.jetbrains.com"
validation {
condition = can(regex("^https?://.+$", var.releases_base_link))
error_message = "The releases_base_link must be a valid HTTP/S address."
}
}
variable "download_base_link" {
type = string
description = ""
default = "https://download.jetbrains.com"
validation {
condition = can(regex("^https?://.+$", var.download_base_link))
error_message = "The download_base_link must be a valid HTTP/S address."
}
}
data "http" "jetbrains_ide_versions" {
for_each = var.latest ? toset(var.jetbrains_ides) : toset([])
url = "${var.releases_base_link}/products/releases?code=${each.key}&latest=true&type=${var.channel}"
}
locals {
jetbrains_ides = {
"GO" = {
icon = "/icon/goland.svg",
name = "GoLand",
identifier = "GO",
build_number = var.jetbrains_ide_versions["GO"].build_number,
download_link = "${var.download_base_link}/go/goland-${var.jetbrains_ide_versions["GO"].version}.tar.gz"
version = var.jetbrains_ide_versions["GO"].version
},
"WS" = {
icon = "/icon/webstorm.svg",
name = "WebStorm",
identifier = "WS",
build_number = var.jetbrains_ide_versions["WS"].build_number,
download_link = "${var.download_base_link}/webstorm/WebStorm-${var.jetbrains_ide_versions["WS"].version}.tar.gz"
version = var.jetbrains_ide_versions["WS"].version
},
"IU" = {
icon = "/icon/intellij.svg",
name = "IntelliJ IDEA Ultimate",
identifier = "IU",
build_number = var.jetbrains_ide_versions["IU"].build_number,
download_link = "${var.download_base_link}/idea/ideaIU-${var.jetbrains_ide_versions["IU"].version}.tar.gz"
version = var.jetbrains_ide_versions["IU"].version
},
"PY" = {
icon = "/icon/pycharm.svg",
name = "PyCharm Professional",
identifier = "PY",
build_number = var.jetbrains_ide_versions["PY"].build_number,
download_link = "${var.download_base_link}/python/pycharm-professional-${var.jetbrains_ide_versions["PY"].version}.tar.gz"
version = var.jetbrains_ide_versions["PY"].version
},
"CL" = {
icon = "/icon/clion.svg",
name = "CLion",
identifier = "CL",
build_number = var.jetbrains_ide_versions["CL"].build_number,
download_link = "${var.download_base_link}/cpp/CLion-${var.jetbrains_ide_versions["CL"].version}.tar.gz"
version = var.jetbrains_ide_versions["CL"].version
},
"PS" = {
icon = "/icon/phpstorm.svg",
name = "PhpStorm",
identifier = "PS",
build_number = var.jetbrains_ide_versions["PS"].build_number,
download_link = "${var.download_base_link}/webide/PhpStorm-${var.jetbrains_ide_versions["PS"].version}.tar.gz"
version = var.jetbrains_ide_versions["PS"].version
},
"RM" = {
icon = "/icon/rubymine.svg",
name = "RubyMine",
identifier = "RM",
build_number = var.jetbrains_ide_versions["RM"].build_number,
download_link = "${var.download_base_link}/ruby/RubyMine-${var.jetbrains_ide_versions["RM"].version}.tar.gz"
version = var.jetbrains_ide_versions["RM"].version
},
"RD" = {
icon = "/icon/rider.svg",
name = "Rider",
identifier = "RD",
build_number = var.jetbrains_ide_versions["RD"].build_number,
download_link = "${var.download_base_link}/rider/JetBrains.Rider-${var.jetbrains_ide_versions["RD"].version}.tar.gz"
version = var.jetbrains_ide_versions["RD"].version
},
"RR" = {
icon = "/icon/rustrover.svg",
name = "RustRover",
identifier = "RR",
build_number = var.jetbrains_ide_versions["RR"].build_number,
download_link = "${var.download_base_link}/rustrover/RustRover-${var.jetbrains_ide_versions["RR"].version}.tar.gz"
version = var.jetbrains_ide_versions["RR"].version
}
}
icon = local.jetbrains_ides[data.coder_parameter.jetbrains_ide.value].icon
json_data = var.latest ? jsondecode(data.http.jetbrains_ide_versions[data.coder_parameter.jetbrains_ide.value].response_body) : {}
key = var.latest ? keys(local.json_data)[0] : ""
display_name = local.jetbrains_ides[data.coder_parameter.jetbrains_ide.value].name
identifier = data.coder_parameter.jetbrains_ide.value
download_link = var.latest ? local.json_data[local.key][0].downloads.linux.link : local.jetbrains_ides[data.coder_parameter.jetbrains_ide.value].download_link
build_number = var.latest ? local.json_data[local.key][0].build : local.jetbrains_ides[data.coder_parameter.jetbrains_ide.value].build_number
version = var.latest ? local.json_data[local.key][0].version : var.jetbrains_ide_versions[data.coder_parameter.jetbrains_ide.value].version
}
data "coder_parameter" "jetbrains_ide" {
type = "string"
name = "jetbrains_ide"
display_name = "JetBrains IDE"
icon = "/icon/gateway.svg"
mutable = true
default = var.default == "" ? var.jetbrains_ides[0] : var.default
order = var.coder_parameter_order
dynamic "option" {
for_each = var.jetbrains_ides
content {
icon = local.jetbrains_ides[option.value].icon
name = local.jetbrains_ides[option.value].name
value = option.value
}
}
}
data "coder_workspace" "me" {}
data "coder_workspace_owner" "me" {}
resource "coder_app" "gateway" {
agent_id = var.agent_id
slug = var.slug
display_name = local.display_name
icon = local.icon
external = true
order = var.order
url = join("", [
"jetbrains-gateway://connect#type=coder&workspace=",
data.coder_workspace.me.name,
"&owner=",
data.coder_workspace_owner.me.name,
"&folder=",
var.folder,
"&url=",
data.coder_workspace.me.access_url,
"&token=",
"$SESSION_TOKEN",
"&ide_product_code=",
data.coder_parameter.jetbrains_ide.value,
"&ide_build_number=",
local.build_number,
"&ide_download_link=",
local.download_link,
])
}
output "identifier" {
value = local.identifier
}
output "display_name" {
value = local.display_name
}
output "icon" {
value = local.icon
}
output "download_link" {
value = local.download_link
}
output "build_number" {
value = local.build_number
}
output "version" {
value = local.version
}
output "url" {
value = coder_app.gateway.url
}

View File

@@ -0,0 +1 @@
{"Modules":[{"Key":"","Source":"","Dir":"."},{"Key":"jetbrains_gateway","Source":"registry.coder.com/modules/jetbrains-gateway/coder","Version":"1.0.29","Dir":".terraform/modules/jetbrains_gateway"}]}

View File

@@ -0,0 +1,375 @@
Copyright (c) 2017 HashiCorp, Inc.
Mozilla Public License Version 2.0
==================================
1. Definitions
--------------
1.1. "Contributor"
means each individual or legal entity that creates, contributes to
the creation of, or owns Covered Software.
1.2. "Contributor Version"
means the combination of the Contributions of others (if any) used
by a Contributor and that particular Contributor's Contribution.
1.3. "Contribution"
means Covered Software of a particular Contributor.
1.4. "Covered Software"
means Source Code Form to which the initial Contributor has attached
the notice in Exhibit A, the Executable Form of such Source Code
Form, and Modifications of such Source Code Form, in each case
including portions thereof.
1.5. "Incompatible With Secondary Licenses"
means
(a) that the initial Contributor has attached the notice described
in Exhibit B to the Covered Software; or
(b) that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the
terms of a Secondary License.
1.6. "Executable Form"
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in
a separate file or files, that is not Covered Software.
1.8. "License"
means this document.
1.9. "Licensable"
means having the right to grant, to the maximum extent possible,
whether at the time of the initial grant or subsequently, any and
all of the rights conveyed by this License.
1.10. "Modifications"
means any of the following:
(a) any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered
Software; or
(b) any new file in Source Code Form that contains any Covered
Software.
1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the
License, by the making, using, selling, offering for sale, having
made, import, or transfer of either its Contributions or its
Contributor Version.
1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU
Lesser General Public License, Version 2.1, the GNU Affero General
Public License, Version 3.0, or any later versions of those
licenses.
1.13. "Source Code Form"
means the form of the work preferred for making modifications.
1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that
controls, is controlled by, or is under common control with You. For
purposes of this definition, "control" means (a) the power, direct
or indirect, to cause the direction or management of such entity,
whether by contract or otherwise, or (b) ownership of more than
fifty percent (50%) of the outstanding shares or beneficial
ownership of such entity.
2. License Grants and Conditions
--------------------------------
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
(a) under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
(b) under Patent Claims of such Contributor to make, use, sell, offer
for sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
(a) for any code that a Contributor has removed from Covered Software;
or
(b) for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
(c) under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights
to grant the rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
in Section 2.1.
3. Responsibilities
-------------------
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
(a) such Covered Software must also be made available in Source Code
Form, as described in Section 3.1, and You must inform recipients of
the Executable Form how they can obtain a copy of such Source Code
Form by reasonable means in a timely manner, at a charge no more
than the cost of distribution to the recipient; and
(b) You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter
the recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).
3.4. Notices
You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty,
or limitations of liability) contained within the Source Code Form of
the Covered Software, except that You may alter any license notices to
the extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
---------------------------------------------------
If it is impossible for You to comply with any of the terms of this
License with respect to some or all of the Covered Software due to
statute, judicial order, or regulation then You must: (a) comply with
the terms of this License to the maximum extent possible; and (b)
describe the limitations and the code they affect. Such description must
be placed in a text file included with all distributions of the Covered
Software under this License. Except to the extent prohibited by statute
or regulation, such description must be sufficiently detailed for a
recipient of ordinary skill to be able to understand it.
5. Termination
--------------
5.1. The rights granted under this License will terminate automatically
if You fail to comply with any of its terms. However, if You become
compliant, then the rights granted under this License from a particular
Contributor are reinstated (a) provisionally, unless and until such
Contributor explicitly and finally terminates Your grants, and (b) on an
ongoing basis, if such Contributor fails to notify You of the
non-compliance by some reasonable means prior to 60 days after You have
come back into compliance. Moreover, Your grants from a particular
Contributor are reinstated on an ongoing basis if such Contributor
notifies You of the non-compliance by some reasonable means, this is the
first time You have received notice of non-compliance with this License
from such Contributor, and You become compliant prior to 30 days after
Your receipt of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
end user license agreements (excluding distributors and resellers) which
have been validly granted by You or Your distributors under this License
prior to termination shall survive termination.
************************************************************************
* *
* 6. Disclaimer of Warranty *
* ------------------------- *
* *
* Covered Software is provided under this License on an "as is" *
* basis, without warranty of any kind, either expressed, implied, or *
* statutory, including, without limitation, warranties that the *
* Covered Software is free of defects, merchantable, fit for a *
* particular purpose or non-infringing. The entire risk as to the *
* quality and performance of the Covered Software is with You. *
* Should any Covered Software prove defective in any respect, You *
* (not any Contributor) assume the cost of any necessary servicing, *
* repair, or correction. This disclaimer of warranty constitutes an *
* essential part of this License. No use of any Covered Software is *
* authorized under this License except under this disclaimer. *
* *
************************************************************************
************************************************************************
* *
* 7. Limitation of Liability *
* -------------------------- *
* *
* Under no circumstances and under no legal theory, whether tort *
* (including negligence), contract, or otherwise, shall any *
* Contributor, or anyone who distributes Covered Software as *
* permitted above, be liable to You for any direct, indirect, *
* special, incidental, or consequential damages of any character *
* including, without limitation, damages for lost profits, loss of *
* goodwill, work stoppage, computer failure or malfunction, or any *
* and all other commercial damages or losses, even if such party *
* shall have been informed of the possibility of such damages. This *
* limitation of liability shall not apply to liability for death or *
* personal injury resulting from such party's negligence to the *
* extent applicable law prohibits such limitation. Some *
* jurisdictions do not allow the exclusion or limitation of *
* incidental or consequential damages, so this exclusion and *
* limitation may not apply to You. *
* *
************************************************************************
8. Litigation
-------------
Any litigation relating to this License may be brought only in the
courts of a jurisdiction where the defendant maintains its principal
place of business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions.
Nothing in this Section shall prevent a party's ability to bring
cross-claims or counter-claims.
9. Miscellaneous
----------------
This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides
that the language of a contract shall be construed against the drafter
shall not be used to construe this License against a Contributor.
10. Versions of the License
---------------------------
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses
If You choose to distribute Source Code Form that is Incompatible With
Secondary Licenses under the terms of this version of the License, the
notice described in Exhibit B of this License must be attached.
Exhibit A - Source Code Form License Notice
-------------------------------------------
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular
file, then You may include the notice in a location (such as a LICENSE
file in a relevant directory) where a recipient would be likely to look
for such a notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - "Incompatible With Secondary Licenses" Notice
---------------------------------------------------------
This Source Code Form is "Incompatible With Secondary Licenses", as
defined by the Mozilla Public License, v. 2.0.

446
tf/README.md Normal file
View File

@@ -0,0 +1,446 @@
# Terraform Coder Development Environment
A comprehensive development environment deployment using Terraform and Coder, providing isolated workspaces with integrated development tools, databases, and AI-powered coding assistants.
## 🏗️ Architecture Overview
This configuration deploys self-contained development workspaces using Docker containers orchestrated by Coder. Each workspace includes:
- **Isolated Development Container** with VS Code, terminal access, and full development toolchain
- **Database Services** (PostgreSQL, Redis, Qdrant) with persistent storage
- **Management Interfaces** (pgAdmin, Qdrant Dashboard)
- **AI Development Tools** (Claude Code, Cursor, Windsurf support)
- **Reverse Proxy Integration** for seamless web access
### Network Architecture
```
┌─────────────────────────────────────────────────────────────┐
│ Reverse Proxy │
│ http://dev.lab │
│ *.dev.lab wildcard │
└─────────────────────────┬───────────────────────────────────┘
┌─────────────────────┼─────────────────────┐
│ Workspace A │ Workspace B │
│ network-{id-a} │ network-{id-b} │
│ │ │
│ ┌─────────────────┐ │ ┌─────────────────┐ │
│ │ Dev Container │ │ │ Dev Container │ │
│ │ VS Code:8080 │ │ │ VS Code:8080 │ │
│ └─────────────────┘ │ └─────────────────┘ │
│ │ │
│ ┌─────────────────┐ │ ┌─────────────────┐ │
│ │ PostgreSQL:5432 │ │ │ PostgreSQL:5432 │ │
│ │ Redis:6379 │ │ │ Redis:6379 │ │
│ │ Qdrant:6333 │ │ │ Qdrant:6333 │ │
│ │ pgAdmin:5050 │ │ │ pgAdmin:5050 │ │
│ └─────────────────┘ │ └─────────────────┘ │
└─────────────────────┼─────────────────────┘
```
**Key Benefits:**
-**Complete Network Isolation** between workspaces
-**No Port Conflicts** - same ports used in different networks
-**Scalable** - unlimited concurrent workspaces
-**Secure** - services only accessible through authenticated Coder session
## 📁 File Structure
```
tf/
├── README.md # This file
├── main.tf # Provider config, parameters, networks, volumes
├── variables.tf # All configurable parameters
├── terraform.tfvars # Variable assignments
├── workspace.tf # Main development container and Coder agent
├── services.tf # Database containers (PostgreSQL, Redis, Qdrant)
├── apps.tf # Coder applications for service access
├── scripts.tf # AI tools installation and configuration
└── outputs.tf # Workspace information exports
```
## 🛠️ Included Services & Tools
### Development Environment
- **Container Base**: Microsoft DevContainers Universal image
- **Languages**: Node.js 20, Python 3.12, Rust (latest stable)
- **Package Managers**: npm, uv (Python), Cargo (Rust)
- **System Tools**: make, tree, jq, curl, wget, build-essential
### Database Services
- **PostgreSQL 17** with Alpine Linux base
- Connection pooling and performance optimization
- pg_stat_statements enabled for query analysis
- PostgreSQL client tools included in workspace
- **Redis 7** with Alpine Linux base
- Authentication enabled with configurable password
- AOF persistence with everysec fsync
- LRU eviction policy with configurable memory limits
- **Qdrant Vector Database** (latest)
- HTTP API on port 6333, gRPC on port 6334
- Persistent storage for vector collections
- Web dashboard for collection management
### Management Interfaces
- **pgAdmin 4** - PostgreSQL administration interface
- **Qdrant Dashboard** - Vector database management
- **VS Code Server** - Browser-based IDE
- **Terminal Access** - Full bash shell access
### AI Development Tools
- **Claude Code CLI** - Anthropic's official CLI
- **Cursor Support** - AI-powered code editor integration
- **Windsurf Support** - Codeium's development environment
### Development Packages
#### Node.js (Global)
```javascript
repomix, create-next-app, nodemon, concurrently
@types/node, typescript, eslint, prettier
```
#### Python (via uv)
```python
fastapi, uvicorn, requests, pandas, numpy
psycopg2-binary, redis, qdrant-client, python-dotenv
```
## 🚀 Quick Start
### Prerequisites
1. **Coder Instance** running and accessible
2. **Reverse Proxy** configured with wildcard subdomain support
3. **Docker** daemon accessible to Coder
4. **Terraform** >= 1.0 installed
### Environment Setup
Set these environment variables in your Coder deployment:
```bash
CODER_ACCESS_URL=http://dev.lab
CODER_WILDCARD_ACCESS_URL=*.dev.lab
```
### Deployment Steps
1. **Clone and Navigate**
```bash
git clone <your-repo>
cd tf/
```
2. **Review Configuration**
```bash
# Edit terraform.tfvars to match your needs
vim terraform.tfvars
```
3. **Deploy Infrastructure**
```bash
terraform init
terraform plan
terraform apply
```
4. **Access Workspace**
- Navigate to your Coder instance
- Create new workspace using this template
- Select your preferred Git repository
- Choose whether to enable database services
## ⚙️ Configuration
### Key Variables (terraform.tfvars)
#### Resource Limits
```hcl
workspace_memory_limit = 16384 # 16GB RAM
workspace_cpu_limit = 4 # 4 CPU cores
```
#### Service Configuration
```hcl
# Database passwords (change in production!)
postgres_password = "devpassword"
redis_password = "devpassword"
# Database tuning
postgres_max_connections = 100
redis_max_memory = "512mb"
```
#### Feature Toggles
```hcl
enable_pgadmin = true # PostgreSQL admin interface
enable_monitoring = true # Resource monitoring
enable_jupyter = false # Jupyter Lab for data science
```
#### Tool Versions
```hcl
node_version = "20" # Node.js LTS
python_version = "3.12" # Python latest stable
postgres_version = "17" # PostgreSQL latest
redis_version = "7" # Redis latest stable
```
### Coder Parameters
When creating a workspace, you'll be prompted for:
1. **Git Repository** - Select from your available repositories
2. **Enable Services** - Toggle database services on/off
3. **Enable AI Tools** - Toggle AI development tool installation
## 🌐 Service Access
### Web Applications
All services are accessible through Coder's reverse proxy with subdomain routing:
| Service | URL Pattern | Description |
|---------|-------------|-------------|
| VS Code | `code-server-{workspace}.dev.lab` | Browser-based IDE |
| Terminal | Available in Coder dashboard | Full bash shell |
| pgAdmin | `pgadmin-{workspace}.dev.lab` | PostgreSQL management |
| Qdrant | `qdrant-dashboard-{workspace}.dev.lab` | Vector DB dashboard |
| Dev Server | `nextjs-3000-{workspace}.dev.lab` | Next.js dev server |
| API Server | `api-8000-{workspace}.dev.lab` | FastAPI/Flask server |
| Vite Dev | `vite-5173-{workspace}.dev.lab` | Vite development |
### Database Connections
From within your workspace container:
```bash
# PostgreSQL
export POSTGRES_URL="postgresql://postgres:devpassword@postgres-{workspace-id}:5432/postgres"
psql $POSTGRES_URL
# Redis
export REDIS_URL="redis://:devpassword@redis-{workspace-id}:6379"
redis-cli -u $REDIS_URL
# Qdrant
export QDRANT_URL="http://qdrant-{workspace-id}:6333"
curl $QDRANT_URL/health
```
### Environment Variables
These are automatically set in your workspace:
```bash
NODE_VERSION=20
PYTHON_VERSION=3.12
POSTGRES_URL=postgresql://postgres:***@postgres-{id}:5432/postgres
REDIS_URL=redis://:***@redis-{id}:6379
QDRANT_URL=http://qdrant-{id}:6333
```
## 🔧 Development Workflow
### Initial Setup
1. **Access Workspace**
```bash
# Run environment info script
devinfo
```
2. **Verify Services**
```bash
# Check PostgreSQL
pg_isready -h postgres-{workspace-id} -U postgres
# Check Redis
redis-cli -h redis-{workspace-id} ping
# Check Qdrant
curl http://qdrant-{workspace-id}:6333/health
```
### Common Tasks
#### Next.js Project
```bash
# Create new Next.js app
npx create-next-app@latest my-app
cd my-app
npm run dev # Accessible at nextjs-3000-{workspace}.dev.lab
```
#### Python FastAPI Project
```bash
# Activate Python environment
source /home/coder/.venv/bin/activate
# Create FastAPI app
uv add fastapi uvicorn
# Your app runs on port 8000, accessible via reverse proxy
```
#### Database Development
```bash
# Connect to PostgreSQL
psql $POSTGRES_URL
# Create tables, run migrations, etc.
# Access pgAdmin for GUI management
```
## 🔍 Monitoring & Debugging
### Built-in Monitoring
Coder automatically tracks:
- **CPU Usage** - Updated every 60 seconds
- **RAM Usage** - Updated every 60 seconds
- **Disk Usage** - Updated every 5 minutes
- **Git Branch** - Updated every 5 minutes
### Health Checks
All services include comprehensive health checks:
- **PostgreSQL**: `pg_isready` command
- **Redis**: Connection test with `redis-cli`
- **Qdrant**: HTTP health endpoint
- **Web Services**: HTTP response verification
### Logs and Debugging
```bash
# Container logs
docker logs postgres-{workspace-id}
docker logs redis-{workspace-id}
docker logs qdrant-{workspace-id}
# Service status
docker ps --filter label=coder.workspace_id={workspace-id}
# Network inspection
docker network inspect coder-{workspace-id}
```
## 🔐 Security Considerations
### Network Security
- **Isolated Networks** - Each workspace has its own Docker network
- **No Host Exposure** - Services only accessible through authenticated Coder session
- **Internal Communication** - Services communicate using internal DNS names
### Authentication
- **Database Passwords** - Configurable via terraform.tfvars
- **Coder Authentication** - All access requires Coder login
- **sudo Access** - Granted to `coder` user for development flexibility
### Data Persistence
- **Database Data** - Persistent Docker volumes per workspace
- **Workspace Files** - Persistent across container restarts
- **User Configuration** - Home directory persistence
## 🚨 Troubleshooting
### Common Issues
#### "Unable to find user coder"
**Solution**: Container automatically creates `coder` user during startup. If issues persist, check container logs.
#### Port Already in Use
**Solution**: This configuration uses no host port mappings. All routing is handled internally.
#### Services Not Accessible
**Solutions**:
1. Verify reverse proxy wildcard configuration
2. Check Coder environment variables:
```bash
echo $CODER_ACCESS_URL
echo $CODER_WILDCARD_ACCESS_URL
```
3. Confirm service health via Coder dashboard
#### Database Connection Issues
**Solutions**:
1. Verify service is enabled in workspace parameters
2. Check container status: `docker ps`
3. Test internal connectivity: `curl http://postgres-{id}:5432`
### Debug Commands
```bash
# Environment information
devinfo
# Network connectivity
docker network ls | grep coder
docker network inspect coder-{workspace-id}
# Service health
curl http://qdrant-{workspace-id}:6333/health
pg_isready -h postgres-{workspace-id} -U postgres
redis-cli -h redis-{workspace-id} ping
# Container status
docker ps --filter label=coder.workspace_id={workspace-id}
```
## 🔄 Updates & Maintenance
### Updating Tool Versions
1. **Modify terraform.tfvars**
```hcl
node_version = "22" # Update Node.js version
python_version = "3.13" # Update Python version
```
2. **Apply Changes**
```bash
terraform plan
terraform apply
```
3. **Restart Workspaces** - Changes apply to new workspace instances
### Adding New Services
1. **Add to services.tf** - Define new container resource
2. **Add to apps.tf** - Create Coder app for access
3. **Update variables.tf** - Add configuration options
4. **Update startup script** in workspace.tf if needed
## 🤝 Contributing
### Adding New Features
1. Follow the existing file structure and naming conventions
2. Add proper health checks for new services
3. Update this README with new service documentation
4. Test with multiple concurrent workspaces
### Best Practices
- **Resource Labels** - All resources should include `coder.workspace_id` label
- **Network Isolation** - New services should join workspace network
- **No Host Ports** - Use internal networking only
- **Health Checks** - All web services need health check endpoints
- **Persistent Data** - Use Docker volumes for data that should survive restarts
## 📚 References
- [Coder Documentation](https://coder.com/docs)
- [Terraform Docker Provider](https://registry.terraform.io/providers/kreuzwerker/docker/latest/docs)
- [PostgreSQL Docker Hub](https://hub.docker.com/_/postgres)
- [Redis Docker Hub](https://hub.docker.com/_/redis)
- [Qdrant Documentation](https://qdrant.tech/documentation/)
## 📝 License
This configuration is provided as-is for development purposes. Modify passwords and security settings for production use.
---
**🚀 Happy Coding!** Your isolated development environment is ready for productive development with full database support and AI-powered tools.

273
tf/apps.tf Normal file
View File

@@ -0,0 +1,273 @@
# =============================================================================
# Coder Applications - Service Access Points
# Web interfaces and tools for development services
# =============================================================================
# =============================================================================
# IDE and Code Editor Access
# =============================================================================
# VS Code Server
resource "coder_app" "code_server" {
agent_id = coder_agent.main.id
slug = "code-server"
display_name = "VS Code"
url = "http://localhost:8080"
icon = "/icon/code.svg"
subdomain = true
share = "owner"
order = 1
healthcheck {
url = "http://localhost:8080/healthz"
interval = 10
threshold = 5
}
}
# Terminal Access
resource "coder_app" "terminal" {
agent_id = coder_agent.main.id
slug = "terminal"
display_name = "Terminal"
icon = "/icon/terminal.svg"
command = "bash"
order = 2
}
# =============================================================================
# Service Port Forwarding for Docker Container Access
# Note: Using direct container URLs since containers are on same Docker network
# =============================================================================
# =============================================================================
# Database Management Interfaces
# =============================================================================
# pgAdmin - PostgreSQL Administration
resource "coder_app" "pgadmin" {
count = data.coder_parameter.enable_services.value && data.coder_parameter.enable_pgadmin.value ? 1 : 0
agent_id = coder_agent.main.id
slug = "pgadmin"
display_name = "pgAdmin"
url = "http://pgadmin-${local.workspace_id}:80"
icon = "/icon/postgresql.svg"
subdomain = true
share = "owner"
order = 10
healthcheck {
url = "http://pgadmin-${local.workspace_id}:80"
interval = 15
threshold = 5
}
}
# Qdrant Dashboard - Vector Database Management
resource "coder_app" "qdrant" {
count = data.coder_parameter.enable_services.value ? 1 : 0
agent_id = coder_agent.main.id
slug = "qdrant-dashboard"
display_name = "Qdrant Dashboard"
url = "http://qdrant-${local.workspace_id}:6333/dashboard"
icon = "/icon/database.svg"
subdomain = true
share = "owner"
order = 11
healthcheck {
url = "http://qdrant-${local.workspace_id}:6333/api/cluster"
interval = 30
threshold = 10
}
}
# =============================================================================
# Development Server Ports
# =============================================================================
# Next.js Development Server (default port 3000)
resource "coder_app" "nextjs_dev" {
agent_id = coder_agent.main.id
slug = "nextjs-3000"
display_name = "Next.js Dev Server"
url = "http://localhost:3000"
icon = "/icon/react.svg"
subdomain = true
share = "owner"
healthcheck {
url = "http://localhost:3000"
interval = 10
threshold = 10
}
}
# Generic Development Server (port 3000)
resource "coder_app" "dev_server_3000" {
agent_id = coder_agent.main.id
slug = "dev-3000"
display_name = "Dev Server (3000)"
url = "http://localhost:3000"
icon = "/icon/web.svg"
subdomain = true
share = "owner"
order = 21
healthcheck {
url = "http://localhost:3000"
interval = 10
threshold = 10
}
}
# API Server - FastAPI/Flask (port 8000)
resource "coder_app" "api_server_8000" {
agent_id = coder_agent.main.id
slug = "api-8000"
display_name = "API Server (8000)"
url = "http://localhost:8000"
icon = "/icon/api.svg"
subdomain = true
share = "owner"
order = 20
healthcheck {
url = "http://localhost:8000/health"
interval = 10
threshold = 10
}
}
# Vite Development Server (port 5173)
resource "coder_app" "vite_dev" {
agent_id = coder_agent.main.id
slug = "vite-5173"
display_name = "Vite Dev Server"
url = "http://localhost:5173"
icon = "/icon/web.svg"
subdomain = true
share = "owner"
healthcheck {
url = "http://localhost:5173"
interval = 10
threshold = 10
}
}
# Rust Development Server (port 8080)
resource "coder_app" "rust_server" {
agent_id = coder_agent.main.id
slug = "rust-8080"
display_name = "Rust Server (8080)"
url = "http://localhost:8080"
icon = "/icon/code.svg"
subdomain = true
share = "owner"
healthcheck {
url = "http://localhost:8080/health"
interval = 10
threshold = 10
}
}
# =============================================================================
# Data Science and Analytics Tools
# =============================================================================
# Jupyter Lab (if enabled)
resource "coder_app" "jupyter" {
count = data.coder_parameter.enable_jupyter.value ? 1 : 0
agent_id = coder_agent.main.id
slug = "jupyter"
display_name = "Jupyter Lab"
url = "http://localhost:8888"
icon = "/icon/python.svg"
subdomain = true
share = "owner"
healthcheck {
url = "http://localhost:8888"
interval = 15
threshold = 10
}
}
# =============================================================================
# Utility and Management Applications
# =============================================================================
# Environment Information
resource "coder_app" "env_info" {
agent_id = coder_agent.main.id
slug = "env-info"
display_name = "Environment Info"
icon = "/icon/info.svg"
command = "devinfo"
}
# Database Connection Tester
resource "coder_app" "db_tester" {
count = data.coder_parameter.enable_services.value ? 1 : 0
agent_id = coder_agent.main.id
slug = "db-tester"
display_name = "Database Tester"
icon = "/icon/terminal.svg"
command = "bash -c 'echo \"=== Database Connection Test ===\"; echo \"PostgreSQL: postgres-${local.workspace_id}:5432\"; echo \"Redis: redis-${local.workspace_id}:6379\"; echo \"Qdrant: qdrant-${local.workspace_id}:6333\"; echo; echo \"Test PostgreSQL:\"; pg_isready -h postgres-${local.workspace_id} -p 5432 -U postgres || echo \"PostgreSQL not ready\"; echo; echo \"Test Redis:\"; redis-cli -h redis-${local.workspace_id} -p 6379 -a \"${var.redis_password}\" ping || echo \"Redis not ready\"; echo; echo \"Test Qdrant:\"; curl -f http://qdrant-${local.workspace_id}:6333 || echo \"Qdrant not ready\"; echo; read -p \"Press Enter to exit...\"'"
}
# Development Logs Viewer
resource "coder_app" "dev_logs" {
agent_id = coder_agent.main.id
slug = "dev-logs"
display_name = "Development Logs"
icon = "/icon/terminal.svg"
command = "bash"
}
# Git Repository Manager
resource "coder_app" "git_manager" {
agent_id = coder_agent.main.id
slug = "git"
display_name = "Git Repository"
icon = "/icon/git.svg"
command = "bash"
}
# =============================================================================
# AI Development Tools Access
# =============================================================================
# Claude Code CLI Access
resource "coder_app" "claude_code" {
count = data.coder_parameter.enable_ai_tools.value && data.coder_parameter.enable_claude_code.value ? 1 : 0
agent_id = coder_agent.main.id
slug = "claude-code"
display_name = "Claude Code"
icon = "/icon/ai.svg"
command = "claude"
}
# JetBrains Gateway
resource "coder_app" "jetbrains_gateway" {
count = data.coder_parameter.enable_jetbrains.value ? 1 : 0
agent_id = coder_agent.main.id
slug = "jetbrains-gateway"
display_name = "JetBrains Gateway"
icon = "/icon/intellij.svg"
command = "bash -c 'echo \"🚀 JetBrains Gateway Integration\"; echo \"========================\"; echo \"\"; echo \"📍 Project Folder: /workspaces\"; echo \"🔧 Available IDEs: IntelliJ IDEA Ultimate, WebStorm, PyCharm Professional, GoLand\"; echo \"🌐 Default IDE: IntelliJ IDEA Ultimate\"; echo \"\"; echo \"💡 To connect:\"; echo \" 1. Install JetBrains Gateway on your local machine\"; echo \" 2. Connect to this workspace using Coder Gateway plugin\"; echo \" 3. Select your preferred IDE from the available options\"; echo \"\"; echo \"📚 Documentation: https://coder.com/docs/ides/gateway\"; echo \"\"; read -p \"Press Enter to continue...\";'"
order = 3
}
# File Manager
resource "coder_app" "file_manager" {
agent_id = coder_agent.main.id
slug = "files"
display_name = "File Manager"
icon = "/icon/folder.svg"
command = "bash -c 'export TERM=xterm-256color && cd /workspaces && ranger'"
order = 5
}

236
tf/main.tf Normal file
View File

@@ -0,0 +1,236 @@
terraform {
required_version = ">= 1.0"
required_providers {
coder = {
source = "coder/coder"
version = "~> 2.0"
}
docker = {
source = "kreuzwerker/docker"
version = "~> 2.25"
}
envbuilder = {
source = "coder/envbuilder"
version = "~> 1.0"
}
}
}
provider "coder" {}
provider "docker" {
host = var.docker_socket != "" ? var.docker_socket : null
}
provider "envbuilder" {}
# Data Sources
data "coder_provisioner" "me" {}
data "coder_workspace" "me" {}
data "coder_workspace_owner" "me" {}
# Parameters
data "coder_parameter" "repo" {
name = "repo"
display_name = "Repository"
description = "Select a repository to clone"
mutable = true
order = 1
option {
name = "Custom Development Environment"
description = "Full-stack development with all services"
value = "custom"
}
option {
name = "vercel/next.js"
description = "The React Framework"
value = "https://github.com/vercel/next.js"
}
option {
name = "Custom URL"
description = "Specify a custom repo URL below"
value = "custom-url"
}
}
data "coder_parameter" "custom_repo_url" {
name = "custom_repo_url"
display_name = "Custom Repository URL"
description = "Enter a custom repository URL"
default = ""
mutable = true
order = 2
}
data "coder_parameter" "enable_services" {
name = "enable_services"
display_name = "Enable Database Services"
description = "Enable PostgreSQL, Redis, Qdrant, and Docker Registry"
type = "bool"
default = "true"
mutable = true
order = 3
}
data "coder_parameter" "enable_ai_tools" {
name = "enable_ai_tools"
display_name = "Enable AI Assistant Tools"
description = "Install Claude Code and AI development tools"
type = "bool"
default = "true"
mutable = true
order = 4
}
data "coder_parameter" "enable_claude_code" {
name = "enable_claude_code"
display_name = "Enable Claude Code CLI"
description = "Install Claude Code command-line interface"
type = "bool"
default = "true"
mutable = true
order = 5
}
data "coder_parameter" "enable_cursor_support" {
name = "enable_cursor_support"
display_name = "Enable Cursor IDE Support"
description = "Install Cursor IDE configuration and settings"
type = "bool"
default = "true"
mutable = true
order = 6
}
data "coder_parameter" "enable_windsurf_support" {
name = "enable_windsurf_support"
display_name = "Enable Windsurf IDE Support"
description = "Install Windsurf IDE configuration and settings"
type = "bool"
default = "true"
mutable = true
order = 7
}
data "coder_parameter" "enable_jetbrains" {
name = "enable_jetbrains"
display_name = "Enable JetBrains Gateway"
description = "Enable JetBrains Gateway integration for remote development"
type = "bool"
default = "true"
mutable = true
order = 8
}
data "coder_parameter" "enable_jupyter" {
name = "enable_jupyter"
display_name = "Enable Jupyter Lab"
description = "Enable Jupyter Lab for data science and notebook development"
type = "bool"
default = "false"
mutable = true
order = 9
}
data "coder_parameter" "enable_pgadmin" {
name = "enable_pgadmin"
display_name = "Enable pgAdmin"
description = "Enable pgAdmin web interface for PostgreSQL management"
type = "bool"
default = "true"
mutable = true
order = 10
}
# Local Variables
locals {
# Container and workspace naming
container_name = "coder-${data.coder_workspace_owner.me.name}-${lower(data.coder_workspace.me.name)}"
workspace_id = data.coder_workspace.me.id
# Git configuration
git_author_name = coalesce(data.coder_workspace_owner.me.full_name, data.coder_workspace_owner.me.name)
git_author_email = data.coder_workspace_owner.me.email
# Repository URL logic
repo_url = (
data.coder_parameter.repo.value == "custom" ? "https://github.com/coder/envbuilder" :
data.coder_parameter.repo.value == "custom-url" ? data.coder_parameter.custom_repo_url.value :
data.coder_parameter.repo.value
)
# Development container image with all required tools
devcontainer_image = var.devcontainer_image
# Environment variables for the development container
dev_environment = {
# Git configuration
"GIT_AUTHOR_NAME" = local.git_author_name
"GIT_AUTHOR_EMAIL" = local.git_author_email
"GIT_COMMITTER_NAME" = local.git_author_name
"GIT_COMMITTER_EMAIL" = local.git_author_email
# Development tools
"NODE_VERSION" = var.node_version
"PYTHON_VERSION" = var.python_version
"RUST_VERSION" = "stable"
# Service URLs (when services are enabled)
"POSTGRES_URL" = data.coder_parameter.enable_services.value ? "postgresql://postgres:${var.postgres_password}@postgres-${local.workspace_id}:5432/postgres" : ""
"REDIS_URL" = data.coder_parameter.enable_services.value ? "redis://:${var.redis_password}@redis-${local.workspace_id}:6379" : ""
"QDRANT_URL" = data.coder_parameter.enable_services.value ? "http://qdrant-${local.workspace_id}:6333" : ""
# Development configuration
"EDITOR" = "code"
"PYTHONPATH" = "/workspaces"
"CARGO_HOME" = "/home/coder/.cargo"
"RUSTUP_HOME" = "/home/coder/.rustup"
}
# Legacy service URLs for backward compatibility
postgres_url = "postgresql://postgres:${var.postgres_password}@postgres-${local.workspace_id}:5432/postgres"
redis_url = "redis://:${var.redis_password}@redis-${local.workspace_id}:6379"
qdrant_url = "http://qdrant-${local.workspace_id}:6333"
}
# Docker Network
resource "docker_network" "workspace" {
name = "coder-${local.workspace_id}"
driver = "bridge"
labels {
label = "coder.workspace_id"
value = local.workspace_id
}
labels {
label = "coder.owner"
value = data.coder_workspace_owner.me.name
}
labels {
label = "coder.project"
value = var.project_name
}
}
# Workspace Volume
resource "docker_volume" "workspaces" {
name = "workspaces-${local.workspace_id}"
labels {
label = "coder.workspace_id"
value = local.workspace_id
}
labels {
label = "coder.owner"
value = data.coder_workspace_owner.me.name
}
labels {
label = "coder.type"
value = "workspace-data"
}
}
# Development Container Image
resource "docker_image" "devcontainer" {
name = local.devcontainer_image
keep_locally = true
}

118
tf/outputs.tf Normal file
View File

@@ -0,0 +1,118 @@
# =============================================================================
# Terraform Outputs
# Expose important values for reference and external use
# =============================================================================
# =============================================================================
# Workspace Information
# =============================================================================
output "workspace_id" {
description = "Unique identifier for the Coder workspace"
value = local.workspace_id
}
output "workspace_name" {
description = "Name of the Coder workspace"
value = data.coder_workspace.me.name
}
output "workspace_owner" {
description = "Owner of the Coder workspace"
value = data.coder_workspace_owner.me.name
}
output "container_name" {
description = "Name of the main development container"
value = local.container_name
}
# =============================================================================
# Git Configuration
# =============================================================================
output "git_author_name" {
description = "Git author name configured for the workspace"
value = local.git_author_name
}
output "git_author_email" {
description = "Git author email configured for the workspace"
value = local.git_author_email
sensitive = true
}
output "repository_url" {
description = "Repository URL that will be cloned"
value = local.repo_url
}
# =============================================================================
# Service Connection Information
# =============================================================================
output "postgres_connection_url" {
description = "PostgreSQL connection URL for applications"
value = data.coder_parameter.enable_services.value ? local.postgres_url : null
sensitive = true
}
output "redis_connection_url" {
description = "Redis connection URL for applications"
value = data.coder_parameter.enable_services.value ? local.redis_url : null
sensitive = true
}
output "qdrant_api_url" {
description = "Qdrant vector database API URL"
value = data.coder_parameter.enable_services.value ? local.qdrant_url : null
}
# =============================================================================
# Network Information
# =============================================================================
output "docker_network_name" {
description = "Name of the Docker network for service communication"
value = docker_network.workspace.name
}
output "workspace_volume_name" {
description = "Name of the persistent workspace volume"
value = docker_volume.workspaces.name
}
# =============================================================================
# Development Environment Information
# =============================================================================
output "development_tools" {
description = "Information about installed development tools"
value = {
node_version = var.node_version
python_version = var.python_version
container_image = local.devcontainer_image
}
}
output "service_status" {
description = "Status of optional development services"
value = {
services_enabled = data.coder_parameter.enable_services.value
ai_tools_enabled = data.coder_parameter.enable_ai_tools.value
pgadmin_enabled = var.enable_pgadmin
jupyter_enabled = var.enable_jupyter
}
}
# =============================================================================
# Access Information
# =============================================================================
output "external_ports" {
description = "External ports exposed for services"
value = data.coder_parameter.enable_services.value ? {
pgadmin = var.enable_pgadmin ? var.pgadmin_port : null
} : {}
}

72
tf/scripts.tf Normal file
View File

@@ -0,0 +1,72 @@
# =============================================================================
# Provisioning Scripts - AI Development Tools and Extensions
# Installation scripts for Cursor, Claude Code, Windsurf support
# =============================================================================
# =============================================================================
# Claude Code CLI Installation
# =============================================================================
resource "coder_script" "claude_code_setup" {
count = data.coder_parameter.enable_ai_tools.value && data.coder_parameter.enable_claude_code.value ? 1 : 0
agent_id = coder_agent.main.id
display_name = "Install Claude Code CLI"
icon = "/icon/ai.svg"
run_on_start = true
script = "bash /home/coder/resources/tf/scripts/claude-install.sh"
}
# =============================================================================
# Cursor IDE Support Setup
# =============================================================================
resource "coder_script" "cursor_setup" {
count = data.coder_parameter.enable_ai_tools.value && data.coder_parameter.enable_cursor_support.value ? 1 : 0
agent_id = coder_agent.main.id
display_name = "Configure Cursor IDE Support"
icon = "/icon/code.svg"
run_on_start = true
script = "bash /home/coder/resources/tf/scripts/cursor-setup.sh"
}
# =============================================================================
# Windsurf IDE Support Setup
# =============================================================================
resource "coder_script" "windsurf_setup" {
count = data.coder_parameter.enable_ai_tools.value && data.coder_parameter.enable_windsurf_support.value ? 1 : 0
agent_id = coder_agent.main.id
display_name = "Configure Windsurf IDE Support"
icon = "/icon/code.svg"
run_on_start = true
script = "bash /home/coder/resources/tf/scripts/windsurf-setup.sh"
}
# =============================================================================
# Development Tools and Extensions
# =============================================================================
resource "coder_script" "dev_extensions" {
agent_id = coder_agent.main.id
display_name = "Install Development Tools"
icon = "/icon/tools.svg"
run_on_start = true
script = "bash /home/coder/resources/tf/scripts/dev-tools.sh"
}
# =============================================================================
# Git Hooks and Metadata Capture Setup
# =============================================================================
resource "coder_script" "git_hooks_setup" {
agent_id = coder_agent.main.id
display_name = "Setup Git Hooks"
icon = "/icon/git.svg"
run_on_start = true
script = "bash /home/coder/resources/tf/scripts/git-hooks.sh"
}

328
tf/services.tf Normal file
View File

@@ -0,0 +1,328 @@
# =============================================================================
# Service Containers - Database and Development Services
# PostgreSQL, Redis, Qdrant, Docker Registry
# =============================================================================
# =============================================================================
# PostgreSQL Database Service
# =============================================================================
# PostgreSQL data volume for persistence
resource "docker_volume" "postgres_data" {
count = data.coder_parameter.enable_services.value ? 1 : 0
name = "postgres-data-${local.workspace_id}"
labels {
label = "coder.service"
value = "postgres"
}
labels {
label = "coder.workspace_id"
value = local.workspace_id
}
}
# PostgreSQL container
resource "docker_container" "postgres" {
count = data.coder_parameter.enable_services.value ? 1 : 0
image = "postgres:${var.postgres_version}-alpine"
name = "postgres-${local.workspace_id}"
# PostgreSQL configuration
env = [
"POSTGRES_DB=postgres",
"POSTGRES_USER=postgres",
"POSTGRES_PASSWORD=${var.postgres_password}",
"POSTGRES_INITDB_ARGS=--auth-local=trust --auth-host=md5",
"POSTGRES_SHARED_PRELOAD_LIBRARIES=pg_stat_statements",
"POSTGRES_MAX_CONNECTIONS=${var.postgres_max_connections}"
]
# Network configuration - internal only, accessible via Coder port forwarding
networks_advanced {
name = docker_network.workspace.name
}
# Data persistence
volumes {
volume_name = docker_volume.postgres_data[0].name
container_path = "/var/lib/postgresql/data"
}
# Health check
healthcheck {
test = ["CMD-SHELL", "pg_isready -U postgres"]
interval = "15s"
timeout = "5s"
retries = 5
}
restart = "unless-stopped"
labels {
label = "coder.service"
value = "postgres"
}
labels {
label = "coder.workspace_id"
value = local.workspace_id
}
}
# =============================================================================
# Redis Cache Service
# =============================================================================
# Redis data volume for persistence
resource "docker_volume" "redis_data" {
count = data.coder_parameter.enable_services.value ? 1 : 0
name = "redis-data-${local.workspace_id}"
labels {
label = "coder.service"
value = "redis"
}
labels {
label = "coder.workspace_id"
value = local.workspace_id
}
}
# Redis container
resource "docker_container" "redis" {
count = data.coder_parameter.enable_services.value ? 1 : 0
image = "redis:${var.redis_version}-alpine"
name = "redis-${local.workspace_id}"
# Redis configuration with authentication
command = [
"redis-server",
"--requirepass", var.redis_password,
"--appendonly", "yes",
"--appendfsync", "everysec",
"--maxmemory", var.redis_max_memory,
"--maxmemory-policy", "allkeys-lru"
]
networks_advanced {
name = docker_network.workspace.name
}
# Data persistence
volumes {
volume_name = docker_volume.redis_data[0].name
container_path = "/data"
}
# Health check with authentication
healthcheck {
test = ["CMD", "redis-cli", "-a", var.redis_password, "ping"]
interval = "15s"
timeout = "3s"
retries = 5
}
restart = "unless-stopped"
labels {
label = "coder.service"
value = "redis"
}
labels {
label = "coder.workspace_id"
value = local.workspace_id
}
}
# =============================================================================
# Qdrant Vector Database Service
# =============================================================================
# Qdrant data volume for persistence
resource "docker_volume" "qdrant_data" {
count = data.coder_parameter.enable_services.value ? 1 : 0
name = "qdrant-data-${local.workspace_id}"
labels {
label = "coder.service"
value = "qdrant"
}
labels {
label = "coder.workspace_id"
value = local.workspace_id
}
}
# Qdrant container
resource "docker_container" "qdrant" {
count = data.coder_parameter.enable_services.value ? 1 : 0
image = "qdrant/qdrant:${var.qdrant_version}"
name = "qdrant-${local.workspace_id}"
# Qdrant configuration
env = [
"QDRANT__SERVICE__HTTP_PORT=6333",
"QDRANT__SERVICE__GRPC_PORT=6334",
"QDRANT__SERVICE__HOST=0.0.0.0",
"QDRANT__LOG_LEVEL=INFO"
]
networks_advanced {
name = docker_network.workspace.name
}
# Data persistence
volumes {
volume_name = docker_volume.qdrant_data[0].name
container_path = "/qdrant/storage"
}
# Health check using simple file existence check (Qdrant creates lock files when running)
healthcheck {
test = ["CMD-SHELL", "test -d /qdrant/storage && test -f /qdrant/qdrant || exit 1"]
interval = "20s"
timeout = "5s"
retries = 5
}
restart = "unless-stopped"
labels {
label = "coder.service"
value = "qdrant"
}
labels {
label = "coder.workspace_id"
value = local.workspace_id
}
}
# =============================================================================
# pgAdmin for PostgreSQL Management (Optional)
# =============================================================================
# pgAdmin data volume
resource "docker_volume" "pgadmin_data" {
count = data.coder_parameter.enable_services.value && data.coder_parameter.enable_pgadmin.value ? 1 : 0
name = "pgadmin-data-${local.workspace_id}"
labels {
label = "coder.service"
value = "pgadmin"
}
labels {
label = "coder.workspace_id"
value = local.workspace_id
}
}
# pgAdmin container
resource "docker_container" "pgadmin" {
count = data.coder_parameter.enable_services.value && data.coder_parameter.enable_pgadmin.value ? 1 : 0
image = "dpage/pgadmin4:latest"
name = "pgadmin-${local.workspace_id}"
# pgAdmin configuration
env = [
"PGADMIN_DEFAULT_EMAIL=${var.pgadmin_email}",
"PGADMIN_DEFAULT_PASSWORD=${var.pgadmin_password}",
"PGADMIN_CONFIG_SERVER_MODE=False",
"PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED=False",
"PGADMIN_LISTEN_PORT=80"
]
networks_advanced {
name = docker_network.workspace.name
}
# Data persistence
volumes {
volume_name = docker_volume.pgadmin_data[0].name
container_path = "/var/lib/pgadmin"
}
# Health check for pgAdmin web interface
healthcheck {
test = ["CMD-SHELL", "curl -f http://localhost:80/misc/ping || wget --no-verbose --tries=1 --spider http://localhost:80/misc/ping || exit 1"]
interval = "30s"
timeout = "10s"
retries = 3
start_period = "60s"
}
restart = "unless-stopped"
labels {
label = "coder.service"
value = "pgadmin"
}
labels {
label = "coder.workspace_id"
value = local.workspace_id
}
}
# =============================================================================
# Jupyter Lab Service (Optional for Data Science)
# =============================================================================
# Jupyter data volume
resource "docker_volume" "jupyter_data" {
count = data.coder_parameter.enable_jupyter.value ? 1 : 0
name = "jupyter-data-${local.workspace_id}"
labels {
label = "coder.service"
value = "jupyter"
}
labels {
label = "coder.workspace_id"
value = local.workspace_id
}
}
# Jupyter Lab container
resource "docker_container" "jupyter" {
count = data.coder_parameter.enable_jupyter.value ? 1 : 0
image = "jupyter/scipy-notebook:latest"
name = "jupyter-${local.workspace_id}"
# Jupyter configuration
env = [
"JUPYTER_ENABLE_LAB=yes",
"JUPYTER_TOKEN=",
"RESTARTABLE=yes",
"JUPYTER_PORT=8888"
]
networks_advanced {
name = docker_network.workspace.name
}
# Port accessible within workspace network - no host exposure needed
# Data persistence
volumes {
volume_name = docker_volume.jupyter_data[0].name
container_path = "/home/jovyan/work"
}
# Share workspace volume
volumes {
volume_name = docker_volume.workspaces.name
container_path = "/home/jovyan/workspaces"
read_only = false
}
restart = "unless-stopped"
labels {
label = "coder.service"
value = "jupyter"
}
labels {
label = "coder.workspace_id"
value = local.workspace_id
}
}

105
tf/terraform.tfvars Normal file
View File

@@ -0,0 +1,105 @@
# =============================================================================
# Terraform Variables Configuration
# Variable assignments for the development environment
# =============================================================================
# =============================================================================
# Project Configuration
# =============================================================================
project_name = "dev-environment"
environment = "dev"
# =============================================================================
# Docker Configuration
# =============================================================================
docker_socket = ""
devcontainer_image = "mcr.microsoft.com/devcontainers/universal:2-linux"
# =============================================================================
# Development Tool Versions
# =============================================================================
node_version = "20"
python_version = "3.12"
postgres_version = "17"
redis_version = "7"
qdrant_version = "latest"
# =============================================================================
# Service Configuration
# =============================================================================
postgres_password = "devpassword"
redis_password = "devpassword"
postgres_max_connections = 100
redis_max_memory = "512mb"
# =============================================================================
# Network Configuration
# =============================================================================
pgadmin_port = 5050
pgadmin_email = "admin@dev.local"
pgadmin_password = "adminpassword"
# =============================================================================
# Development Packages
# =============================================================================
npm_packages = [
"repomix",
"create-next-app",
"nodemon",
"concurrently",
"@types/node",
"typescript",
"eslint",
"prettier"
]
python_packages = [
"fastapi",
"uvicorn",
"requests",
"pandas",
"numpy",
"psycopg2-binary",
"redis",
"qdrant-client",
"python-dotenv"
]
system_packages = [
"make",
"tree",
"jq",
"curl",
"wget",
"unzip",
"build-essential"
]
# =============================================================================
# AI Development Tools
# =============================================================================
install_claude_code = true
install_cursor_support = true
install_windsurf_support = true
# =============================================================================
# Performance Configuration
# =============================================================================
workspace_memory_limit = 16384
workspace_cpu_limit = 4
# =============================================================================
# Feature Toggles
# =============================================================================
enable_pgadmin = true
enable_monitoring = true
enable_jupyter = false
# =============================================================================
# Common Tags
# =============================================================================
common_tags = {
Environment = "development"
ManagedBy = "terraform"
Purpose = "remote-development"
}

322
tf/variables.tf Normal file
View File

@@ -0,0 +1,322 @@
# =============================================================================
# Variable Definitions
# Modular Development Environment Configuration
# =============================================================================
# =============================================================================
# Project Configuration
# =============================================================================
variable "project_name" {
description = "Name of the project for resource tagging and identification"
type = string
default = "dev-environment"
validation {
condition = can(regex("^[a-z0-9-]+$", var.project_name))
error_message = "Project name must contain only lowercase letters, numbers, and hyphens."
}
}
variable "environment" {
description = "Environment designation (dev, staging, prod)"
type = string
default = "dev"
validation {
condition = contains(["dev", "staging", "prod"], var.environment)
error_message = "Environment must be one of: dev, staging, prod."
}
}
# =============================================================================
# Docker Configuration
# =============================================================================
variable "docker_socket" {
description = "Docker daemon socket URI (empty for default)"
type = string
default = ""
}
variable "devcontainer_image" {
description = "Development container image with all required tools pre-installed"
type = string
default = "mcr.microsoft.com/devcontainers/universal:2-linux"
}
# =============================================================================
# Development Tool Versions
# =============================================================================
variable "node_version" {
description = "Node.js version to install"
type = string
default = "20"
validation {
condition = contains(["18", "20", "21"], var.node_version)
error_message = "Node.js version must be one of: 18, 20, 21."
}
}
variable "python_version" {
description = "Python version to install"
type = string
default = "3.12"
validation {
condition = contains(["3.10", "3.11", "3.12"], var.python_version)
error_message = "Python version must be 3.10, 3.11, or 3.12."
}
}
variable "postgres_version" {
description = "PostgreSQL version"
type = string
default = "17"
validation {
condition = contains(["13", "14", "15", "16", "17"], var.postgres_version)
error_message = "PostgreSQL version must be one of: 13, 14, 15, 16, 17."
}
}
variable "redis_version" {
description = "Redis version"
type = string
default = "7"
validation {
condition = contains(["6", "7"], var.redis_version)
error_message = "Redis version must be 6 or 7."
}
}
variable "qdrant_version" {
description = "Qdrant vector database version"
type = string
default = "latest"
}
# =============================================================================
# Service Configuration
# =============================================================================
variable "postgres_password" {
description = "PostgreSQL postgres user password"
type = string
default = "devpassword"
sensitive = true
validation {
condition = length(var.postgres_password) >= 8
error_message = "PostgreSQL password must be at least 8 characters long."
}
}
variable "redis_password" {
description = "Redis authentication password"
type = string
default = "devpassword"
sensitive = true
validation {
condition = length(var.redis_password) >= 8
error_message = "Redis password must be at least 8 characters long."
}
}
variable "postgres_max_connections" {
description = "Maximum PostgreSQL connections"
type = number
default = 100
validation {
condition = var.postgres_max_connections >= 20 && var.postgres_max_connections <= 1000
error_message = "PostgreSQL max connections must be between 20 and 1000."
}
}
variable "redis_max_memory" {
description = "Redis maximum memory (e.g., '256mb', '1gb')"
type = string
default = "512mb"
validation {
condition = can(regex("^[0-9]+[kmg]b$", var.redis_max_memory))
error_message = "Redis max memory must be in format like '256mb' or '1gb'."
}
}
# =============================================================================
# Network Configuration
# =============================================================================
variable "pgadmin_port" {
description = "pgAdmin web interface port"
type = number
default = 5050
validation {
condition = var.pgadmin_port >= 1024 && var.pgadmin_port <= 65535
error_message = "pgAdmin port must be between 1024 and 65535."
}
}
variable "pgadmin_email" {
description = "pgAdmin login email"
type = string
default = "admin@example.com"
validation {
condition = can(regex("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$", var.pgadmin_email))
error_message = "pgAdmin email must be a valid email address."
}
}
variable "pgadmin_password" {
description = "pgAdmin login password"
type = string
default = "adminpassword"
sensitive = true
validation {
condition = length(var.pgadmin_password) >= 8
error_message = "pgAdmin password must be at least 8 characters long."
}
}
# =============================================================================
# Development Packages
# =============================================================================
variable "npm_packages" {
description = "Global npm packages to install"
type = list(string)
default = [
"repomix", # Repository packaging tool
"create-next-app", # Next.js app generator
"nodemon", # Development server auto-reload
"concurrently", # Run multiple commands
"@types/node", # Node.js TypeScript types
"typescript", # TypeScript compiler
"eslint", # JavaScript linter
"prettier" # Code formatter
]
}
variable "python_packages" {
description = "Python packages to install via uv"
type = list(string)
default = [
"fastapi", # Modern web framework
"uvicorn", # ASGI server
"requests", # HTTP library
"pandas", # Data manipulation
"numpy", # Numerical computing
"psycopg2-binary", # PostgreSQL adapter
"redis", # Redis client
"qdrant-client", # Qdrant vector database client
"python-dotenv" # Environment variable loading
]
}
variable "system_packages" {
description = "Additional system packages to install"
type = list(string)
default = [
"make", # Build tool
"tree", # Directory tree viewer
"jq", # JSON processor
"curl", # HTTP client
"wget", # File downloader
"unzip", # Archive extractor
"build-essential" # Compilation tools
]
}
# =============================================================================
# AI Development Tools
# =============================================================================
variable "install_claude_code" {
description = "Install Claude Code CLI for AI assistance"
type = bool
default = true
}
variable "install_cursor_support" {
description = "Install Cursor IDE support and extensions"
type = bool
default = true
}
variable "install_windsurf_support" {
description = "Install Windsurf IDE support"
type = bool
default = false
}
# =============================================================================
# Performance Configuration
# =============================================================================
variable "workspace_memory_limit" {
description = "Memory limit for workspace container (MB)"
type = number
default = 8192
validation {
condition = var.workspace_memory_limit >= 2048 && var.workspace_memory_limit <= 32768
error_message = "Workspace memory limit must be between 2048MB (2GB) and 32768MB (32GB)."
}
}
variable "workspace_cpu_limit" {
description = "CPU limit for workspace container (cores)"
type = number
default = 4
validation {
condition = var.workspace_cpu_limit >= 1 && var.workspace_cpu_limit <= 16
error_message = "Workspace CPU limit must be between 1 and 16 cores."
}
}
# =============================================================================
# Feature Toggles
# =============================================================================
variable "enable_pgadmin" {
description = "Enable pgAdmin web interface (resource intensive)"
type = bool
default = true
}
variable "enable_monitoring" {
description = "Enable container monitoring and metrics collection"
type = bool
default = false
}
variable "enable_jupyter" {
description = "Enable Jupyter Lab for data science workflows"
type = bool
default = false
}
# =============================================================================
# Common Tags
# =============================================================================
variable "common_tags" {
description = "Common tags to apply to all resources"
type = map(string)
default = {
Environment = "development"
ManagedBy = "terraform"
Purpose = "remote-development"
}
}

224
tf/workspace.tf Normal file
View File

@@ -0,0 +1,224 @@
# =============================================================================
# Development Workspace Container
# Main development environment with all required tools
# =============================================================================
# =============================================================================
# Coder Agent - Workspace Management
# =============================================================================
resource "coder_agent" "main" {
arch = data.coder_provisioner.me.arch
os = "linux"
dir = "/workspaces"
# Environment variables for development
env = {
"GIT_AUTHOR_NAME" = local.git_author_name
"GIT_AUTHOR_EMAIL" = local.git_author_email
"GIT_COMMITTER_NAME" = local.git_author_name
"GIT_COMMITTER_EMAIL" = local.git_author_email
"NODE_VERSION" = var.node_version
"PYTHON_VERSION" = var.python_version
"PATH" = "$PATH:/home/coder/.cargo/bin:/home/coder/.local/bin:/usr/local/bin"
"HOME" = "/home/coder"
"USER" = "coder"
# Service URLs for development
"POSTGRES_URL" = data.coder_parameter.enable_services.value ? "postgresql://postgres:${var.postgres_password}@postgres-${local.workspace_id}:5432/postgres" : ""
"REDIS_URL" = data.coder_parameter.enable_services.value ? "redis://:${var.redis_password}@redis-${local.workspace_id}:6379" : ""
"QDRANT_URL" = data.coder_parameter.enable_services.value ? "http://qdrant-${local.workspace_id}:6333" : ""
# Additional environment variables for scripts
"ENABLE_SERVICES" = tostring(data.coder_parameter.enable_services.value)
}
# Reference bind-mounted startup script plus service port forwarding
startup_script = <<-EOT
bash /home/coder/resources/tf/scripts/workspace-setup.sh
# Register JetBrains Gateway backend location if enabled
if [ "${data.coder_parameter.enable_jetbrains.value}" = "true" ] && [ -d ~/JetBrains ]; then
~/JetBrains/*/bin/remote-dev-server.sh registerBackendLocationForGateway 2>/dev/null || echo "JetBrains Gateway registration skipped"
fi
EOT
# Performance and resource monitoring
metadata {
display_name = "CPU Usage"
key = "cpu_usage"
script = "coder stat cpu"
interval = 60
timeout = 10
}
metadata {
display_name = "RAM Usage"
key = "ram_usage"
script = "coder stat mem"
interval = 60
timeout = 10
}
metadata {
display_name = "Disk Usage"
key = "disk_usage"
script = "coder stat disk --path /workspaces"
interval = 300
timeout = 10
}
metadata {
display_name = "Git Branch"
key = "git_branch"
script = "cd /workspaces && git branch --show-current 2>/dev/null || echo 'no-repo'"
interval = 300
timeout = 5
}
}
# =============================================================================
# Main Development Container
# =============================================================================
resource "docker_container" "workspace" {
count = data.coder_workspace.me.start_count
image = docker_image.devcontainer.image_id
name = local.container_name
hostname = data.coder_workspace.me.name
# Container resource limits
memory = var.workspace_memory_limit * 1024 * 1024 # Convert MB to bytes
# Environment variables
env = [
"GIT_AUTHOR_NAME=${local.git_author_name}",
"GIT_AUTHOR_EMAIL=${local.git_author_email}",
"GIT_COMMITTER_NAME=${local.git_author_name}",
"GIT_COMMITTER_EMAIL=${local.git_author_email}",
"NODE_VERSION=${var.node_version}",
"PYTHON_VERSION=${var.python_version}",
"CODER_AGENT_TOKEN=${coder_agent.main.token}"
]
# Network configuration
networks_advanced {
name = docker_network.workspace.name
}
# Host networking for Docker-in-Docker and reverse proxy support
host {
host = "host.docker.internal"
ip = "host-gateway"
}
# No port mappings needed - reverse proxy will handle routing
# All services run within the isolated workspace network
# Coder's port forwarding and apps will provide access via reverse proxy
# Volume mounts
volumes {
container_path = "/workspaces"
volume_name = docker_volume.workspaces.name
read_only = false
}
# Mount the existing coder-home volume for user data persistence
volumes {
container_path = "/home/coder"
volume_name = "bwk8ckcok8o84cc0o4os4sso_coder-home"
read_only = false
}
# Bind mount code-tools directory for live script updates
volumes {
host_path = "/home/trav/code-tools"
container_path = "/home/coder/resources"
read_only = true
}
# Docker socket for Docker-in-Docker
volumes {
host_path = "/var/run/docker.sock"
container_path = "/var/run/docker.sock"
}
# Working directory
working_dir = "/workspaces"
# Keep container running
command = ["/bin/bash", "-c", "${coder_agent.main.init_script} && sleep infinity"]
# Container labels for management
labels {
label = "coder.owner"
value = data.coder_workspace_owner.me.name
}
labels {
label = "coder.workspace_id"
value = local.workspace_id
}
labels {
label = "coder.project"
value = var.project_name
}
# Dependencies
depends_on = [
docker_network.workspace,
docker_volume.workspaces,
docker_image.devcontainer
]
}
# =============================================================================
# JetBrains Gateway Integration
# =============================================================================
module "jetbrains_gateway" {
count = data.coder_parameter.enable_jetbrains.value && data.coder_workspace.me.start_count > 0 ? 1 : 0
source = "registry.coder.com/modules/jetbrains-gateway/coder"
version = "1.0.29"
agent_id = coder_agent.main.id
folder = "/workspaces"
jetbrains_ides = ["IU", "WS", "PY", "GO"]
default = "IU"
latest = false
jetbrains_ide_versions = {
"IU" = {
build_number = "251.25410.129"
version = "2025.1"
}
"WS" = {
build_number = "251.25410.129"
version = "2025.1"
}
"PY" = {
build_number = "251.25410.129"
version = "2025.1"
}
"GO" = {
build_number = "251.25410.129"
version = "2025.1"
}
"CL" = {
build_number = "251.25410.129"
version = "2025.1"
}
"PS" = {
build_number = "251.25410.129"
version = "2025.1"
}
"RR" = {
build_number = "251.25410.129"
version = "2025.1"
}
"RM" = {
build_number = "251.25410.129"
version = "2025.1"
}
"RD" = {
build_number = "251.25410.129"
version = "2025.1"
}
}
}

BIN
ui.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB