Skip to content

Commit

Permalink
feat: add Amazon DCV Windows module
Browse files Browse the repository at this point in the history
- Introduce Amazon DCV for remote desktop on Windows workspaces.
- Script to install and configure DCV components.
- Terraform setup for configuring resources like coder_app.
- Ensure secure management with sensitive variable handling.

chore: add health check badge (#341)

chore: integrate Instatus in check script (#342)
  • Loading branch information
matifali committed Nov 20, 2024
1 parent 57d96ca commit 576d701
Show file tree
Hide file tree
Showing 7 changed files with 399 additions and 10 deletions.
91 changes: 84 additions & 7 deletions .github/scripts/check.sh
Original file line number Diff line number Diff line change
@@ -1,24 +1,85 @@
#!/usr/bin/env bash
set -o pipefail
REGISTRY_BASE_URL="${REGISTRY_BASE_URL:-https://registry.coder.com}"
set -u

if [[ -n "${VERBOSE:-}" ]]; then
set -x
fi
# List of required environment variables
required_vars=(
"INSTATUS_API_KEY"
"INSTATUS_PAGE_ID"
"INSTATUS_COMPONENT_ID"
)

# 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 . -not -path '*/.*' -type f -name main.tf -maxdepth 2 | 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="Testing Instatus"
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
response=$(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\"
}
]
}")

incident_id=$(echo "$response" | jq -r '.id')
echo "$incident_id"
}

# 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 "=== Check module %s at %s\n" "${module}" "${url}"
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}")
# shellcheck disable=SC2181
if (( status_code != 200 )); then
Expand All @@ -30,7 +91,23 @@ for module in "${modules[@]}"; do
fi
done

if (( status != 0 )); then
echo "The following modules appear to have issues: ${failures[*]}"
# 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

# Create a new incident
incident_id=$(create_incident)
echo "Created incident with ID: $incident_id"
fi

exit "${status}"
10 changes: 7 additions & 3 deletions .github/workflows/check.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: Check modules on registry.coder.com

name: Health
# Check modules health on registry.coder.com
on:
schedule:
- cron: "*/13 * * * *" # Runs every 13th minute
Expand All @@ -11,8 +11,12 @@ jobs:

steps:
- name: Checkout repository
uses: actions/checkout@v2
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 }}
1 change: 1 addition & 0 deletions .icons/dcv.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

[![discord](https://img.shields.io/discord/747933592273027093?label=discord)](https://discord.gg/coder)
[![license](https://img.shields.io/github/license/coder/modules)](./LICENSE)
[![Health](https://github.com/coder/modules/actions/workflows/check.yaml/badge.svg)](https://github.com/coder/modules/actions/workflows/check.yaml)

</div>

Expand Down
48 changes: 48 additions & 0 deletions amazon-dcv-windows/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
---
display_name: Amazon DCV Windows
description: Amazon DCV Server and Web Client for Windows
icon: ../.icons/dcv.svg
maintainer_github: coder
partner_github: aws
verified: true
tags: [windows, 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.
173 changes: 173 additions & 0 deletions amazon-dcv-windows/install-dcv.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
# 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 (($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\connectivity" -Name enable-quic-frontend -PropertyType DWORD -Value 1 -Force
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\display" -Name target-fps -PropertyType DWORD -Value 60 -Force
New-ItemProperty -Path "$dcvPath\connectivity" -Name enable-datagrams-display -Value "always-off" -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"
Loading

0 comments on commit 576d701

Please sign in to comment.