Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 26 additions & 4 deletions registry/coder/modules/windows-rdp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
```
Expand All @@ -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
}
```
Expand All @@ -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
}
```
Expand All @@ -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
33 changes: 33 additions & 0 deletions registry/coder/modules/windows-rdp/keepalive-script.tftpl
Original file line number Diff line number Diff line change
@@ -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
}
60 changes: 60 additions & 0 deletions registry/coder/modules/windows-rdp/main.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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,
Expand Down Expand Up @@ -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<TestVariables>(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<TestVariables>(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<TestVariables>(import.meta.dir, {
agent_id: "foo",
keepalive: true,
keepalive_interval: customInterval,
});

const keepaliveScript = findKeepaliveScript(state);
expect(keepaliveScript).toBeString();
expect(keepaliveScript).toContain(`$checkInterval = ${customInterval}`);
});
});
30 changes: 30 additions & 0 deletions registry/coder/modules/windows-rdp/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand Down
61 changes: 61 additions & 0 deletions registry/coder/modules/windows-rdp/main.tftest.hcl
Original file line number Diff line number Diff line change
@@ -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"
}
}