From ac103db216e91acff096869563182625f8af7f7c Mon Sep 17 00:00:00 2001 From: Akinniranye Samuel Tomiwa Date: Wed, 4 Feb 2026 16:17:20 +0100 Subject: [PATCH] feat: add RDP keep-alive to windows-rdp module --- registry/coder/modules/windows-rdp/README.md | 30 +++++++-- .../windows-rdp/keepalive-script.tftpl | 33 ++++++++++ .../coder/modules/windows-rdp/main.test.ts | 60 ++++++++++++++++++ registry/coder/modules/windows-rdp/main.tf | 30 +++++++++ .../coder/modules/windows-rdp/main.tftest.hcl | 61 +++++++++++++++++++ 5 files changed, 210 insertions(+), 4 deletions(-) create mode 100644 registry/coder/modules/windows-rdp/keepalive-script.tftpl create mode 100644 registry/coder/modules/windows-rdp/main.tftest.hcl diff --git a/registry/coder/modules/windows-rdp/README.md b/registry/coder/modules/windows-rdp/README.md index 111e8d7fd..904c40c40 100644 --- a/registry/coder/modules/windows-rdp/README.md +++ b/registry/coder/modules/windows-rdp/README.md @@ -15,7 +15,7 @@ Enable Remote Desktop + a web based client on Windows workspaces, powered by [de module "windows_rdp" { count = data.coder_workspace.me.start_count source = "registry.coder.com/coder/windows-rdp/coder" - version = "1.3.0" + version = "1.4.0" agent_id = coder_agent.main.id } ``` @@ -32,7 +32,7 @@ module "windows_rdp" { module "windows_rdp" { count = data.coder_workspace.me.start_count source = "registry.coder.com/coder/windows-rdp/coder" - version = "1.3.0" + version = "1.4.0" agent_id = coder_agent.main.id } ``` @@ -43,7 +43,7 @@ module "windows_rdp" { module "windows_rdp" { count = data.coder_workspace.me.start_count source = "registry.coder.com/coder/windows-rdp/coder" - version = "1.3.0" + version = "1.4.0" agent_id = coder_agent.main.id } ``` @@ -54,8 +54,30 @@ module "windows_rdp" { module "windows_rdp" { count = data.coder_workspace.me.start_count source = "registry.coder.com/coder/windows-rdp/coder" - version = "1.3.0" + version = "1.4.0" agent_id = coder_agent.main.id devolutions_gateway_version = "2025.2.2" # Specify a specific version } ``` + +### With Keep-Alive for Active RDP Sessions + +Enable automatic workspace session extension while RDP connections are active. This prevents workspace shutdown during remote desktop use: + +```tf +module "windows_rdp" { + count = data.coder_workspace.me.start_count + source = "registry.coder.com/coder/windows-rdp/coder" + version = "1.4.0" + agent_id = coder_agent.main.id + keepalive = true # Enable RDP connection monitoring + keepalive_interval = 300 # Check every 5 minutes (default) +} +``` + +The keep-alive feature monitors active RDP connections (port 3389) and reports workspace activity to Coder. When enabled: + +- Workspace remains active while RDP sessions are connected +- Activity checks occur at the specified interval (default: 5 minutes) +- Session timeout resumes normal countdown after RDP disconnection +- No manual intervention required from users diff --git a/registry/coder/modules/windows-rdp/keepalive-script.tftpl b/registry/coder/modules/windows-rdp/keepalive-script.tftpl new file mode 100644 index 000000000..6ae245b4e --- /dev/null +++ b/registry/coder/modules/windows-rdp/keepalive-script.tftpl @@ -0,0 +1,33 @@ +# RDP Keep-Alive Script +# Monitors active RDP connections and reports activity to prevent workspace shutdown + +$checkInterval = ${keepalive_interval} + +Write-Host "Starting RDP keep-alive monitor (checking every $checkInterval seconds)..." + +while ($true) { + try { + # Check for active RDP connections on port 3389 + $rdpConnections = Get-NetTCPConnection -LocalPort 3389 -State Established -ErrorAction SilentlyContinue + + if ($rdpConnections) { + $connectionCount = ($rdpConnections | Measure-Object).Count + Write-Host "[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] Active RDP connection(s) detected ($connectionCount). Reporting workspace activity..." + + # Report activity to Coder to extend workspace deadline + # The coder CLI will use the agent token automatically + & coder stat connectivity > $null 2>&1 + + if ($LASTEXITCODE -eq 0) { + Write-Host "[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] Activity reported successfully." + } else { + Write-Host "[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] Warning: Failed to report activity (exit code: $LASTEXITCODE)" + } + } + } + catch { + Write-Host "[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] Error checking RDP connections: $_" + } + + Start-Sleep -Seconds $checkInterval +} diff --git a/registry/coder/modules/windows-rdp/main.test.ts b/registry/coder/modules/windows-rdp/main.test.ts index 80c09fd0d..a3bd592d0 100644 --- a/registry/coder/modules/windows-rdp/main.test.ts +++ b/registry/coder/modules/windows-rdp/main.test.ts @@ -11,6 +11,8 @@ type TestVariables = Readonly<{ share?: string; admin_username?: string; admin_password?: string; + keepalive?: boolean; + keepalive_interval?: number; }>; function findWindowsRdpScript(state: TerraformState): string | null { @@ -35,6 +37,29 @@ function findWindowsRdpScript(state: TerraformState): string | null { return null; } +function findKeepaliveScript(state: TerraformState): string | null { + for (const resource of state.resources) { + const isKeepaliveScriptResource = + resource.type === "coder_script" && + resource.name === "windows-rdp-keepalive"; + + if (!isKeepaliveScriptResource) { + continue; + } + + for (const instance of resource.instances) { + if ( + instance.attributes.display_name === "windows-rdp-keepalive" && + typeof instance.attributes.script === "string" + ) { + return instance.attributes.script; + } + } + } + + return null; +} + /** * @todo It would be nice if we had a way to verify that the Devolutions root * HTML file is modified to include the import for the patched Coder script, @@ -128,4 +153,39 @@ describe("Web RDP", async () => { expect(customResultsGroup.username).toBe(customAdminUsername); expect(customResultsGroup.password).toBe(customAdminPassword); }); + + it("Does not create keepalive script when keepalive is disabled (default)", async () => { + const state = await runTerraformApply(import.meta.dir, { + agent_id: "foo", + }); + + const keepaliveScript = findKeepaliveScript(state); + expect(keepaliveScript).toBeNull(); + }); + + it("Creates keepalive script when keepalive is enabled", async () => { + const state = await runTerraformApply(import.meta.dir, { + agent_id: "foo", + keepalive: true, + }); + + const keepaliveScript = findKeepaliveScript(state); + expect(keepaliveScript).toBeString(); + expect(keepaliveScript).toContain("Get-NetTCPConnection"); + expect(keepaliveScript).toContain("-LocalPort 3389"); + expect(keepaliveScript).toContain("coder stat connectivity"); + }); + + it("Uses correct keepalive interval", async () => { + const customInterval = 600; + const state = await runTerraformApply(import.meta.dir, { + agent_id: "foo", + keepalive: true, + keepalive_interval: customInterval, + }); + + const keepaliveScript = findKeepaliveScript(state); + expect(keepaliveScript).toBeString(); + expect(keepaliveScript).toContain(`$checkInterval = ${customInterval}`); + }); }); diff --git a/registry/coder/modules/windows-rdp/main.tf b/registry/coder/modules/windows-rdp/main.tf index 3c83d195b..c5eab8990 100644 --- a/registry/coder/modules/windows-rdp/main.tf +++ b/registry/coder/modules/windows-rdp/main.tf @@ -70,6 +70,22 @@ variable "devolutions_gateway_version" { description = "Version of Devolutions Gateway to install. Use 'latest' for the most recent version, or specify a version like '2025.3.2'." } +variable "keepalive" { + type = bool + default = false + description = "Enable automatic workspace session extension while RDP is active. When enabled, the workspace will remain active as long as an RDP connection exists." +} + +variable "keepalive_interval" { + type = number + default = 300 + description = "Interval in seconds between RDP connection checks when keepalive is enabled. Default is 300 seconds (5 minutes)." + validation { + condition = var.keepalive_interval >= 60 && var.keepalive_interval <= 3600 + error_message = "keepalive_interval must be between 60 and 3600 seconds." + } +} + resource "coder_script" "windows-rdp" { agent_id = var.agent_id display_name = "windows-rdp" @@ -110,6 +126,20 @@ resource "coder_app" "windows-rdp" { } } +resource "coder_script" "windows-rdp-keepalive" { + count = var.keepalive ? 1 : 0 + agent_id = var.agent_id + display_name = "windows-rdp-keepalive" + icon = "/icon/rdp.svg" + + script = templatefile("${path.module}/keepalive-script.tftpl", { + keepalive_interval = var.keepalive_interval + }) + + run_on_start = true + start_blocks_login = false +} + resource "coder_app" "rdp-docs" { agent_id = var.agent_id display_name = "Local RDP Docs" diff --git a/registry/coder/modules/windows-rdp/main.tftest.hcl b/registry/coder/modules/windows-rdp/main.tftest.hcl new file mode 100644 index 000000000..b4236d0ec --- /dev/null +++ b/registry/coder/modules/windows-rdp/main.tftest.hcl @@ -0,0 +1,61 @@ +run "basic_module_test" { + command = plan + + variables { + agent_id = "test-agent-id" + } + + assert { + condition = coder_script.windows-rdp.agent_id == "test-agent-id" + error_message = "Windows RDP script should be created with correct agent_id" + } + + assert { + condition = coder_app.windows-rdp.agent_id == "test-agent-id" + error_message = "Windows RDP app should be created with correct agent_id" + } + + assert { + condition = length(coder_script.windows-rdp-keepalive) == 0 + error_message = "Keepalive script should NOT be created when keepalive is disabled (default)" + } +} + +run "keepalive_enabled_test" { + command = plan + + variables { + agent_id = "test-agent-id" + keepalive = true + } + + assert { + condition = length(coder_script.windows-rdp-keepalive) == 1 + error_message = "Keepalive script should be created when keepalive is enabled" + } + + assert { + condition = coder_script.windows-rdp-keepalive[0].display_name == "windows-rdp-keepalive" + error_message = "Keepalive script should have correct display name" + } + + assert { + condition = coder_script.windows-rdp-keepalive[0].start_blocks_login == false + error_message = "Keepalive script should not block login" + } +} + +run "custom_keepalive_interval_test" { + command = plan + + variables { + agent_id = "test-agent-id" + keepalive = true + keepalive_interval = 600 + } + + assert { + condition = length(coder_script.windows-rdp-keepalive) == 1 + error_message = "Keepalive script should be created with custom interval" + } +}