Compare commits
28 Commits
v1.0.20
...
optionalit
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
71d648c7c1 | ||
|
|
2d644da2bc | ||
|
|
e9238f107a | ||
|
|
e94f70a286 | ||
|
|
7654140330 | ||
|
|
bc6490f0d3 | ||
|
|
482ed84399 | ||
|
|
32b69016a0 | ||
|
|
6d2739131a | ||
|
|
cbd06b1135 | ||
|
|
675c82367a | ||
|
|
bf697e1fa4 | ||
|
|
b345e62ac1 | ||
|
|
6597a2d547 | ||
|
|
5101c27c83 | ||
|
|
90bfbfdc40 | ||
|
|
57d96ca27f | ||
|
|
f5ab7995d1 | ||
|
|
528a8a9fea | ||
|
|
87854707bc | ||
|
|
b53554b4e4 | ||
|
|
ce5a5b383a | ||
|
|
1b147ae90d | ||
|
|
7992d9d265 | ||
|
|
20d97a25dd | ||
|
|
8e0dfcd534 | ||
|
|
9752bf89a6 | ||
|
|
48c81c9ff4 |
203
.github/scripts/check.sh
vendored
Executable file
203
.github/scripts/check.sh
vendored
Executable file
@@ -0,0 +1,203 @@
|
||||
#!/usr/bin/env bash
|
||||
set -o pipefail
|
||||
set -u
|
||||
|
||||
VERBOSE="${VERBOSE:-0}"
|
||||
if [[ "${VERBOSE}" -ne "0" ]]; then
|
||||
set -x
|
||||
fi
|
||||
|
||||
# List of required environment variables
|
||||
required_vars=(
|
||||
"INSTATUS_API_KEY"
|
||||
"INSTATUS_PAGE_ID"
|
||||
"INSTATUS_COMPONENT_ID"
|
||||
"VERCEL_API_KEY"
|
||||
)
|
||||
|
||||
# Check if each required variable is set
|
||||
for var in "${required_vars[@]}"; do
|
||||
if [[ -z "${!var:-}" ]]; then
|
||||
echo "Error: Environment variable '$var' is not set."
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
REGISTRY_BASE_URL="${REGISTRY_BASE_URL:-https://registry.coder.com}"
|
||||
|
||||
status=0
|
||||
declare -a modules=()
|
||||
declare -a failures=()
|
||||
|
||||
# Collect all module directories containing a main.tf file
|
||||
for path in $(find . -maxdepth 2 -not -path '*/.*' -type f -name main.tf | cut -d '/' -f 2 | sort -u); do
|
||||
modules+=("${path}")
|
||||
done
|
||||
|
||||
echo "Checking modules: ${modules[*]}"
|
||||
|
||||
# Function to update the component status on Instatus
|
||||
update_component_status() {
|
||||
local component_status=$1
|
||||
# see https://instatus.com/help/api/components
|
||||
(curl -X PUT "https://api.instatus.com/v1/$INSTATUS_PAGE_ID/components/$INSTATUS_COMPONENT_ID" \
|
||||
-H "Authorization: Bearer $INSTATUS_API_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"status\": \"$component_status\"}")
|
||||
}
|
||||
|
||||
# Function to create an incident
|
||||
create_incident() {
|
||||
local incident_name="Degraded Service"
|
||||
local message="The following modules are experiencing issues:\n"
|
||||
for i in "${!failures[@]}"; do
|
||||
message+="$((i + 1)). ${failures[$i]}\n"
|
||||
done
|
||||
|
||||
component_status="PARTIALOUTAGE"
|
||||
if (( ${#failures[@]} == ${#modules[@]} )); then
|
||||
component_status="MAJOROUTAGE"
|
||||
fi
|
||||
# see https://instatus.com/help/api/incidents
|
||||
incident_id=$(curl -s -X POST "https://api.instatus.com/v1/$INSTATUS_PAGE_ID/incidents" \
|
||||
-H "Authorization: Bearer $INSTATUS_API_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{
|
||||
\"name\": \"$incident_name\",
|
||||
\"message\": \"$message\",
|
||||
\"components\": [\"$INSTATUS_COMPONENT_ID\"],
|
||||
\"status\": \"INVESTIGATING\",
|
||||
\"notify\": true,
|
||||
\"statuses\": [
|
||||
{
|
||||
\"id\": \"$INSTATUS_COMPONENT_ID\",
|
||||
\"status\": \"PARTIALOUTAGE\"
|
||||
}
|
||||
]
|
||||
}" | jq -r '.id')
|
||||
|
||||
echo "Created incident with ID: $incident_id"
|
||||
}
|
||||
|
||||
# Function to check for existing unresolved incidents
|
||||
check_existing_incident() {
|
||||
# Fetch the latest incidents with status not equal to "RESOLVED"
|
||||
local unresolved_incidents=$(curl -s -X GET "https://api.instatus.com/v1/$INSTATUS_PAGE_ID/incidents" \
|
||||
-H "Authorization: Bearer $INSTATUS_API_KEY" \
|
||||
-H "Content-Type: application/json" | jq -r '.incidents[] | select(.status != "RESOLVED") | .id')
|
||||
|
||||
if [[ -n "$unresolved_incidents" ]]; then
|
||||
echo "Unresolved incidents found: $unresolved_incidents"
|
||||
return 0 # Indicate that there are unresolved incidents
|
||||
else
|
||||
echo "No unresolved incidents found."
|
||||
return 1 # Indicate that no unresolved incidents exist
|
||||
fi
|
||||
}
|
||||
|
||||
force_redeploy_registry () {
|
||||
# These are not secret values; safe to just expose directly in script
|
||||
local VERCEL_TEAM_SLUG="codercom"
|
||||
local VERCEL_TEAM_ID="team_tGkWfhEGGelkkqUUm9nXq17r"
|
||||
local VERCEL_APP="registry"
|
||||
|
||||
local latest_res
|
||||
latest_res=$(curl "https://api.vercel.com/v6/deployments?app=$VERCEL_APP&limit=1&slug=$VERCEL_TEAM_SLUG&teamId=$VERCEL_TEAM_ID&target=production&state=BUILDING,INITIALIZING,QUEUED,READY" \
|
||||
--fail \
|
||||
--silent \
|
||||
--header "Authorization: Bearer $VERCEL_API_KEY" \
|
||||
--header "Content-Type: application/json"
|
||||
)
|
||||
|
||||
# If we have zero deployments, something is VERY wrong. Make the whole
|
||||
# script exit with a non-zero status code
|
||||
local latest_id
|
||||
latest_id=$(echo "${latest_res}" | jq -r '.deployments[0].uid')
|
||||
if [[ "${latest_id}" = "null" ]]; then
|
||||
echo "Unable to pull any previous deployments for redeployment"
|
||||
echo "Please redeploy the latest deployment manually in Vercel."
|
||||
echo "https://vercel.com/codercom/registry/deployments"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
local latest_date_ts_seconds
|
||||
latest_date_ts_seconds=$(echo "${latest_res}" | jq -r '.deployments[0].createdAt/1000|floor')
|
||||
local current_date_ts_seconds
|
||||
current_date_ts_seconds="$(date +%s)"
|
||||
local max_redeploy_interval_seconds=7200 # 2 hours
|
||||
if (( current_date_ts_seconds - latest_date_ts_seconds < max_redeploy_interval_seconds )); then
|
||||
echo "The registry was deployed less than 2 hours ago."
|
||||
echo "Not automatically re-deploying the regitstry."
|
||||
echo "A human reading this message should decide if a redeployment is necessary."
|
||||
echo "Please check the Vercel dashboard for more information."
|
||||
echo "https://vercel.com/codercom/registry/deployments"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
local latest_deployment_state
|
||||
latest_deployment_state="$(echo "${latest_res}" | jq -r '.deployments[0].state')"
|
||||
if [[ "${latest_deployment_state}" != "READY" ]]; then
|
||||
echo "Last deployment was not in READY state. Skipping redeployment."
|
||||
echo "A human reading this message should decide if a redeployment is necessary."
|
||||
echo "Please check the Vercel dashboard for more information."
|
||||
echo "https://vercel.com/codercom/registry/deployments"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "============================================================="
|
||||
echo "!!! Redeploying registry with deployment ID: ${latest_id} !!!"
|
||||
echo "============================================================="
|
||||
|
||||
if ! curl -X POST "https://api.vercel.com/v13/deployments?forceNew=1&skipAutoDetectionConfirmation=1&slug=$VERCEL_TEAM_SLUG&teamId=$VERCEL_TEAM_ID" \
|
||||
--fail \
|
||||
--header "Authorization: Bearer $VERCEL_API_KEY" \
|
||||
--header "Content-Type: application/json" \
|
||||
--data-raw "{ \"deploymentId\": \"${latest_id}\", \"name\": \"${VERCEL_APP}\", \"target\": \"production\" }"; then
|
||||
echo "DEPLOYMENT FAILED! Please check the Vercel dashboard for more information."
|
||||
echo "https://vercel.com/codercom/registry/deployments"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Check each module's accessibility
|
||||
for module in "${modules[@]}"; do
|
||||
# Trim leading/trailing whitespace from module name
|
||||
module=$(echo "${module}" | xargs)
|
||||
url="${REGISTRY_BASE_URL}/modules/${module}"
|
||||
printf "=== Checking module %s at %s\n" "${module}" "${url}"
|
||||
status_code=$(curl --output /dev/null --head --silent --fail --location "${url}" --retry 3 --write-out "%{http_code}")
|
||||
if (( status_code != 200 )); then
|
||||
printf "==> FAIL(%s)\n" "${status_code}"
|
||||
status=1
|
||||
failures+=("${module}")
|
||||
else
|
||||
printf "==> OK(%s)\n" "${status_code}"
|
||||
fi
|
||||
done
|
||||
|
||||
# Determine overall status and update Instatus component
|
||||
if (( status == 0 )); then
|
||||
echo "All modules are operational."
|
||||
# set to
|
||||
update_component_status "OPERATIONAL"
|
||||
else
|
||||
echo "The following modules have issues: ${failures[*]}"
|
||||
# check if all modules are down
|
||||
if (( ${#failures[@]} == ${#modules[@]} )); then
|
||||
update_component_status "MAJOROUTAGE"
|
||||
else
|
||||
update_component_status "PARTIALOUTAGE"
|
||||
fi
|
||||
|
||||
# Check if there is an existing incident before creating a new one
|
||||
if ! check_existing_incident; then
|
||||
create_incident
|
||||
fi
|
||||
|
||||
# If a module is down, force a reployment to try getting things back online
|
||||
# ASAP
|
||||
# EDIT: registry.coder.com is no longer hosted on vercel
|
||||
#force_redeploy_registry
|
||||
fi
|
||||
|
||||
exit "${status}"
|
||||
23
.github/workflows/check.yaml
vendored
Normal file
23
.github/workflows/check.yaml
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
name: Health
|
||||
# Check modules health on registry.coder.com
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0,15,30,45 * * * *" # Runs every 15 minutes
|
||||
workflow_dispatch: # Allows manual triggering of the workflow if needed
|
||||
|
||||
jobs:
|
||||
run-script:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Run check.sh
|
||||
run: |
|
||||
./.github/scripts/check.sh
|
||||
env:
|
||||
INSTATUS_API_KEY: ${{ secrets.INSTATUS_API_KEY }}
|
||||
INSTATUS_PAGE_ID: ${{ secrets.INSTATUS_PAGE_ID }}
|
||||
INSTATUS_COMPONENT_ID: ${{ secrets.INSTATUS_COMPONENT_ID }}
|
||||
VERCEL_API_KEY: ${{ secrets.VERCEL_API_KEY }}
|
||||
37
.github/workflows/deploy-registry.yaml
vendored
Normal file
37
.github/workflows/deploy-registry.yaml
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
name: deploy-registry
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
# Set id-token permission for gcloud
|
||||
# Adding a comment because retriggering the build manually hung? I am the lord of devops and you will bend?
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Authenticate to Google Cloud
|
||||
uses: google-github-actions/auth@6fc4af4b145ae7821d527454aa9bd537d1f2dc5f
|
||||
with:
|
||||
workload_identity_provider: projects/309789351055/locations/global/workloadIdentityPools/github-actions/providers/github
|
||||
service_account: registry-v2-github@coder-registry-1.iam.gserviceaccount.com
|
||||
|
||||
- name: Set up Google Cloud SDK
|
||||
uses: google-github-actions/setup-gcloud@6189d56e4096ee891640bb02ac264be376592d6a
|
||||
|
||||
# For the time being, let's have the first couple merges to main in modules deploy a new version
|
||||
# to *dev*. Once we review and make sure everything's working, we can deploy a new version to *main*.
|
||||
# Maybe in the future we could automate this based on the result of E2E tests.
|
||||
- name: Deploy to dev.registry.coder.com
|
||||
run: |
|
||||
gcloud builds triggers run 29818181-126d-4f8a-a937-f228b27d3d34 --branch dev
|
||||
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -1,4 +1,7 @@
|
||||
.terraform*
|
||||
node_modules
|
||||
*.tfstate
|
||||
*.tfstate.lock.info
|
||||
*.tfstate.lock.info
|
||||
|
||||
# Ignore generated credentials from google-github-actions/auth
|
||||
gha-creds-*.json
|
||||
1
.icons/dcv.svg
Normal file
1
.icons/dcv.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg width="82" height="80" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" overflow="hidden"><g transform="translate(-550 -124)"><g><g><g><g><path d="M551 124 631 124 631 204 551 204Z" fill="#ED7100" fill-rule="evenodd" fill-opacity="1"/><path d="M612.069 162.386C607.327 165.345 600.717 168.353 593.46 170.855 588.339 172.62 583.33 173.978 578.865 174.838 582.727 184.68 589.944 191.037 596.977 189.853 603.514 188.75 608.387 181.093 609.1 170.801L611.096 170.939C610.304 182.347 604.893 190.545 597.309 191.825 596.648 191.937 595.984 191.991 595.323 191.991 587.945 191.991 580.718 185.209 576.871 175.194 575.733 175.38 574.625 175.542 573.584 175.653 572.173 175.803 570.901 175.879 569.769 175.879 565.95 175.879 563.726 175.025 563.141 173.328 562.414 171.218 564.496 168.566 569.328 165.445L570.414 167.125C565.704 170.167 564.814 172.046 565.032 172.677 565.263 173.348 567.279 174.313 573.372 173.665 574.267 173.57 575.216 173.433 576.187 173.28 575.537 171.297 575.014 169.205 574.647 167.028 573.406 159.673 574.056 152.438 576.48 146.654 578.969 140.715 583.031 136.99 587.917 136.166 593.803 135.171 600.075 138.691 604.679 145.579L603.017 146.69C598.862 140.476 593.349 137.28 588.249 138.138 584.063 138.844 580.539 142.143 578.325 147.427 576.046 152.866 575.44 159.709 576.62 166.695 576.988 168.876 577.515 170.966 578.173 172.937 582.618 172.1 587.651 170.742 592.807 168.965 599.927 166.51 606.392 163.572 611.01 160.689 616.207 157.447 617.201 155.444 616.969 154.772 616.769 154.189 615.095 153.299 610.097 153.653L609.957 151.657C615.171 151.289 618.171 152.116 618.86 154.12 619.619 156.32 617.334 159.101 612.069 162.386" fill="#FFFFFF" fill-rule="evenodd" fill-opacity="1"/></g></g></g></g></g></svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
BIN
.images/amazon-dcv-windows.png
Normal file
BIN
.images/amazon-dcv-windows.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.3 MiB |
@@ -13,6 +13,7 @@ tags: [helper]
|
||||
|
||||
```tf
|
||||
module "MODULE_NAME" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/MODULE_NAME/coder"
|
||||
version = "1.0.2"
|
||||
}
|
||||
@@ -28,6 +29,7 @@ Install the Dracula theme from [OpenVSX](https://open-vsx.org/):
|
||||
|
||||
```tf
|
||||
module "MODULE_NAME" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/MODULE_NAME/coder"
|
||||
version = "1.0.2"
|
||||
agent_id = coder_agent.example.id
|
||||
@@ -45,6 +47,7 @@ Configure VS Code's [settings.json](https://code.visualstudio.com/docs/getstarte
|
||||
|
||||
```tf
|
||||
module "MODULE_NAME" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/MODULE_NAME/coder"
|
||||
version = "1.0.2"
|
||||
agent_id = coder_agent.example.id
|
||||
|
||||
@@ -47,6 +47,7 @@ You can test a module locally by updating the source as follows
|
||||
```tf
|
||||
module "example" {
|
||||
source = "git::https://github.com/<USERNAME>/<REPO>.git//<MODULE-NAME>?ref=<BRANCH-NAME>"
|
||||
# You may need to remove the 'version' field, it is incompatible with some sources.
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
[](https://discord.gg/coder)
|
||||
[](./LICENSE)
|
||||
[](https://github.com/coder/modules/actions/workflows/check.yaml)
|
||||
|
||||
</div>
|
||||
|
||||
@@ -16,6 +17,7 @@ e.g.
|
||||
|
||||
```tf
|
||||
module "code-server" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/code-server/coder"
|
||||
version = "1.0.2"
|
||||
agent_id = coder_agent.main.id
|
||||
|
||||
49
amazon-dcv-windows/README.md
Normal file
49
amazon-dcv-windows/README.md
Normal file
@@ -0,0 +1,49 @@
|
||||
---
|
||||
display_name: Amazon DCV Windows
|
||||
description: Amazon DCV Server and Web Client for Windows
|
||||
icon: ../.icons/dcv.svg
|
||||
maintainer_github: coder
|
||||
verified: true
|
||||
tags: [windows, amazon, dcv, web, desktop]
|
||||
---
|
||||
|
||||
# Amazon DCV Windows
|
||||
|
||||
Amazon DCV is high performance remote display protocol that provides a secure way to deliver remote desktop and application streaming from any cloud or data center to any device, over varying network conditions.
|
||||
|
||||

|
||||
|
||||
Enable DCV Server and Web Client on Windows workspaces.
|
||||
|
||||
```tf
|
||||
module "dcv" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/amazon-dcv-windows/coder"
|
||||
version = "1.0.24"
|
||||
agent_id = resource.coder_agent.main.id
|
||||
}
|
||||
|
||||
|
||||
resource "coder_metadata" "dcv" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
resource_id = aws_instance.dev.id # id of the instance resource
|
||||
|
||||
item {
|
||||
key = "DCV client instructions"
|
||||
value = "Run `coder port-forward ${data.coder_workspace.me.name} -p ${module.dcv[count.index].port}` and connect to **localhost:${module.dcv[count.index].port}${module.dcv[count.index].web_url_path}**"
|
||||
}
|
||||
item {
|
||||
key = "username"
|
||||
value = module.dcv[count.index].username
|
||||
}
|
||||
item {
|
||||
key = "password"
|
||||
value = module.dcv[count.index].password
|
||||
sensitive = true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
Amazon DCV is free to use on AWS EC2 instances but requires a license for other cloud providers. Please see the instructions [here](https://docs.aws.amazon.com/dcv/latest/adminguide/setting-up-license.html#setting-up-license-ec2) for more information.
|
||||
170
amazon-dcv-windows/install-dcv.ps1
Normal file
170
amazon-dcv-windows/install-dcv.ps1
Normal file
@@ -0,0 +1,170 @@
|
||||
# Terraform variables
|
||||
$adminPassword = "${admin_password}"
|
||||
$port = "${port}"
|
||||
$webURLPath = "${web_url_path}"
|
||||
|
||||
function Set-LocalAdminUser {
|
||||
Write-Output "[INFO] Starting Set-LocalAdminUser function"
|
||||
$securePassword = ConvertTo-SecureString $adminPassword -AsPlainText -Force
|
||||
Write-Output "[DEBUG] Secure password created"
|
||||
Get-LocalUser -Name Administrator | Set-LocalUser -Password $securePassword
|
||||
Write-Output "[INFO] Administrator password set"
|
||||
Get-LocalUser -Name Administrator | Enable-LocalUser
|
||||
Write-Output "[INFO] User Administrator enabled successfully"
|
||||
Read-Host "[DEBUG] Press Enter to proceed to the next step"
|
||||
}
|
||||
|
||||
function Get-VirtualDisplayDriverRequired {
|
||||
Write-Output "[INFO] Starting Get-VirtualDisplayDriverRequired function"
|
||||
$token = Invoke-RestMethod -Headers @{'X-aws-ec2-metadata-token-ttl-seconds' = '21600'} -Method PUT -Uri http://169.254.169.254/latest/api/token
|
||||
Write-Output "[DEBUG] Token acquired: $token"
|
||||
$instanceType = Invoke-RestMethod -Headers @{'X-aws-ec2-metadata-token' = $token} -Method GET -Uri http://169.254.169.254/latest/meta-data/instance-type
|
||||
Write-Output "[DEBUG] Instance type: $instanceType"
|
||||
$OSVersion = ((Get-ItemProperty -Path "Microsoft.PowerShell.Core\Registry::\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion" -Name ProductName).ProductName) -replace "[^0-9]", ''
|
||||
Write-Output "[DEBUG] OS version: $OSVersion"
|
||||
|
||||
# Force boolean result
|
||||
$result = (($OSVersion -ne "2019") -and ($OSVersion -ne "2022") -and ($OSVersion -ne "2025")) -and (($instanceType[0] -ne 'g') -and ($instanceType[0] -ne 'p'))
|
||||
Write-Output "[INFO] VirtualDisplayDriverRequired result: $result"
|
||||
Read-Host "[DEBUG] Press Enter to proceed to the next step"
|
||||
return [bool]$result
|
||||
}
|
||||
|
||||
function Download-DCV {
|
||||
param (
|
||||
[bool]$VirtualDisplayDriverRequired
|
||||
)
|
||||
Write-Output "[INFO] Starting Download-DCV function"
|
||||
|
||||
$downloads = @(
|
||||
@{
|
||||
Name = "DCV Display Driver"
|
||||
Required = $VirtualDisplayDriverRequired
|
||||
Path = "C:\Windows\Temp\DCVDisplayDriver.msi"
|
||||
Uri = "https://d1uj6qtbmh3dt5.cloudfront.net/nice-dcv-virtual-display-x64-Release.msi"
|
||||
},
|
||||
@{
|
||||
Name = "DCV Server"
|
||||
Required = $true
|
||||
Path = "C:\Windows\Temp\DCVServer.msi"
|
||||
Uri = "https://d1uj6qtbmh3dt5.cloudfront.net/nice-dcv-server-x64-Release.msi"
|
||||
}
|
||||
)
|
||||
|
||||
foreach ($download in $downloads) {
|
||||
if ($download.Required -and -not (Test-Path $download.Path)) {
|
||||
try {
|
||||
Write-Output "[INFO] Downloading $($download.Name)"
|
||||
|
||||
# Display progress manually (no events)
|
||||
$progressActivity = "Downloading $($download.Name)"
|
||||
$progressStatus = "Starting download..."
|
||||
Write-Progress -Activity $progressActivity -Status $progressStatus -PercentComplete 0
|
||||
|
||||
# Synchronously download the file
|
||||
$webClient = New-Object System.Net.WebClient
|
||||
$webClient.DownloadFile($download.Uri, $download.Path)
|
||||
|
||||
# Update progress
|
||||
Write-Progress -Activity $progressActivity -Status "Completed" -PercentComplete 100
|
||||
|
||||
Write-Output "[INFO] $($download.Name) downloaded successfully."
|
||||
} catch {
|
||||
Write-Output "[ERROR] Failed to download $($download.Name): $_"
|
||||
throw
|
||||
}
|
||||
} else {
|
||||
Write-Output "[INFO] $($download.Name) already exists. Skipping download."
|
||||
}
|
||||
}
|
||||
|
||||
Write-Output "[INFO] All downloads completed"
|
||||
Read-Host "[DEBUG] Press Enter to proceed to the next step"
|
||||
}
|
||||
|
||||
function Install-DCV {
|
||||
param (
|
||||
[bool]$VirtualDisplayDriverRequired
|
||||
)
|
||||
Write-Output "[INFO] Starting Install-DCV function"
|
||||
|
||||
if (-not (Get-Service -Name "dcvserver" -ErrorAction SilentlyContinue)) {
|
||||
if ($VirtualDisplayDriverRequired) {
|
||||
Write-Output "[INFO] Installing DCV Display Driver"
|
||||
Start-Process "C:\Windows\System32\msiexec.exe" -ArgumentList "/I C:\Windows\Temp\DCVDisplayDriver.msi /quiet /norestart" -Wait
|
||||
} else {
|
||||
Write-Output "[INFO] DCV Display Driver installation skipped (not required)."
|
||||
}
|
||||
Write-Output "[INFO] Installing DCV Server"
|
||||
Start-Process "C:\Windows\System32\msiexec.exe" -ArgumentList "/I C:\Windows\Temp\DCVServer.msi ADDLOCAL=ALL /quiet /norestart /l*v C:\Windows\Temp\dcv_install_msi.log" -Wait
|
||||
} else {
|
||||
Write-Output "[INFO] DCV Server already installed, skipping installation."
|
||||
}
|
||||
|
||||
# Wait for the service to appear with a timeout
|
||||
$timeout = 10 # seconds
|
||||
$elapsed = 0
|
||||
while (-not (Get-Service -Name "dcvserver" -ErrorAction SilentlyContinue) -and ($elapsed -lt $timeout)) {
|
||||
Start-Sleep -Seconds 1
|
||||
$elapsed++
|
||||
}
|
||||
|
||||
if ($elapsed -ge $timeout) {
|
||||
Write-Output "[WARNING] Timeout waiting for dcvserver service. A restart is required to complete installation."
|
||||
Restart-SystemForDCV
|
||||
} else {
|
||||
Write-Output "[INFO] dcvserver service detected successfully."
|
||||
}
|
||||
}
|
||||
|
||||
function Restart-SystemForDCV {
|
||||
Write-Output "[INFO] The system will restart in 10 seconds to finalize DCV installation."
|
||||
Start-Sleep -Seconds 10
|
||||
|
||||
# Initiate restart
|
||||
Restart-Computer -Force
|
||||
|
||||
# Exit the script after initiating restart
|
||||
Write-Output "[INFO] Please wait for the system to restart..."
|
||||
|
||||
Exit 1
|
||||
}
|
||||
|
||||
|
||||
function Configure-DCV {
|
||||
Write-Output "[INFO] Starting Configure-DCV function"
|
||||
$dcvPath = "Microsoft.PowerShell.Core\Registry::\HKEY_USERS\S-1-5-18\Software\GSettings\com\nicesoftware\dcv"
|
||||
|
||||
# Create the required paths
|
||||
@("$dcvPath\connectivity", "$dcvPath\session-management", "$dcvPath\session-management\automatic-console-session", "$dcvPath\display") | ForEach-Object {
|
||||
if (-not (Test-Path $_)) {
|
||||
New-Item -Path $_ -Force | Out-Null
|
||||
}
|
||||
}
|
||||
|
||||
# Set registry keys
|
||||
New-ItemProperty -Path "$dcvPath\session-management" -Name create-session -PropertyType DWORD -Value 1 -Force
|
||||
New-ItemProperty -Path "$dcvPath\session-management\automatic-console-session" -Name owner -Value Administrator -Force
|
||||
New-ItemProperty -Path "$dcvPath\connectivity" -Name quic-port -PropertyType DWORD -Value $port -Force
|
||||
New-ItemProperty -Path "$dcvPath\connectivity" -Name web-port -PropertyType DWORD -Value $port -Force
|
||||
New-ItemProperty -Path "$dcvPath\connectivity" -Name web-url-path -PropertyType String -Value $webURLPath -Force
|
||||
|
||||
# Attempt to restart service
|
||||
if (Get-Service -Name "dcvserver" -ErrorAction SilentlyContinue) {
|
||||
Restart-Service -Name "dcvserver"
|
||||
} else {
|
||||
Write-Output "[WARNING] dcvserver service not found. Ensure the system was restarted properly."
|
||||
}
|
||||
|
||||
Write-Output "[INFO] DCV configuration completed"
|
||||
Read-Host "[DEBUG] Press Enter to proceed to the next step"
|
||||
}
|
||||
|
||||
# Main Script Execution
|
||||
Write-Output "[INFO] Starting script"
|
||||
$VirtualDisplayDriverRequired = [bool](Get-VirtualDisplayDriverRequired)
|
||||
Set-LocalAdminUser
|
||||
Download-DCV -VirtualDisplayDriverRequired $VirtualDisplayDriverRequired
|
||||
Install-DCV -VirtualDisplayDriverRequired $VirtualDisplayDriverRequired
|
||||
Configure-DCV
|
||||
Write-Output "[INFO] Script completed"
|
||||
85
amazon-dcv-windows/main.tf
Normal file
85
amazon-dcv-windows/main.tf
Normal file
@@ -0,0 +1,85 @@
|
||||
terraform {
|
||||
required_version = ">= 1.0"
|
||||
|
||||
required_providers {
|
||||
coder = {
|
||||
source = "coder/coder"
|
||||
version = ">= 0.17"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
variable "agent_id" {
|
||||
type = string
|
||||
description = "The ID of a Coder agent."
|
||||
}
|
||||
|
||||
variable "admin_password" {
|
||||
type = string
|
||||
default = "coderDCV!"
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
variable "port" {
|
||||
type = number
|
||||
description = "The port number for the DCV server."
|
||||
default = 8443
|
||||
}
|
||||
|
||||
variable "subdomain" {
|
||||
type = bool
|
||||
description = "Whether to use a subdomain for the DCV server."
|
||||
default = true
|
||||
}
|
||||
|
||||
variable "slug" {
|
||||
type = string
|
||||
description = "The slug of the web-dcv coder_app resource."
|
||||
default = "web-dcv"
|
||||
}
|
||||
|
||||
resource "coder_app" "web-dcv" {
|
||||
agent_id = var.agent_id
|
||||
slug = var.slug
|
||||
display_name = "Web DCV"
|
||||
url = "https://localhost:${var.port}${local.web_url_path}?username=${local.admin_username}&password=${var.admin_password}"
|
||||
icon = "/icon/dcv.svg"
|
||||
subdomain = var.subdomain
|
||||
}
|
||||
|
||||
resource "coder_script" "install-dcv" {
|
||||
agent_id = var.agent_id
|
||||
display_name = "Install DCV"
|
||||
icon = "/icon/dcv.svg"
|
||||
run_on_start = true
|
||||
script = templatefile("${path.module}/install-dcv.ps1", {
|
||||
admin_password : var.admin_password,
|
||||
port : var.port,
|
||||
web_url_path : local.web_url_path
|
||||
})
|
||||
}
|
||||
|
||||
data "coder_workspace" "me" {}
|
||||
data "coder_workspace_owner" "me" {}
|
||||
|
||||
locals {
|
||||
web_url_path = var.subdomain ? "/" : format("/@%s/%s/apps/%s", data.coder_workspace_owner.me.name, data.coder_workspace.me.name, var.slug)
|
||||
admin_username = "Administrator"
|
||||
}
|
||||
|
||||
output "web_url_path" {
|
||||
value = local.web_url_path
|
||||
}
|
||||
|
||||
output "username" {
|
||||
value = local.admin_username
|
||||
}
|
||||
|
||||
output "password" {
|
||||
value = var.admin_password
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
output "port" {
|
||||
value = var.port
|
||||
}
|
||||
@@ -14,6 +14,7 @@ A module that adds Apache Airflow in your Coder template.
|
||||
|
||||
```tf
|
||||
module "airflow" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/apache-airflow/coder"
|
||||
version = "1.0.13"
|
||||
agent_id = coder_agent.main.id
|
||||
|
||||
@@ -16,6 +16,7 @@ Customize the preselected parameter value:
|
||||
|
||||
```tf
|
||||
module "aws-region" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/aws-region/coder"
|
||||
version = "1.0.12"
|
||||
default = "us-east-1"
|
||||
@@ -36,6 +37,7 @@ Change the display name and icon for a region using the corresponding maps:
|
||||
|
||||
```tf
|
||||
module "aws-region" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/aws-region/coder"
|
||||
version = "1.0.12"
|
||||
default = "ap-south-1"
|
||||
@@ -62,6 +64,7 @@ Hide the Asia Pacific regions Seoul and Osaka:
|
||||
|
||||
```tf
|
||||
module "aws-region" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/aws-region/coder"
|
||||
version = "1.0.12"
|
||||
exclude = ["ap-northeast-2", "ap-northeast-3"]
|
||||
|
||||
@@ -13,6 +13,7 @@ This module adds a parameter with all Azure regions, allowing developers to sele
|
||||
|
||||
```tf
|
||||
module "azure_region" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/azure-region/coder"
|
||||
version = "1.0.12"
|
||||
default = "eastus"
|
||||
@@ -33,6 +34,7 @@ Change the display name and icon for a region using the corresponding maps:
|
||||
|
||||
```tf
|
||||
module "azure-region" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/azure-region/coder"
|
||||
version = "1.0.12"
|
||||
custom_names = {
|
||||
@@ -56,6 +58,7 @@ Hide all regions in Australia except australiacentral:
|
||||
|
||||
```tf
|
||||
module "azure-region" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/azure-region/coder"
|
||||
version = "1.0.12"
|
||||
exclude = [
|
||||
|
||||
@@ -13,8 +13,9 @@ Automatically install [code-server](https://github.com/coder/code-server) in a w
|
||||
|
||||
```tf
|
||||
module "code-server" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/code-server/coder"
|
||||
version = "1.0.18"
|
||||
version = "1.0.26"
|
||||
agent_id = coder_agent.example.id
|
||||
}
|
||||
```
|
||||
@@ -27,8 +28,9 @@ module "code-server" {
|
||||
|
||||
```tf
|
||||
module "code-server" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/code-server/coder"
|
||||
version = "1.0.18"
|
||||
version = "1.0.26"
|
||||
agent_id = coder_agent.example.id
|
||||
install_version = "4.8.3"
|
||||
}
|
||||
@@ -40,8 +42,9 @@ Install the Dracula theme from [OpenVSX](https://open-vsx.org/):
|
||||
|
||||
```tf
|
||||
module "code-server" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/code-server/coder"
|
||||
version = "1.0.18"
|
||||
version = "1.0.26"
|
||||
agent_id = coder_agent.example.id
|
||||
extensions = [
|
||||
"dracula-theme.theme-dracula"
|
||||
@@ -57,8 +60,9 @@ Configure VS Code's [settings.json](https://code.visualstudio.com/docs/getstarte
|
||||
|
||||
```tf
|
||||
module "code-server" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/code-server/coder"
|
||||
version = "1.0.18"
|
||||
version = "1.0.26"
|
||||
agent_id = coder_agent.example.id
|
||||
extensions = ["dracula-theme.theme-dracula"]
|
||||
settings = {
|
||||
@@ -73,8 +77,9 @@ Just run code-server in the background, don't fetch it from GitHub:
|
||||
|
||||
```tf
|
||||
module "code-server" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/code-server/coder"
|
||||
version = "1.0.18"
|
||||
version = "1.0.26"
|
||||
agent_id = coder_agent.example.id
|
||||
extensions = ["dracula-theme.theme-dracula", "ms-azuretools.vscode-docker"]
|
||||
}
|
||||
@@ -88,8 +93,9 @@ Run an existing copy of code-server if found, otherwise download from GitHub:
|
||||
|
||||
```tf
|
||||
module "code-server" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/code-server/coder"
|
||||
version = "1.0.18"
|
||||
version = "1.0.26"
|
||||
agent_id = coder_agent.example.id
|
||||
use_cached = true
|
||||
extensions = ["dracula-theme.theme-dracula", "ms-azuretools.vscode-docker"]
|
||||
@@ -100,8 +106,9 @@ Just run code-server in the background, don't fetch it from GitHub:
|
||||
|
||||
```tf
|
||||
module "code-server" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/code-server/coder"
|
||||
version = "1.0.18"
|
||||
version = "1.0.26"
|
||||
agent_id = coder_agent.example.id
|
||||
offline = true
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ variable "slug" {
|
||||
}
|
||||
|
||||
variable "settings" {
|
||||
type = map(string)
|
||||
type = any
|
||||
description = "A map of settings to apply to code-server."
|
||||
default = {}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ Automatically logs the user into Coder when creating their workspace.
|
||||
|
||||
```tf
|
||||
module "coder-login" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/coder-login/coder"
|
||||
version = "1.0.15"
|
||||
agent_id = coder_agent.example.id
|
||||
|
||||
@@ -15,6 +15,7 @@ Uses the [Coder Remote VS Code Extension](https://github.com/coder/cursor-coder)
|
||||
|
||||
```tf
|
||||
module "cursor" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/cursor/coder"
|
||||
version = "1.0.19"
|
||||
agent_id = coder_agent.example.id
|
||||
@@ -27,6 +28,7 @@ module "cursor" {
|
||||
|
||||
```tf
|
||||
module "cursor" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/cursor/coder"
|
||||
version = "1.0.19"
|
||||
agent_id = coder_agent.example.id
|
||||
|
||||
@@ -17,8 +17,9 @@ Under the hood, this module uses the [coder dotfiles](https://coder.com/docs/v2/
|
||||
|
||||
```tf
|
||||
module "dotfiles" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/dotfiles/coder"
|
||||
version = "1.0.18"
|
||||
version = "1.0.28"
|
||||
agent_id = coder_agent.example.id
|
||||
}
|
||||
```
|
||||
@@ -29,8 +30,9 @@ module "dotfiles" {
|
||||
|
||||
```tf
|
||||
module "dotfiles" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/dotfiles/coder"
|
||||
version = "1.0.18"
|
||||
version = "1.0.28"
|
||||
agent_id = coder_agent.example.id
|
||||
}
|
||||
```
|
||||
@@ -39,8 +41,9 @@ module "dotfiles" {
|
||||
|
||||
```tf
|
||||
module "dotfiles" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/dotfiles/coder"
|
||||
version = "1.0.18"
|
||||
version = "1.0.28"
|
||||
agent_id = coder_agent.example.id
|
||||
user = "root"
|
||||
}
|
||||
@@ -50,14 +53,16 @@ module "dotfiles" {
|
||||
|
||||
```tf
|
||||
module "dotfiles" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/dotfiles/coder"
|
||||
version = "1.0.18"
|
||||
version = "1.0.28"
|
||||
agent_id = coder_agent.example.id
|
||||
}
|
||||
|
||||
module "dotfiles-root" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/dotfiles/coder"
|
||||
version = "1.0.18"
|
||||
version = "1.0.28"
|
||||
agent_id = coder_agent.example.id
|
||||
user = "root"
|
||||
dotfiles_uri = module.dotfiles.dotfiles_uri
|
||||
@@ -70,8 +75,9 @@ You can set a default dotfiles repository for all users by setting the `default_
|
||||
|
||||
```tf
|
||||
module "dotfiles" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/dotfiles/coder"
|
||||
version = "1.0.18"
|
||||
version = "1.0.28"
|
||||
agent_id = coder_agent.example.id
|
||||
default_dotfiles_uri = "https://github.com/coder/dotfiles"
|
||||
}
|
||||
|
||||
@@ -18,6 +18,6 @@ if [ -n "$${DOTFILES_URI// }" ]; then
|
||||
|
||||
CODER_BIN=$(which coder)
|
||||
DOTFILES_USER_HOME=$(eval echo ~"$DOTFILES_USER")
|
||||
sudo -u "$DOTFILES_USER" sh -c "'$CODER_BIN' dotfiles '$DOTFILES_URI' -y 2>&1 | tee '$DOTFILES_USER_HOME'/.dotfiles.log"
|
||||
sudo -u "$DOTFILES_USER" sh -c "'$CODER_BIN' dotfiles '$DOTFILES_URI' -y 2>&1 | tee '$DOTFILES_USER_HOME'/.dotfiles.log || true"
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -16,6 +16,7 @@ Customize the preselected parameter value:
|
||||
|
||||
```tf
|
||||
module "exoscale-instance-type" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/exoscale-instance-type/coder"
|
||||
version = "1.0.12"
|
||||
default = "standard.medium"
|
||||
@@ -44,6 +45,7 @@ Change the display name a type using the corresponding maps:
|
||||
|
||||
```tf
|
||||
module "exoscale-instance-type" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/exoscale-instance-type/coder"
|
||||
version = "1.0.12"
|
||||
default = "standard.medium"
|
||||
@@ -78,6 +80,7 @@ Show only gpu1 types
|
||||
|
||||
```tf
|
||||
module "exoscale-instance-type" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/exoscale-instance-type/coder"
|
||||
version = "1.0.12"
|
||||
default = "gpu.large"
|
||||
|
||||
@@ -16,6 +16,7 @@ Customize the preselected parameter value:
|
||||
|
||||
```tf
|
||||
module "exoscale-zone" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/exoscale-zone/coder"
|
||||
version = "1.0.12"
|
||||
default = "ch-dk-2"
|
||||
@@ -43,6 +44,7 @@ Change the display name and icon for a zone using the corresponding maps:
|
||||
|
||||
```tf
|
||||
module "exoscale-zone" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/exoscale-zone/coder"
|
||||
version = "1.0.12"
|
||||
default = "at-vie-1"
|
||||
|
||||
@@ -13,8 +13,9 @@ A file browser for your workspace.
|
||||
|
||||
```tf
|
||||
module "filebrowser" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/filebrowser/coder"
|
||||
version = "1.0.19"
|
||||
version = "1.0.23"
|
||||
agent_id = coder_agent.example.id
|
||||
}
|
||||
```
|
||||
@@ -27,8 +28,9 @@ module "filebrowser" {
|
||||
|
||||
```tf
|
||||
module "filebrowser" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/filebrowser/coder"
|
||||
version = "1.0.19"
|
||||
version = "1.0.23"
|
||||
agent_id = coder_agent.example.id
|
||||
folder = "/home/coder/project"
|
||||
}
|
||||
@@ -38,8 +40,9 @@ module "filebrowser" {
|
||||
|
||||
```tf
|
||||
module "filebrowser" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/filebrowser/coder"
|
||||
version = "1.0.19"
|
||||
version = "1.0.23"
|
||||
agent_id = coder_agent.example.id
|
||||
database_path = ".config/filebrowser.db"
|
||||
}
|
||||
@@ -49,7 +52,9 @@ module "filebrowser" {
|
||||
|
||||
```tf
|
||||
module "filebrowser" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/filebrowser/coder"
|
||||
version = "1.0.23"
|
||||
agent_id = coder_agent.example.id
|
||||
agent_name = "main"
|
||||
subdomain = false
|
||||
|
||||
@@ -20,13 +20,8 @@ data "coder_workspace_owner" "me" {}
|
||||
|
||||
variable "agent_name" {
|
||||
type = string
|
||||
description = "The name of the main deployment. (Used to build the subpath for coder_app.)"
|
||||
default = ""
|
||||
validation {
|
||||
# If subdomain is false, then agent_name must be set.
|
||||
condition = var.subdomain || var.agent_name != ""
|
||||
error_message = "The agent_name must be set."
|
||||
}
|
||||
description = "The name of the coder_agent resource. (Only required if subdomain is false and the template uses multiple agents.)"
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "database_path" {
|
||||
@@ -73,6 +68,12 @@ variable "order" {
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "slug" {
|
||||
type = string
|
||||
description = "The slug of the coder_app resource."
|
||||
default = "filebrowser"
|
||||
}
|
||||
|
||||
variable "subdomain" {
|
||||
type = bool
|
||||
description = <<-EOT
|
||||
@@ -85,7 +86,7 @@ variable "subdomain" {
|
||||
resource "coder_script" "filebrowser" {
|
||||
agent_id = var.agent_id
|
||||
display_name = "File Browser"
|
||||
icon = "https://raw.githubusercontent.com/filebrowser/logo/master/icon_raw.svg"
|
||||
icon = "/icon/filebrowser.svg"
|
||||
script = templatefile("${path.module}/run.sh", {
|
||||
LOG_PATH : var.log_path,
|
||||
PORT : var.port,
|
||||
@@ -93,18 +94,30 @@ resource "coder_script" "filebrowser" {
|
||||
LOG_PATH : var.log_path,
|
||||
DB_PATH : var.database_path,
|
||||
SUBDOMAIN : var.subdomain,
|
||||
SERVER_BASE_PATH : var.subdomain ? "" : format("/@%s/%s.%s/apps/filebrowser", data.coder_workspace_owner.me.name, data.coder_workspace.me.name, var.agent_name),
|
||||
SERVER_BASE_PATH : local.server_base_path
|
||||
})
|
||||
run_on_start = true
|
||||
}
|
||||
|
||||
resource "coder_app" "filebrowser" {
|
||||
agent_id = var.agent_id
|
||||
slug = "filebrowser"
|
||||
slug = var.slug
|
||||
display_name = "File Browser"
|
||||
url = "http://localhost:${var.port}"
|
||||
icon = "https://raw.githubusercontent.com/filebrowser/logo/master/icon_raw.svg"
|
||||
url = local.url
|
||||
icon = "/icon/filebrowser.svg"
|
||||
subdomain = var.subdomain
|
||||
share = var.share
|
||||
order = var.order
|
||||
|
||||
healthcheck {
|
||||
url = local.healthcheck_url
|
||||
interval = 5
|
||||
threshold = 6
|
||||
}
|
||||
}
|
||||
|
||||
locals {
|
||||
server_base_path = var.subdomain ? "" : format("/@%s/%s%s/apps/%s", data.coder_workspace_owner.me.name, data.coder_workspace.me.name, var.agent_name != null ? ".${var.agent_name}" : "", var.slug)
|
||||
url = "http://localhost:${var.port}${local.server_base_path}"
|
||||
healthcheck_url = "http://localhost:${var.port}${local.server_base_path}/health"
|
||||
}
|
||||
@@ -1,9 +1,13 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
BOLD='\033[0;1m'
|
||||
|
||||
printf "$${BOLD}Installing filebrowser \n\n"
|
||||
|
||||
curl -fsSL https://raw.githubusercontent.com/filebrowser/get/master/get.sh | bash
|
||||
# Check if filebrowser is installed
|
||||
if ! command -v filebrowser &> /dev/null; then
|
||||
curl -fsSL https://raw.githubusercontent.com/filebrowser/get/master/get.sh | bash
|
||||
fi
|
||||
|
||||
printf "🥳 Installation complete! \n\n"
|
||||
|
||||
@@ -18,7 +22,7 @@ if [ "${DB_PATH}" != "filebrowser.db" ]; then
|
||||
fi
|
||||
|
||||
# set baseurl to be able to run if sudomain=false; if subdomain=true the SERVER_BASE_PATH value will be ""
|
||||
filebrowser config set --baseurl "${SERVER_BASE_PATH}" > ${LOG_PATH} 2>&1
|
||||
filebrowser config set --baseurl "${SERVER_BASE_PATH}"$${DB_FLAG} > ${LOG_PATH} 2>&1
|
||||
|
||||
printf "📂 Serving $${ROOT_DIR} at http://localhost:${PORT} \n\n"
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ We can use the simplest format here, only adding a default selection as the `atl
|
||||
|
||||
```tf
|
||||
module "fly-region" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/fly-region/coder"
|
||||
version = "1.0.2"
|
||||
default = "atl"
|
||||
@@ -31,6 +32,7 @@ The regions argument can be used to display only the desired regions in the Code
|
||||
|
||||
```tf
|
||||
module "fly-region" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/fly-region/coder"
|
||||
version = "1.0.2"
|
||||
default = "ams"
|
||||
@@ -46,6 +48,7 @@ Set custom icons and names with their respective maps.
|
||||
|
||||
```tf
|
||||
module "fly-region" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/fly-region/coder"
|
||||
version = "1.0.2"
|
||||
default = "ams"
|
||||
|
||||
@@ -13,6 +13,7 @@ This module adds Google Cloud Platform regions to your Coder template.
|
||||
|
||||
```tf
|
||||
module "gcp_region" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/gcp-region/coder"
|
||||
version = "1.0.12"
|
||||
regions = ["us", "europe"]
|
||||
@@ -33,6 +34,7 @@ Note: setting `gpu_only = true` and using a default region without GPU support,
|
||||
|
||||
```tf
|
||||
module "gcp_region" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/gcp-region/coder"
|
||||
version = "1.0.12"
|
||||
default = ["us-west1-a"]
|
||||
@@ -49,6 +51,7 @@ resource "google_compute_instance" "example" {
|
||||
|
||||
```tf
|
||||
module "gcp_region" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/gcp-region/coder"
|
||||
version = "1.0.12"
|
||||
regions = ["europe-west"]
|
||||
@@ -64,6 +67,7 @@ resource "google_compute_instance" "example" {
|
||||
|
||||
```tf
|
||||
module "gcp_region" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/gcp-region/coder"
|
||||
version = "1.0.12"
|
||||
regions = ["us", "europe"]
|
||||
|
||||
@@ -13,8 +13,9 @@ This module allows you to automatically clone a repository by URL and skip if it
|
||||
|
||||
```tf
|
||||
module "git-clone" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/git-clone/coder"
|
||||
version = "1.0.18"
|
||||
version = "1.0.28"
|
||||
agent_id = coder_agent.example.id
|
||||
url = "https://github.com/coder/coder"
|
||||
}
|
||||
@@ -26,8 +27,9 @@ module "git-clone" {
|
||||
|
||||
```tf
|
||||
module "git-clone" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/git-clone/coder"
|
||||
version = "1.0.18"
|
||||
version = "1.0.28"
|
||||
agent_id = coder_agent.example.id
|
||||
url = "https://github.com/coder/coder"
|
||||
base_dir = "~/projects/coder"
|
||||
@@ -40,8 +42,9 @@ To use with [Git Authentication](https://coder.com/docs/v2/latest/admin/git-prov
|
||||
|
||||
```tf
|
||||
module "git-clone" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/git-clone/coder"
|
||||
version = "1.0.18"
|
||||
version = "1.0.28"
|
||||
agent_id = coder_agent.example.id
|
||||
url = "https://github.com/coder/coder"
|
||||
}
|
||||
@@ -65,31 +68,33 @@ data "coder_parameter" "git_repo" {
|
||||
|
||||
# Clone the repository for branch `feat/example`
|
||||
module "git_clone" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/git-clone/coder"
|
||||
version = "1.0.18"
|
||||
version = "1.0.28"
|
||||
agent_id = coder_agent.example.id
|
||||
url = data.coder_parameter.git_repo.value
|
||||
}
|
||||
|
||||
# Create a code-server instance for the cloned repository
|
||||
module "code-server" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/code-server/coder"
|
||||
version = "1.0.18"
|
||||
version = "1.0.28"
|
||||
agent_id = coder_agent.example.id
|
||||
order = 1
|
||||
folder = "/home/${local.username}/${module.git_clone.folder_name}"
|
||||
folder = "/home/${local.username}/${module.git_clone[count.index].folder_name}"
|
||||
}
|
||||
|
||||
# Create a Coder app for the website
|
||||
resource "coder_app" "website" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
agent_id = coder_agent.example.id
|
||||
order = 2
|
||||
slug = "website"
|
||||
external = true
|
||||
display_name = module.git_clone.folder_name
|
||||
url = module.git_clone.web_url
|
||||
icon = module.git_clone.git_provider != "" ? "/icon/${module.git_clone.git_provider}.svg" : "/icon/git.svg"
|
||||
count = module.git_clone.web_url != "" ? 1 : 0
|
||||
display_name = module.git_clone[count.index].folder_name
|
||||
url = module.git_clone[count.index].web_url
|
||||
icon = module.git_clone[count.index].git_provider != "" ? "/icon/${module.git_clone[count.index].git_provider}.svg" : "/icon/git.svg"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -97,8 +102,9 @@ Configuring `git-clone` for a self-hosted GitHub Enterprise Server running at `g
|
||||
|
||||
```tf
|
||||
module "git-clone" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/git-clone/coder"
|
||||
version = "1.0.18"
|
||||
version = "1.0.28"
|
||||
agent_id = coder_agent.example.id
|
||||
url = "https://github.example.com/coder/coder/tree/feat/example"
|
||||
git_providers = {
|
||||
@@ -115,8 +121,9 @@ To GitLab clone with a specific branch like `feat/example`
|
||||
|
||||
```tf
|
||||
module "git-clone" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/git-clone/coder"
|
||||
version = "1.0.18"
|
||||
version = "1.0.28"
|
||||
agent_id = coder_agent.example.id
|
||||
url = "https://gitlab.com/coder/coder/-/tree/feat/example"
|
||||
}
|
||||
@@ -126,8 +133,9 @@ Configuring `git-clone` for a self-hosted GitLab running at `gitlab.example.com`
|
||||
|
||||
```tf
|
||||
module "git-clone" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/git-clone/coder"
|
||||
version = "1.0.18"
|
||||
version = "1.0.28"
|
||||
agent_id = coder_agent.example.id
|
||||
url = "https://gitlab.example.com/coder/coder/-/tree/feat/example"
|
||||
git_providers = {
|
||||
@@ -146,8 +154,9 @@ For example, to clone the `feat/example` branch:
|
||||
|
||||
```tf
|
||||
module "git-clone" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/git-clone/coder"
|
||||
version = "1.0.18"
|
||||
version = "1.0.28"
|
||||
agent_id = coder_agent.example.id
|
||||
url = "https://github.com/coder/coder"
|
||||
branch_name = "feat/example"
|
||||
@@ -162,8 +171,9 @@ For example, this will clone into the `~/projects/coder/coder-dev` folder:
|
||||
|
||||
```tf
|
||||
module "git-clone" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/git-clone/coder"
|
||||
version = "1.0.18"
|
||||
version = "1.0.28"
|
||||
agent_id = coder_agent.example.id
|
||||
url = "https://github.com/coder/coder"
|
||||
folder_name = "coder-dev"
|
||||
|
||||
@@ -9,7 +9,7 @@ CLONE_PATH="$${CLONE_PATH/#\~/$${HOME}}"
|
||||
# Check if the variable is empty...
|
||||
if [ -z "$REPO_URL" ]; then
|
||||
echo "No repository specified!"
|
||||
exit 1
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Check if the variable is empty...
|
||||
|
||||
@@ -9,6 +9,9 @@ tags: [helper, git]
|
||||
|
||||
# git-commit-signing
|
||||
|
||||
> [!IMPORTANT]
|
||||
> This module will only work with Git versions >=2.34, prior versions [do not support signing commits via SSH keys](https://lore.kernel.org/git/xmqq8rxpgwki.fsf@gitster.g/).
|
||||
|
||||
This module downloads your SSH key from Coder and uses it to sign commits with Git.
|
||||
It requires `curl` and `jq` to be installed inside your workspace.
|
||||
|
||||
@@ -18,6 +21,7 @@ This module has a chance of conflicting with the user's dotfiles / the personali
|
||||
|
||||
```tf
|
||||
module "git-commit-signing" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/git-commit-signing/coder"
|
||||
version = "1.0.11"
|
||||
agent_id = coder_agent.example.id
|
||||
|
||||
@@ -13,6 +13,7 @@ Runs a script that updates git credentials in the workspace to match the user's
|
||||
|
||||
```tf
|
||||
module "git-config" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/git-config/coder"
|
||||
version = "1.0.15"
|
||||
agent_id = coder_agent.example.id
|
||||
@@ -27,6 +28,7 @@ TODO: Add screenshot
|
||||
|
||||
```tf
|
||||
module "git-config" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/git-config/coder"
|
||||
version = "1.0.15"
|
||||
agent_id = coder_agent.example.id
|
||||
@@ -40,6 +42,7 @@ TODO: Add screenshot
|
||||
|
||||
```tf
|
||||
module "git-config" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/git-config/coder"
|
||||
version = "1.0.15"
|
||||
agent_id = coder_agent.example.id
|
||||
|
||||
@@ -13,6 +13,7 @@ Templates that utilize Github External Auth can automatically ensure that the Co
|
||||
|
||||
```tf
|
||||
module "github-upload-public-key" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/github-upload-public-key/coder"
|
||||
version = "1.0.15"
|
||||
agent_id = coder_agent.example.id
|
||||
@@ -45,6 +46,7 @@ data "coder_external_auth" "github" {
|
||||
}
|
||||
|
||||
module "github-upload-public-key" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/github-upload-public-key/coder"
|
||||
version = "1.0.15"
|
||||
agent_id = coder_agent.example.id
|
||||
|
||||
@@ -13,8 +13,9 @@ This module adds a JetBrains Gateway Button to open any workspace with a single
|
||||
|
||||
```tf
|
||||
module "jetbrains_gateway" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/jetbrains-gateway/coder"
|
||||
version = "1.0.20"
|
||||
version = "1.0.27"
|
||||
agent_id = coder_agent.example.id
|
||||
agent_name = "example"
|
||||
folder = "/home/coder/example"
|
||||
@@ -31,8 +32,9 @@ module "jetbrains_gateway" {
|
||||
|
||||
```tf
|
||||
module "jetbrains_gateway" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/jetbrains-gateway/coder"
|
||||
version = "1.0.20"
|
||||
version = "1.0.27"
|
||||
agent_id = coder_agent.example.id
|
||||
agent_name = "example"
|
||||
folder = "/home/coder/example"
|
||||
@@ -41,27 +43,55 @@ module "jetbrains_gateway" {
|
||||
}
|
||||
```
|
||||
|
||||
### Use the latest release version
|
||||
### 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.20"
|
||||
version = "1.0.27"
|
||||
agent_id = coder_agent.example.id
|
||||
agent_name = "example"
|
||||
folder = "/home/coder/example"
|
||||
jetbrains_ides = ["GO", "WS"]
|
||||
default = "GO"
|
||||
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.27"
|
||||
agent_id = coder_agent.example.id
|
||||
agent_name = "example"
|
||||
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.20"
|
||||
version = "1.0.27"
|
||||
agent_id = coder_agent.example.id
|
||||
agent_name = "example"
|
||||
folder = "/home/coder/example"
|
||||
@@ -72,15 +102,35 @@ module "jetbrains_gateway" {
|
||||
}
|
||||
```
|
||||
|
||||
### 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.27"
|
||||
agent_id = coder_agent.example.id
|
||||
agent_name = "example"
|
||||
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`)
|
||||
- WebStorm (`WS`)
|
||||
- IntelliJ IDEA Ultimate (`IU`)
|
||||
- PyCharm Professional (`PY`)
|
||||
- PhpStorm (`PS`)
|
||||
- CLion (`CL`)
|
||||
- RubyMine (`RM`)
|
||||
- Rider (`RD`)
|
||||
- [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/)
|
||||
|
||||
@@ -22,7 +22,7 @@ describe("jetbrains-gateway", async () => {
|
||||
folder: "/home/coder",
|
||||
});
|
||||
expect(state.outputs.url.value).toBe(
|
||||
"jetbrains-gateway://connect#type=coder&workspace=default&owner=default&agent=foo&folder=/home/coder&url=https://mydeployment.coder.com&token=$SESSION_TOKEN&ide_product_code=IU&ide_build_number=241.14494.240&ide_download_link=https://download.jetbrains.com/idea/ideaIU-2024.1.tar.gz",
|
||||
"jetbrains-gateway://connect#type=coder&workspace=default&owner=default&agent=foo&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(
|
||||
|
||||
@@ -18,6 +18,12 @@ variable "agent_id" {
|
||||
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."
|
||||
@@ -74,59 +80,63 @@ variable "jetbrains_ide_versions" {
|
||||
description = "The set of versions for each jetbrains IDE"
|
||||
default = {
|
||||
"IU" = {
|
||||
build_number = "241.14494.240"
|
||||
version = "2024.1"
|
||||
build_number = "243.21565.193"
|
||||
version = "2024.3"
|
||||
}
|
||||
"PS" = {
|
||||
build_number = "241.14494.237"
|
||||
version = "2024.1"
|
||||
build_number = "243.21565.202"
|
||||
version = "2024.3"
|
||||
}
|
||||
"WS" = {
|
||||
build_number = "241.14494.235"
|
||||
version = "2024.1"
|
||||
build_number = "243.21565.180"
|
||||
version = "2024.3"
|
||||
}
|
||||
"PY" = {
|
||||
build_number = "241.14494.241"
|
||||
version = "2024.1"
|
||||
build_number = "243.21565.199"
|
||||
version = "2024.3"
|
||||
}
|
||||
"CL" = {
|
||||
build_number = "241.14494.288"
|
||||
build_number = "243.21565.238"
|
||||
version = "2024.1"
|
||||
}
|
||||
"GO" = {
|
||||
build_number = "241.14494.238"
|
||||
version = "2024.1"
|
||||
build_number = "243.21565.208"
|
||||
version = "2024.3"
|
||||
}
|
||||
"RM" = {
|
||||
build_number = "241.14494.234"
|
||||
version = "2024.1"
|
||||
build_number = "243.21565.197"
|
||||
version = "2024.3"
|
||||
}
|
||||
"RD" = {
|
||||
build_number = "241.14494.307"
|
||||
version = "2024.1"
|
||||
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"], code)
|
||||
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"])}."
|
||||
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"]
|
||||
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"], code)
|
||||
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"])}."
|
||||
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 {
|
||||
@@ -140,9 +150,29 @@ variable "jetbrains_ides" {
|
||||
}
|
||||
}
|
||||
|
||||
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 = "https://data.services.jetbrains.com/products/releases?code=${each.key}&latest=true&type=${var.channel}"
|
||||
url = "${var.releases_base_link}/products/releases?code=${each.key}&latest=true&type=${var.channel}"
|
||||
}
|
||||
|
||||
locals {
|
||||
@@ -152,7 +182,7 @@ locals {
|
||||
name = "GoLand",
|
||||
identifier = "GO",
|
||||
build_number = var.jetbrains_ide_versions["GO"].build_number,
|
||||
download_link = "https://download.jetbrains.com/go/goland-${var.jetbrains_ide_versions["GO"].version}.tar.gz"
|
||||
download_link = "${var.download_base_link}/go/goland-${var.jetbrains_ide_versions["GO"].version}.tar.gz"
|
||||
version = var.jetbrains_ide_versions["GO"].version
|
||||
},
|
||||
"WS" = {
|
||||
@@ -160,7 +190,7 @@ locals {
|
||||
name = "WebStorm",
|
||||
identifier = "WS",
|
||||
build_number = var.jetbrains_ide_versions["WS"].build_number,
|
||||
download_link = "https://download.jetbrains.com/webstorm/WebStorm-${var.jetbrains_ide_versions["WS"].version}.tar.gz"
|
||||
download_link = "${var.download_base_link}/webstorm/WebStorm-${var.jetbrains_ide_versions["WS"].version}.tar.gz"
|
||||
version = var.jetbrains_ide_versions["WS"].version
|
||||
},
|
||||
"IU" = {
|
||||
@@ -168,7 +198,7 @@ locals {
|
||||
name = "IntelliJ IDEA Ultimate",
|
||||
identifier = "IU",
|
||||
build_number = var.jetbrains_ide_versions["IU"].build_number,
|
||||
download_link = "https://download.jetbrains.com/idea/ideaIU-${var.jetbrains_ide_versions["IU"].version}.tar.gz"
|
||||
download_link = "${var.download_base_link}/idea/ideaIU-${var.jetbrains_ide_versions["IU"].version}.tar.gz"
|
||||
version = var.jetbrains_ide_versions["IU"].version
|
||||
},
|
||||
"PY" = {
|
||||
@@ -176,7 +206,7 @@ locals {
|
||||
name = "PyCharm Professional",
|
||||
identifier = "PY",
|
||||
build_number = var.jetbrains_ide_versions["PY"].build_number,
|
||||
download_link = "https://download.jetbrains.com/python/pycharm-professional-${var.jetbrains_ide_versions["PY"].version}.tar.gz"
|
||||
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" = {
|
||||
@@ -184,7 +214,7 @@ locals {
|
||||
name = "CLion",
|
||||
identifier = "CL",
|
||||
build_number = var.jetbrains_ide_versions["CL"].build_number,
|
||||
download_link = "https://download.jetbrains.com/cpp/CLion-${var.jetbrains_ide_versions["CL"].version}.tar.gz"
|
||||
download_link = "${var.download_base_link}/cpp/CLion-${var.jetbrains_ide_versions["CL"].version}.tar.gz"
|
||||
version = var.jetbrains_ide_versions["CL"].version
|
||||
},
|
||||
"PS" = {
|
||||
@@ -192,7 +222,7 @@ locals {
|
||||
name = "PhpStorm",
|
||||
identifier = "PS",
|
||||
build_number = var.jetbrains_ide_versions["PS"].build_number,
|
||||
download_link = "https://download.jetbrains.com/webide/PhpStorm-${var.jetbrains_ide_versions["PS"].version}.tar.gz"
|
||||
download_link = "${var.download_base_link}/webide/PhpStorm-${var.jetbrains_ide_versions["PS"].version}.tar.gz"
|
||||
version = var.jetbrains_ide_versions["PS"].version
|
||||
},
|
||||
"RM" = {
|
||||
@@ -200,16 +230,24 @@ locals {
|
||||
name = "RubyMine",
|
||||
identifier = "RM",
|
||||
build_number = var.jetbrains_ide_versions["RM"].build_number,
|
||||
download_link = "https://download.jetbrains.com/ruby/RubyMine-${var.jetbrains_ide_versions["RM"].version}.tar.gz"
|
||||
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 = "https://download.jetbrains.com/rider/JetBrains.Rider-${var.jetbrains_ide_versions["RD"].version}.tar.gz"
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -247,7 +285,7 @@ data "coder_workspace_owner" "me" {}
|
||||
|
||||
resource "coder_app" "gateway" {
|
||||
agent_id = var.agent_id
|
||||
slug = "gateway"
|
||||
slug = var.slug
|
||||
display_name = local.display_name
|
||||
icon = local.icon
|
||||
external = true
|
||||
|
||||
@@ -16,6 +16,7 @@ Install the JF CLI and authenticate package managers with Artifactory using OAut
|
||||
|
||||
```tf
|
||||
module "jfrog" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/jfrog-oauth/coder"
|
||||
version = "1.0.19"
|
||||
agent_id = coder_agent.example.id
|
||||
@@ -44,6 +45,7 @@ Configure the Python pip package manager to fetch packages from Artifactory whil
|
||||
|
||||
```tf
|
||||
module "jfrog" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/jfrog-oauth/coder"
|
||||
version = "1.0.19"
|
||||
agent_id = coder_agent.example.id
|
||||
@@ -72,6 +74,7 @@ The [JFrog extension](https://open-vsx.org/extension/JFrog/jfrog-vscode-extensio
|
||||
|
||||
```tf
|
||||
module "jfrog" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/jfrog-oauth/coder"
|
||||
version = "1.0.19"
|
||||
agent_id = coder_agent.example.id
|
||||
@@ -95,8 +98,8 @@ provider "docker" {
|
||||
# ...
|
||||
registry_auth {
|
||||
address = "https://example.jfrog.io/artifactory/api/docker/REPO-KEY"
|
||||
username = module.jfrog.username
|
||||
password = module.jfrog.access_token
|
||||
username = try(module.jfrog[0].username, "")
|
||||
password = try(module.jfrog[0].access_token, "")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -15,6 +15,7 @@ A module that adds Jupyter Notebook in your Coder template.
|
||||
|
||||
```tf
|
||||
module "jupyter-notebook" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/jupyter-notebook/coder"
|
||||
version = "1.0.19"
|
||||
agent_id = coder_agent.example.id
|
||||
|
||||
@@ -15,8 +15,9 @@ A module that adds JupyterLab in your Coder template.
|
||||
|
||||
```tf
|
||||
module "jupyterlab" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/jupyterlab/coder"
|
||||
version = "1.0.19"
|
||||
version = "1.0.23"
|
||||
agent_id = coder_agent.example.id
|
||||
}
|
||||
```
|
||||
|
||||
@@ -9,6 +9,9 @@ terraform {
|
||||
}
|
||||
}
|
||||
|
||||
data "coder_workspace" "me" {}
|
||||
data "coder_workspace_owner" "me" {}
|
||||
|
||||
# Add required variables for your modules and remove any unneeded variables
|
||||
variable "agent_id" {
|
||||
type = string
|
||||
@@ -36,6 +39,12 @@ variable "share" {
|
||||
}
|
||||
}
|
||||
|
||||
variable "subdomain" {
|
||||
type = bool
|
||||
description = "Determines whether JupyterLab will be accessed via its own subdomain or whether it will be accessed via a path on Coder."
|
||||
default = true
|
||||
}
|
||||
|
||||
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)."
|
||||
@@ -49,17 +58,18 @@ resource "coder_script" "jupyterlab" {
|
||||
script = templatefile("${path.module}/run.sh", {
|
||||
LOG_PATH : var.log_path,
|
||||
PORT : var.port
|
||||
BASE_URL : var.subdomain ? "" : "/@${data.coder_workspace_owner.me.name}/${data.coder_workspace.me.name}/apps/jupyterlab"
|
||||
})
|
||||
run_on_start = true
|
||||
}
|
||||
|
||||
resource "coder_app" "jupyterlab" {
|
||||
agent_id = var.agent_id
|
||||
slug = "jupyterlab"
|
||||
slug = "jupyterlab" # sync with the usage in URL
|
||||
display_name = "JupyterLab"
|
||||
url = "http://localhost:${var.port}"
|
||||
url = var.subdomain ? "http://localhost:${var.port}" : "http://localhost:${var.port}/@${data.coder_workspace_owner.me.name}/${data.coder_workspace.me.name}/apps/jupyterlab"
|
||||
icon = "/icon/jupyter.svg"
|
||||
subdomain = true
|
||||
subdomain = var.subdomain
|
||||
share = var.share
|
||||
order = var.order
|
||||
}
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
if [ -n "${BASE_URL}" ]; then
|
||||
BASE_URL_FLAG="--ServerApp.base_url=${BASE_URL}"
|
||||
fi
|
||||
|
||||
BOLD='\033[0;1m'
|
||||
|
||||
printf "$${BOLD}Installing jupyterlab!\n"
|
||||
|
||||
# check if jupyterlab is installed
|
||||
if ! command -v jupyterlab > /dev/null 2>&1; then
|
||||
if ! command -v jupyter-lab > /dev/null 2>&1; then
|
||||
# install jupyterlab
|
||||
# check if pipx is installed
|
||||
if ! command -v pipx > /dev/null 2>&1; then
|
||||
@@ -15,11 +19,17 @@ if ! command -v jupyterlab > /dev/null 2>&1; then
|
||||
fi
|
||||
# install jupyterlab
|
||||
pipx install -q jupyterlab
|
||||
echo "🥳 jupyterlab has been installed\n\n"
|
||||
printf "%s\n\n" "🥳 jupyterlab has been installed"
|
||||
else
|
||||
echo "🥳 jupyterlab is already installed\n\n"
|
||||
printf "%s\n\n" "🥳 jupyterlab is already installed"
|
||||
fi
|
||||
|
||||
echo "👷 Starting jupyterlab in background..."
|
||||
echo "check logs at ${LOG_PATH}"
|
||||
$HOME/.local/bin/jupyter-lab --ServerApp.ip='0.0.0.0' --ServerApp.port=${PORT} --no-browser --ServerApp.token='' --ServerApp.password='' > ${LOG_PATH} 2>&1 &
|
||||
printf "👷 Starting jupyterlab in background..."
|
||||
printf "check logs at ${LOG_PATH}"
|
||||
$HOME/.local/bin/jupyter-lab --no-browser \
|
||||
"$BASE_URL_FLAG" \
|
||||
--ServerApp.ip='*' \
|
||||
--ServerApp.port="${PORT}" \
|
||||
--ServerApp.token='' \
|
||||
--ServerApp.password='' \
|
||||
> "${LOG_PATH}" 2>&1 &
|
||||
|
||||
24
kasmvnc/README.md
Normal file
24
kasmvnc/README.md
Normal file
@@ -0,0 +1,24 @@
|
||||
---
|
||||
display_name: KasmVNC
|
||||
description: A modern open source VNC server
|
||||
icon: ../.icons/kasmvnc.svg
|
||||
maintainer_github: coder
|
||||
verified: true
|
||||
tags: [helper, vnc, desktop]
|
||||
---
|
||||
|
||||
# KasmVNC
|
||||
|
||||
Automatically install [KasmVNC](https://kasmweb.com/kasmvnc) in a workspace, and create an app to access it via the dashboard.
|
||||
|
||||
```tf
|
||||
module "kasmvnc" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/kasmvnc/coder"
|
||||
version = "1.0.23"
|
||||
agent_id = coder_agent.example.id
|
||||
desktop_environment = "xfce"
|
||||
}
|
||||
```
|
||||
|
||||
> **Note:** This module only works on workspaces with a pre-installed desktop environment. As an example base image you can use `codercom/enterprise-desktop` image.
|
||||
37
kasmvnc/main.test.ts
Normal file
37
kasmvnc/main.test.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { describe, expect, it } from "bun:test";
|
||||
import {
|
||||
runTerraformApply,
|
||||
runTerraformInit,
|
||||
testRequiredVariables,
|
||||
} from "../test";
|
||||
|
||||
const allowedDesktopEnvs = ["xfce", "kde", "gnome", "lxde", "lxqt"] as const;
|
||||
type AllowedDesktopEnv = (typeof allowedDesktopEnvs)[number];
|
||||
|
||||
type TestVariables = Readonly<{
|
||||
agent_id: string;
|
||||
desktop_environment: AllowedDesktopEnv;
|
||||
port?: string;
|
||||
kasm_version?: string;
|
||||
}>;
|
||||
|
||||
describe("Kasm VNC", async () => {
|
||||
await runTerraformInit(import.meta.dir);
|
||||
testRequiredVariables<TestVariables>(import.meta.dir, {
|
||||
agent_id: "foo",
|
||||
desktop_environment: "gnome",
|
||||
});
|
||||
|
||||
it("Successfully installs for all expected Kasm desktop versions", async () => {
|
||||
for (const v of allowedDesktopEnvs) {
|
||||
const applyWithEnv = () => {
|
||||
runTerraformApply<TestVariables>(import.meta.dir, {
|
||||
agent_id: "foo",
|
||||
desktop_environment: v,
|
||||
});
|
||||
};
|
||||
|
||||
expect(applyWithEnv).not.toThrow();
|
||||
}
|
||||
});
|
||||
});
|
||||
63
kasmvnc/main.tf
Normal file
63
kasmvnc/main.tf
Normal file
@@ -0,0 +1,63 @@
|
||||
terraform {
|
||||
required_version = ">= 1.0"
|
||||
|
||||
required_providers {
|
||||
coder = {
|
||||
source = "coder/coder"
|
||||
version = ">= 0.12"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
variable "agent_id" {
|
||||
type = string
|
||||
description = "The ID of a Coder agent."
|
||||
}
|
||||
|
||||
variable "port" {
|
||||
type = number
|
||||
description = "The port to run KasmVNC on."
|
||||
default = 6800
|
||||
}
|
||||
|
||||
variable "kasm_version" {
|
||||
type = string
|
||||
description = "Version of KasmVNC to install."
|
||||
default = "1.3.2"
|
||||
}
|
||||
|
||||
variable "desktop_environment" {
|
||||
type = string
|
||||
description = "Specifies the desktop environment of the workspace. This should be pre-installed on the workspace."
|
||||
validation {
|
||||
condition = contains(["xfce", "kde", "gnome", "lxde", "lxqt"], var.desktop_environment)
|
||||
error_message = "Invalid desktop environment. Please specify a valid desktop environment."
|
||||
}
|
||||
}
|
||||
|
||||
resource "coder_script" "kasm_vnc" {
|
||||
agent_id = var.agent_id
|
||||
display_name = "KasmVNC"
|
||||
icon = "/icon/kasmvnc.svg"
|
||||
script = templatefile("${path.module}/run.sh", {
|
||||
PORT : var.port,
|
||||
DESKTOP_ENVIRONMENT : var.desktop_environment,
|
||||
KASM_VERSION : var.kasm_version
|
||||
})
|
||||
run_on_start = true
|
||||
}
|
||||
|
||||
resource "coder_app" "kasm_vnc" {
|
||||
agent_id = var.agent_id
|
||||
slug = "kasm-vnc"
|
||||
display_name = "kasmVNC"
|
||||
url = "http://localhost:${var.port}"
|
||||
icon = "/icon/kasmvnc.svg"
|
||||
subdomain = true
|
||||
share = "owner"
|
||||
healthcheck {
|
||||
url = "http://localhost:${var.port}/app"
|
||||
interval = 5
|
||||
threshold = 5
|
||||
}
|
||||
}
|
||||
235
kasmvnc/run.sh
Normal file
235
kasmvnc/run.sh
Normal file
@@ -0,0 +1,235 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Exit on error, undefined variables, and pipe failures
|
||||
set -euo pipefail
|
||||
|
||||
# Function to check if vncserver is already installed
|
||||
check_installed() {
|
||||
if command -v vncserver &> /dev/null; then
|
||||
echo "vncserver is already installed."
|
||||
return 0 # Don't exit, just indicate it's installed
|
||||
else
|
||||
return 1 # Indicates not installed
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to download a file using wget, curl, or busybox as a fallback
|
||||
download_file() {
|
||||
local url="$1"
|
||||
local output="$2"
|
||||
local download_tool
|
||||
|
||||
if command -v curl &> /dev/null; then
|
||||
# shellcheck disable=SC2034
|
||||
download_tool=(curl -fsSL)
|
||||
elif command -v wget &> /dev/null; then
|
||||
# shellcheck disable=SC2034
|
||||
download_tool=(wget -q -O-)
|
||||
elif command -v busybox &> /dev/null; then
|
||||
# shellcheck disable=SC2034
|
||||
download_tool=(busybox wget -O-)
|
||||
else
|
||||
echo "ERROR: No download tool available (curl, wget, or busybox required)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# shellcheck disable=SC2288
|
||||
"$${download_tool[@]}" "$url" > "$output" || {
|
||||
echo "ERROR: Failed to download $url"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
# Function to install kasmvncserver for debian-based distros
|
||||
install_deb() {
|
||||
local url=$1
|
||||
local kasmdeb="/tmp/kasmvncserver.deb"
|
||||
|
||||
download_file "$url" "$kasmdeb"
|
||||
|
||||
CACHE_DIR="/var/lib/apt/lists/partial"
|
||||
# Check if the directory exists and was modified in the last 60 minutes
|
||||
if [[ ! -d "$CACHE_DIR" ]] || ! find "$CACHE_DIR" -mmin -60 -print -quit &> /dev/null; then
|
||||
echo "Stale package cache, updating..."
|
||||
# Update package cache with a 300-second timeout for dpkg lock
|
||||
sudo apt-get -o DPkg::Lock::Timeout=300 -qq update
|
||||
fi
|
||||
|
||||
DEBIAN_FRONTEND=noninteractive sudo apt-get -o DPkg::Lock::Timeout=300 install --yes -qq --no-install-recommends --no-install-suggests "$kasmdeb"
|
||||
rm "$kasmdeb"
|
||||
}
|
||||
|
||||
# Function to install kasmvncserver for rpm-based distros
|
||||
install_rpm() {
|
||||
local url=$1
|
||||
local kasmrpm="/tmp/kasmvncserver.rpm"
|
||||
local package_manager
|
||||
|
||||
if command -v dnf &> /dev/null; then
|
||||
# shellcheck disable=SC2034
|
||||
package_manager=(dnf localinstall -y)
|
||||
elif command -v zypper &> /dev/null; then
|
||||
# shellcheck disable=SC2034
|
||||
package_manager=(zypper install -y)
|
||||
elif command -v yum &> /dev/null; then
|
||||
# shellcheck disable=SC2034
|
||||
package_manager=(yum localinstall -y)
|
||||
elif command -v rpm &> /dev/null; then
|
||||
# Do we need to manually handle missing dependencies?
|
||||
# shellcheck disable=SC2034
|
||||
package_manager=(rpm -i)
|
||||
else
|
||||
echo "ERROR: No supported package manager available (dnf, zypper, yum, or rpm required)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
download_file "$url" "$kasmrpm"
|
||||
|
||||
# shellcheck disable=SC2288
|
||||
sudo "$${package_manager[@]}" "$kasmrpm" || {
|
||||
echo "ERROR: Failed to install $kasmrpm"
|
||||
exit 1
|
||||
}
|
||||
|
||||
rm "$kasmrpm"
|
||||
}
|
||||
|
||||
# Function to install kasmvncserver for Alpine Linux
|
||||
install_alpine() {
|
||||
local url=$1
|
||||
local kasmtgz="/tmp/kasmvncserver.tgz"
|
||||
|
||||
download_file "$url" "$kasmtgz"
|
||||
|
||||
tar -xzf "$kasmtgz" -C /usr/local/bin/
|
||||
rm "$kasmtgz"
|
||||
}
|
||||
|
||||
# Detect system information
|
||||
if [[ ! -f /etc/os-release ]]; then
|
||||
echo "ERROR: Cannot detect OS: /etc/os-release not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# shellcheck disable=SC1091
|
||||
source /etc/os-release
|
||||
distro="$ID"
|
||||
distro_version="$VERSION_ID"
|
||||
codename="$VERSION_CODENAME"
|
||||
arch="$(uname -m)"
|
||||
if [[ "$ID" == "ol" ]]; then
|
||||
distro="oracle"
|
||||
distro_version="$${distro_version%%.*}"
|
||||
elif [[ "$ID" == "fedora" ]]; then
|
||||
distro_version="$(grep -oP '\(\K[\w ]+' /etc/fedora-release | tr '[:upper:]' '[:lower:]' | tr -d ' ')"
|
||||
fi
|
||||
|
||||
echo "Detected Distribution: $distro"
|
||||
echo "Detected Version: $distro_version"
|
||||
echo "Detected Codename: $codename"
|
||||
echo "Detected Architecture: $arch"
|
||||
|
||||
# Map arch to package arch
|
||||
case "$arch" in
|
||||
x86_64)
|
||||
if [[ "$distro" =~ ^(ubuntu|debian|kali)$ ]]; then
|
||||
arch="amd64"
|
||||
fi
|
||||
;;
|
||||
aarch64)
|
||||
if [[ "$distro" =~ ^(ubuntu|debian|kali)$ ]]; then
|
||||
arch="arm64"
|
||||
fi
|
||||
;;
|
||||
arm64)
|
||||
: # This is effectively a noop
|
||||
;;
|
||||
*)
|
||||
echo "ERROR: Unsupported architecture: $arch"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# Check if vncserver is installed, and install if not
|
||||
if ! check_installed; then
|
||||
# Check for NOPASSWD sudo (required)
|
||||
if ! command -v sudo &> /dev/null || ! sudo -n true 2> /dev/null; then
|
||||
echo "ERROR: sudo NOPASSWD access required!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
base_url="https://github.com/kasmtech/KasmVNC/releases/download/v${KASM_VERSION}"
|
||||
|
||||
echo "Installing KASM version: ${KASM_VERSION}"
|
||||
case $distro in
|
||||
ubuntu | debian | kali)
|
||||
bin_name="kasmvncserver_$${codename}_${KASM_VERSION}_$${arch}.deb"
|
||||
install_deb "$base_url/$bin_name"
|
||||
;;
|
||||
oracle | fedora | opensuse)
|
||||
bin_name="kasmvncserver_$${distro}_$${distro_version}_${KASM_VERSION}_$${arch}.rpm"
|
||||
install_rpm "$base_url/$bin_name"
|
||||
;;
|
||||
alpine)
|
||||
bin_name="kasmvnc.alpine_$${distro_version//./}_$${arch}.tgz"
|
||||
install_alpine "$base_url/$bin_name"
|
||||
;;
|
||||
*)
|
||||
echo "Unsupported distribution: $distro"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
else
|
||||
echo "vncserver already installed. Skipping installation."
|
||||
fi
|
||||
|
||||
if command -v sudo &> /dev/null && sudo -n true 2> /dev/null; then
|
||||
kasm_config_file="/etc/kasmvnc/kasmvnc.yaml"
|
||||
SUDO=sudo
|
||||
else
|
||||
kasm_config_file="$HOME/.vnc/kasmvnc.yaml"
|
||||
SUDO=
|
||||
|
||||
echo "WARNING: Sudo access not available, using user config dir!"
|
||||
|
||||
if [[ -f "$kasm_config_file" ]]; then
|
||||
echo "WARNING: Custom user KasmVNC config exists, not overwriting!"
|
||||
echo "WARNING: Ensure that you manually configure the appropriate settings."
|
||||
kasm_config_file="/dev/stderr"
|
||||
else
|
||||
echo "WARNING: This may prevent custom user KasmVNC settings from applying!"
|
||||
mkdir -p "$HOME/.vnc"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "Writing KasmVNC config to $kasm_config_file"
|
||||
$SUDO tee "$kasm_config_file" > /dev/null << EOF
|
||||
network:
|
||||
protocol: http
|
||||
websocket_port: ${PORT}
|
||||
ssl:
|
||||
require_ssl: false
|
||||
pem_certificate:
|
||||
pem_key:
|
||||
udp:
|
||||
public_ip: 127.0.0.1
|
||||
EOF
|
||||
|
||||
# This password is not used since we start the server without auth.
|
||||
# The server is protected via the Coder session token / tunnel
|
||||
# and does not listen publicly
|
||||
echo -e "password\npassword\n" | vncpasswd -wo -u "$USER"
|
||||
|
||||
# Start the server
|
||||
printf "🚀 Starting KasmVNC server...\n"
|
||||
vncserver -select-de "${DESKTOP_ENVIRONMENT}" -disableBasicAuth > /tmp/kasmvncserver.log 2>&1 &
|
||||
pid=$!
|
||||
|
||||
# Wait for server to start
|
||||
sleep 5
|
||||
grep -v '^[[:space:]]*$' /tmp/kasmvncserver.log | tail -n 10
|
||||
if ps -p $pid | grep -q "^$pid"; then
|
||||
echo "ERROR: Failed to start KasmVNC server. Check full logs at /tmp/kasmvncserver.log"
|
||||
exit 1
|
||||
fi
|
||||
printf "🚀 KasmVNC server started successfully!\n"
|
||||
@@ -13,6 +13,7 @@ Automatically installs [Node.js](https://github.com/nodejs/node) via [nvm](https
|
||||
|
||||
```tf
|
||||
module "nodejs" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/nodejs/coder"
|
||||
version = "1.0.10"
|
||||
agent_id = coder_agent.example.id
|
||||
@@ -25,6 +26,7 @@ This installs multiple versions of Node.js:
|
||||
|
||||
```tf
|
||||
module "nodejs" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/nodejs/coder"
|
||||
version = "1.0.10"
|
||||
agent_id = coder_agent.example.id
|
||||
@@ -43,6 +45,7 @@ A example with all available options:
|
||||
|
||||
```tf
|
||||
module "nodejs" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/nodejs/coder"
|
||||
version = "1.0.10"
|
||||
agent_id = coder_agent.example.id
|
||||
|
||||
@@ -13,6 +13,7 @@ Run a script on workspace start that allows developers to run custom commands to
|
||||
|
||||
```tf
|
||||
module "personalize" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/personalize/coder"
|
||||
version = "1.0.2"
|
||||
agent_id = coder_agent.example.id
|
||||
|
||||
@@ -56,6 +56,7 @@ slackme npm run long-build
|
||||
|
||||
```tf
|
||||
module "slackme" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/slackme/coder"
|
||||
version = "1.0.2"
|
||||
agent_id = coder_agent.example.id
|
||||
@@ -72,6 +73,7 @@ slackme npm run long-build
|
||||
|
||||
```tf
|
||||
module "slackme" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/slackme/coder"
|
||||
version = "1.0.2"
|
||||
agent_id = coder_agent.example.id
|
||||
|
||||
@@ -21,14 +21,39 @@ for dir in "${changed_dirs[@]}"; do
|
||||
if [[ -f "$dir/README.md" ]]; then
|
||||
file="$dir/README.md"
|
||||
tmpfile=$(mktemp /tmp/tempfile.XXXXXX)
|
||||
awk -v tag="$LATEST_TAG" '{
|
||||
if ($1 == "version" && $2 == "=") {
|
||||
sub(/"[^"]*"/, "\"" tag "\"")
|
||||
print
|
||||
} else {
|
||||
awk -v tag="$LATEST_TAG" '
|
||||
BEGIN { in_code_block = 0; in_nested_block = 0 }
|
||||
{
|
||||
# Detect the start and end of Markdown code blocks.
|
||||
if ($0 ~ /^```/) {
|
||||
in_code_block = !in_code_block
|
||||
# Reset nested block tracking when exiting a code block.
|
||||
if (!in_code_block) {
|
||||
in_nested_block = 0
|
||||
}
|
||||
}
|
||||
|
||||
# Handle nested blocks within a code block.
|
||||
if (in_code_block) {
|
||||
# Detect the start of a nested block (skipping "module" blocks).
|
||||
if ($0 ~ /{/ && !($1 == "module" || $1 ~ /^[a-zA-Z0-9_]+$/)) {
|
||||
in_nested_block++
|
||||
}
|
||||
|
||||
# Detect the end of a nested block.
|
||||
if ($0 ~ /}/ && in_nested_block > 0) {
|
||||
in_nested_block--
|
||||
}
|
||||
|
||||
# Update "version" only if not in a nested block.
|
||||
if (!in_nested_block && $1 == "version" && $2 == "=") {
|
||||
sub(/"[^"]*"/, "\"" tag "\"")
|
||||
}
|
||||
}
|
||||
|
||||
print
|
||||
}
|
||||
}' "$file" > "$tmpfile" && mv "$tmpfile" "$file"
|
||||
' "$file" > "$tmpfile" && mv "$tmpfile" "$file"
|
||||
|
||||
# Check if the README.md file has changed
|
||||
if ! git diff --quiet -- "$dir/README.md"; then
|
||||
|
||||
@@ -14,6 +14,7 @@ This module lets you authenticate with [Hashicorp Vault](https://www.vaultprojec
|
||||
|
||||
```tf
|
||||
module "vault" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/vault-github/coder"
|
||||
version = "1.0.7"
|
||||
agent_id = coder_agent.example.id
|
||||
@@ -45,6 +46,7 @@ To configure the Vault module, you must set up a Vault GitHub auth method. See t
|
||||
|
||||
```tf
|
||||
module "vault" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/vault-github/coder"
|
||||
version = "1.0.7"
|
||||
agent_id = coder_agent.example.id
|
||||
@@ -57,6 +59,7 @@ module "vault" {
|
||||
|
||||
```tf
|
||||
module "vault" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/vault-github/coder"
|
||||
version = "1.0.7"
|
||||
agent_id = coder_agent.example.id
|
||||
@@ -70,6 +73,7 @@ module "vault" {
|
||||
|
||||
```tf
|
||||
module "vault" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/vault-github/coder"
|
||||
version = "1.0.7"
|
||||
agent_id = coder_agent.example.id
|
||||
|
||||
@@ -14,6 +14,7 @@ This module lets you authenticate with [Hashicorp Vault](https://www.vaultprojec
|
||||
|
||||
```tf
|
||||
module "vault" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/vault-jwt/coder"
|
||||
version = "1.0.20"
|
||||
agent_id = coder_agent.example.id
|
||||
@@ -40,6 +41,7 @@ curl -H "X-Vault-Token: ${VAULT_TOKEN}" -X GET "${VAULT_ADDR}/v1/coder/secrets/d
|
||||
|
||||
```tf
|
||||
module "vault" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/vault-jwt/coder"
|
||||
version = "1.0.20"
|
||||
agent_id = coder_agent.example.id
|
||||
@@ -55,6 +57,7 @@ module "vault" {
|
||||
data "coder_workspace_owner" "me" {}
|
||||
|
||||
module "vault" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/vault-jwt/coder"
|
||||
version = "1.0.20"
|
||||
agent_id = coder_agent.example.id
|
||||
@@ -67,6 +70,7 @@ module "vault" {
|
||||
|
||||
```tf
|
||||
module "vault" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/vault-jwt/coder"
|
||||
version = "1.0.20"
|
||||
agent_id = coder_agent.example.id
|
||||
|
||||
@@ -15,6 +15,7 @@ Uses the [Coder Remote VS Code Extension](https://github.com/coder/vscode-coder)
|
||||
|
||||
```tf
|
||||
module "vscode" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/vscode-desktop/coder"
|
||||
version = "1.0.15"
|
||||
agent_id = coder_agent.example.id
|
||||
@@ -27,6 +28,7 @@ module "vscode" {
|
||||
|
||||
```tf
|
||||
module "vscode" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/vscode-desktop/coder"
|
||||
version = "1.0.15"
|
||||
agent_id = coder_agent.example.id
|
||||
|
||||
@@ -13,8 +13,9 @@ Automatically install [Visual Studio Code Server](https://code.visualstudio.com/
|
||||
|
||||
```tf
|
||||
module "vscode-web" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/vscode-web/coder"
|
||||
version = "1.0.20"
|
||||
version = "1.0.26"
|
||||
agent_id = coder_agent.example.id
|
||||
accept_license = true
|
||||
}
|
||||
@@ -28,8 +29,9 @@ module "vscode-web" {
|
||||
|
||||
```tf
|
||||
module "vscode-web" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/vscode-web/coder"
|
||||
version = "1.0.20"
|
||||
version = "1.0.26"
|
||||
agent_id = coder_agent.example.id
|
||||
install_prefix = "/home/coder/.vscode-web"
|
||||
folder = "/home/coder"
|
||||
@@ -41,8 +43,9 @@ module "vscode-web" {
|
||||
|
||||
```tf
|
||||
module "vscode-web" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/vscode-web/coder"
|
||||
version = "1.0.20"
|
||||
version = "1.0.26"
|
||||
agent_id = coder_agent.example.id
|
||||
extensions = ["github.copilot", "ms-python.python", "ms-toolsai.jupyter"]
|
||||
accept_license = true
|
||||
@@ -55,8 +58,9 @@ Configure VS Code's [settings.json](https://code.visualstudio.com/docs/getstarte
|
||||
|
||||
```tf
|
||||
module "vscode-web" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/vscode-web/coder"
|
||||
version = "1.0.20"
|
||||
version = "1.0.26"
|
||||
agent_id = coder_agent.example.id
|
||||
extensions = ["dracula-theme.theme-dracula"]
|
||||
settings = {
|
||||
|
||||
@@ -92,7 +92,7 @@ variable "order" {
|
||||
}
|
||||
|
||||
variable "settings" {
|
||||
type = map(string)
|
||||
type = any
|
||||
description = "A map of settings to apply to VS Code web."
|
||||
default = {}
|
||||
}
|
||||
@@ -121,6 +121,18 @@ variable "auto_install_extensions" {
|
||||
default = false
|
||||
}
|
||||
|
||||
variable "subdomain" {
|
||||
type = bool
|
||||
description = <<-EOT
|
||||
Determines whether the app will be accessed via it's own subdomain or whether it will be accessed via a path on Coder.
|
||||
If wildcards have not been setup by the administrator then apps with "subdomain" set to true will not be accessible.
|
||||
EOT
|
||||
default = true
|
||||
}
|
||||
|
||||
data "coder_workspace_owner" "me" {}
|
||||
data "coder_workspace" "me" {}
|
||||
|
||||
resource "coder_script" "vscode-web" {
|
||||
agent_id = var.agent_id
|
||||
display_name = "VS Code Web"
|
||||
@@ -138,6 +150,7 @@ resource "coder_script" "vscode-web" {
|
||||
EXTENSIONS_DIR : var.extensions_dir,
|
||||
FOLDER : var.folder,
|
||||
AUTO_INSTALL_EXTENSIONS : var.auto_install_extensions,
|
||||
SERVER_BASE_PATH : local.server_base_path,
|
||||
})
|
||||
run_on_start = true
|
||||
|
||||
@@ -158,15 +171,21 @@ resource "coder_app" "vscode-web" {
|
||||
agent_id = var.agent_id
|
||||
slug = var.slug
|
||||
display_name = var.display_name
|
||||
url = var.folder == "" ? "http://localhost:${var.port}" : "http://localhost:${var.port}?folder=${var.folder}"
|
||||
url = local.url
|
||||
icon = "/icon/code.svg"
|
||||
subdomain = true
|
||||
subdomain = var.subdomain
|
||||
share = var.share
|
||||
order = var.order
|
||||
|
||||
healthcheck {
|
||||
url = "http://localhost:${var.port}/healthz"
|
||||
url = local.healthcheck_url
|
||||
interval = 5
|
||||
threshold = 6
|
||||
}
|
||||
}
|
||||
|
||||
locals {
|
||||
server_base_path = var.subdomain ? "" : format("/@%s/%s/apps/%s/", data.coder_workspace_owner.me.name, data.coder_workspace.me.name, var.slug)
|
||||
url = var.folder == "" ? "http://localhost:${var.port}${local.server_base_path}" : "http://localhost:${var.port}${local.server_base_path}?folder=${var.folder}"
|
||||
healthcheck_url = var.subdomain ? "http://localhost:${var.port}/healthz" : "http://localhost:${var.port}${local.server_base_path}/healthz"
|
||||
}
|
||||
|
||||
@@ -10,10 +10,16 @@ if [ -n "${EXTENSIONS_DIR}" ]; then
|
||||
EXTENSION_ARG="--extensions-dir=${EXTENSIONS_DIR}"
|
||||
fi
|
||||
|
||||
# Set extension directory
|
||||
SERVER_BASE_PATH_ARG=""
|
||||
if [ -n "${SERVER_BASE_PATH}" ]; then
|
||||
SERVER_BASE_PATH_ARG="--server-base-path=${SERVER_BASE_PATH}"
|
||||
fi
|
||||
|
||||
run_vscode_web() {
|
||||
echo "👷 Running $VSCODE_WEB serve-local $EXTENSION_ARG --port ${PORT} --host 127.0.0.1 --accept-server-license-terms --without-connection-token --telemetry-level ${TELEMETRY_LEVEL} in the background..."
|
||||
echo "👷 Running $VSCODE_WEB serve-local $EXTENSION_ARG $SERVER_BASE_PATH_ARG --port ${PORT} --host 127.0.0.1 --accept-server-license-terms --without-connection-token --telemetry-level ${TELEMETRY_LEVEL} in the background..."
|
||||
echo "Check logs at ${LOG_PATH}!"
|
||||
"$VSCODE_WEB" serve-local "$EXTENSION_ARG" --port "${PORT}" --host 127.0.0.1 --accept-server-license-terms --without-connection-token --telemetry-level "${TELEMETRY_LEVEL}" > "${LOG_PATH}" 2>&1 &
|
||||
"$VSCODE_WEB" serve-local "$EXTENSION_ARG" "$SERVER_BASE_PATH_ARG" --port "${PORT}" --host 127.0.0.1 --accept-server-license-terms --without-connection-token --telemetry-level "${TELEMETRY_LEVEL}" > "${LOG_PATH}" 2>&1 &
|
||||
}
|
||||
|
||||
# Check if the settings file exists...
|
||||
|
||||
@@ -14,9 +14,9 @@ Enable Remote Desktop + a web based client on Windows workspaces, powered by [de
|
||||
```tf
|
||||
# AWS example. See below for examples of using this module with other providers
|
||||
module "windows_rdp" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/windows-rdp/coder"
|
||||
version = "1.0.18"
|
||||
count = data.coder_workspace.me.start_count
|
||||
agent_id = resource.coder_agent.main.id
|
||||
resource_id = resource.aws_instance.dev.id
|
||||
}
|
||||
@@ -32,9 +32,9 @@ module "windows_rdp" {
|
||||
|
||||
```tf
|
||||
module "windows_rdp" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/windows-rdp/coder"
|
||||
version = "1.0.18"
|
||||
count = data.coder_workspace.me.start_count
|
||||
agent_id = resource.coder_agent.main.id
|
||||
resource_id = resource.aws_instance.dev.id
|
||||
}
|
||||
@@ -44,9 +44,9 @@ module "windows_rdp" {
|
||||
|
||||
```tf
|
||||
module "windows_rdp" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/windows-rdp/coder"
|
||||
version = "1.0.18"
|
||||
count = data.coder_workspace.me.start_count
|
||||
agent_id = resource.coder_agent.main.id
|
||||
resource_id = resource.google_compute_instance.dev[0].id
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user