Skip to content

Conversation

@awakecoding
Copy link
Contributor

Overview

Extends the Devolutions Agent auto-updater to support Devolutions Hub Service MSI updates, using the same mechanism as Gateway updates.

Changes

Core Implementation

  • Hub Service MSI upgrade code: f437046e-8e13-430a-8c8f-29fcb9023b59
  • Product ID: HubServicesbin (from productinfo.htm)
  • Windows Services:
    • Devolutions Hub PAM Service (main)
    • Devolutions Hub Encryption Service (optional)
    • Devolutions Hub Reporting Service (optional)

Code Changes

  1. crates/devolutions-agent-shared/src/windows/mod.rs: Added HUB_SERVICE_UPDATE_CODE constant
  2. crates/devolutions-agent-shared/src/update_json.rs: Extended schema with optional hub_service field
  3. devolutions-agent/src/updater/productinfo/mod.rs: Added HUB_SERVICE_PRODUCT_ID
  4. devolutions-agent/src/updater/product.rs: Added HubService product variant
  5. devolutions-agent/src/updater/detect.rs: Extended detection logic for Hub Service
  6. devolutions-agent/src/updater/package.rs: Extended MSI operations for Hub Service
  7. devolutions-agent/src/updater/product_actions.rs: Refactored to ServiceUpdateActions supporting multiple services per product
  8. devolutions-agent/src/updater/mod.rs: Added HubService to PRODUCTS array

Key Features

  • ✅ Handles multiple Windows services per product (3 Hub services)
  • ✅ Gracefully handles optional services that may not be installed
  • ✅ Preserves individual service states and startup modes during updates
  • ✅ Backward compatible - no breaking changes to Gateway updater
  • ✅ Extended JSON schema with optional fields only

Testing Instructions

Prerequisites

  1. Install Devolutions Hub Service on test machine (any combination of PAM/Encryption/Reporting features)
  2. Build and deploy Devolutions Agent with these changes
  3. Verify Agent service is running

Test 1: Manual Update Trigger

Verify current state:

# Check installed Hub services
Get-Service | Where-Object { $_.DisplayName -like "Devolutions Hub*" } | Select-Object Name, DisplayName, Status, StartType | Format-Table -AutoSize

Create update.json:

$updateJson = @{
    HubService = @{
        TargetVersion = "2025.4.0.0"  # Replace with actual newer version
        DownloadUrl = "https://cdn.devolutions.net/download/Setup.DevolutionsHubServices.2025.4.0.0.msi"
        Checksum = @{
            Algorithm = "SHA256"
            Value = "abc123..."  # Replace with actual SHA256 hash
        }
    }
} | ConvertTo-Json -Depth 10

$updateJson | Out-File -FilePath "$env:ProgramData\Devolutions\Agent\update.json" -Encoding utf8 -Force

Monitor logs:

Get-Content "$env:ProgramData\Devolutions\Agent\logs\gateway.log" -Wait -Tail 50

Expected log sequence (within 3 seconds):

[INFO] File watcher detected update.json change
[INFO] Processing update request for HubService
[INFO] Querying service states for HubService
[INFO] Service 'Devolutions Hub PAM Service' found - running: true, automatic_startup: true
[DEBUG] Service 'Devolutions Hub Encryption Service' not found (feature not installed): ...
[DEBUG] Service 'Devolutions Hub Reporting Service' not found (feature not installed): ...
[INFO] Current HubService version: X.X.X.X
[INFO] Target HubService version: Y.Y.Y.Y
[INFO] Update required for HubService
[INFO] Downloading package...
[INFO] Checksum validation passed
[INFO] Package signature valid
[INFO] Uninstalling current HubService version...
[INFO] Installing HubService version Y.Y.Y.Y...
[INFO] HubService update completed successfully

Verify results:

# Check new version installed
Get-Service | Where-Object { $_.DisplayName -like "Devolutions Hub*" } | Select-Object Name, Status, StartType | Format-Table -AutoSize

Test 2: Service State Preservation

Set PAM service to Manual startup:

Set-Service -Name "Devolutions Hub PAM Service" -StartupType Manual
Stop-Service -Name "Devolutions Hub PAM Service"
Start-Service -Name "Devolutions Hub PAM Service"

Trigger update (same as Test 1)

Expected behavior:

  • Service should be restarted after update (because it was running with Manual startup)
  • StartType should remain Manual (not changed to Automatic)

Verify:

Get-Service "Devolutions Hub PAM Service" | Select-Object Status, StartType
# Expected: Status=Running, StartType=Manual

Test 3: Multiple Services Installed

Install all Hub Service features (PAM, Encryption, Reporting)

Set different startup types:

Set-Service "Devolutions Hub PAM Service" -StartupType Automatic
Set-Service "Devolutions Hub Encryption Service" -StartupType Manual
Set-Service "Devolutions Hub Reporting Service" -StartupType Automatic
Start-Service "Devolutions Hub PAM Service", "Devolutions Hub Encryption Service", "Devolutions Hub Reporting Service"

Trigger update

Expected:

  • All 3 services detected in logs
  • Encryption service manually restarted (Manual startup + was running)
  • PAM and Reporting services auto-restarted (Automatic startup)
  • All startup types preserved after update

Test 4: Already Up-to-Date

Create update.json with current version (same as installed)

Expected log:

[INFO] HubService is already up-to-date

No installation should occur.

Test 5: Backward Compatibility

Create update.json for Gateway only (no hub_service field):

$updateJson = @{
    Gateway = @{
        TargetVersion = "2025.3.0.0"
        DownloadUrl = "..."
        Checksum = @{ Algorithm = "SHA256"; Value = "..." }
    }
} | ConvertTo-Json -Depth 10

Expected: Gateway updates normally, Hub Service ignored (backward compatible).

Test 6: Negative Test - Invalid Checksum

Create update.json with wrong checksum:

$updateJson = @{
    HubService = @{
        TargetVersion = "2025.4.0.0"
        DownloadUrl = "https://cdn.devolutions.net/download/Setup.DevolutionsHubServices.2025.4.0.0.msi"
        Checksum = @{
            Algorithm = "SHA256"
            Value = "0000000000000000000000000000000000000000000000000000000000000000"
        }
    }
} | ConvertTo-Json -Depth 10

Expected:

[ERROR] Checksum validation failed for HubService package
[ERROR] Update aborted for HubService

Installation should NOT proceed.

Validation Script

# Test-HubServiceUpdater.ps1
function Test-HubServiceUpdater {
    Write-Host "=== Hub Service Updater Test Suite ===" -ForegroundColor Cyan
    
    # Check Agent
    $agent = Get-Service DevolutionsAgent -ErrorAction SilentlyContinue
    if (-not $agent -or $agent.Status -ne 'Running') {
        Write-Host "ERROR: Devolutions Agent not running" -ForegroundColor Red
        return
    }
    Write-Host "✓ Devolutions Agent is running" -ForegroundColor Green
    
    # Check Hub Services
    $hubServices = Get-Service | Where-Object { $_.DisplayName -like "Devolutions Hub*" }
    if ($hubServices.Count -eq 0) {
        Write-Host "ERROR: No Hub Services found" -ForegroundColor Red
        return
    }
    Write-Host "✓ Found $($hubServices.Count) Hub Service(s):" -ForegroundColor Green
    $hubServices | Format-Table Name, DisplayName, Status, StartType -AutoSize
    
    Write-Host "`n✓ Ready to test!" -ForegroundColor Cyan
    Write-Host "  Create update.json at: $env:ProgramData\Devolutions\Agent\update.json"
    Write-Host "  Watch logs at: $env:ProgramData\Devolutions\Agent\logs\gateway.log"
}

Test-HubServiceUpdater

Notes

  • No breaking changes to existing Gateway updater functionality
  • JSON schema extended with optional hub_service field (backward compatible)
  • All MSI operations use the same proven logic as Gateway (signature validation, checksum verification)
  • Service management generalized to support products with multiple Windows services
  • Graceful degradation when optional Hub Service features are not installed

@github-actions
Copy link

github-actions bot commented Nov 3, 2025

Let maintainers know that an action is required on their side

  • Add the label release-required Please cut a new release (Devolutions Gateway, Devolutions Agent, Jetsocat, PowerShell module) when you request a maintainer to cut a new release (Devolutions Gateway, Devolutions Agent, Jetsocat, PowerShell module)

  • Add the label release-blocker Follow-up is required before cutting a new release if a follow-up is required before cutting a new release

  • Add the label publish-required Please publish libraries (`Devolutions.Gateway.Utils`, OpenAPI clients, etc) when you request a maintainer to publish libraries (Devolutions.Gateway.Utils, OpenAPI clients, etc.)

  • Add the label publish-blocker Follow-up is required before publishing libraries if a follow-up is required before publishing libraries

@awakecoding
Copy link
Contributor Author

First shot with GitHub Copilot for https://devolutions.atlassian.net/browse/ARC-417

@awakecoding awakecoding marked this pull request as draft November 3, 2025 20:03
Copy link
Member

@CBenoit CBenoit left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me overall.

I think we also need to update the /jet/update endpoint of the Devolutions Gateway (found in devolutions-gateway/src/api/update.rs), to support the product selection.

Likely need a quick manual test before merging as well. I assume you didn’t get a chance to try it out yet.

@CBenoit CBenoit changed the title Add Hub Service auto-updater support feat(agent,dgw): add Hub Service auto-updater support Nov 4, 2025
@CBenoit
Copy link
Member

CBenoit commented Nov 4, 2025

Follow up.

We use the title of the PR and the body to generate the changelog. The title must follow the conventional commit specification, and the body must include (optional) details about the features. This can be technical to some extend (in-repository changelog for technically-inclined users), but crucially, it should still be used-oriented. (Does not apply to chore, ci, and other commit types we do not include in the changelog). The Jira ticket must be specified using Issue: KEY-ID; in this case: Issue: ARC-417. I recommend creating a DGW ticket dedicated to tracking this feature on Devolutions Gateway side.

This is stuff I’ll add in the agents instructions so you don’t have to remember when using Copilot, but maybe you could try providing it with instructions above?

@CBenoit
Copy link
Member

CBenoit commented Nov 7, 2025

I opened a PR with agent instructions: #1564
You may run against it again once merged.

@mamoreau-devolutions mamoreau-devolutions force-pushed the hub-service-updater branch 2 times, most recently from c852b7a to a338674 Compare November 26, 2025 16:29
@awakecoding awakecoding marked this pull request as ready for review November 26, 2025 16:34
@CBenoit CBenoit requested a review from pacmancoder November 26, 2025 16:35
@CBenoit
Copy link
Member

CBenoit commented Nov 26, 2025

@pacmancoder Can you review this?

EDIT: Avoid merging the PR with the current description. It will get as-is in the changelog.

@mamoreau-devolutions
Copy link
Contributor

The PR is ready, what matters is that it doesn't introduce a breaking change to the existing Devolutions Gateway auto-updater feature. Normally it shouldn't, the JSON fields are optional so it won't break if one of the two version field is missing, etc. We just need a proper review.

Extends the agent updater to support automatic updates for Devolutions Hub Service
in addition to Gateway. This enables centralized management of both products through
a unified update.json configuration file.

Changes include:
- Extended UpdateJson schema to support Hub Service version updates via new
  hub_service field alongside existing gateway field
- Added HUB_SERVICE_UPDATE_CODE UUID constant and registry lookups for Hub Service
  MSI detection and version tracking
- Expanded updater core to track Hub Service as an updateable product alongside
  Gateway with proper staging and processing
- Enhanced MSI handling to support both Gateway (single service) and Hub Service
  (multi-service: PAM, Encryption, Reporting features) installation patterns
- Improved MSI exit code handling to distinguish between success (0), reboot
  required (3010, 1641), and actual errors; previously treated all non-zero codes
  as failures
- Added UTF-8 BOM stripping in update.json parsing to handle files created by
  certain editors that add a BOM header
- Refactored service state management to use generic ServiceUpdateActions trait
  supporting both single-service (Gateway) and multi-service (Hub Service) products
- Added smart service startup mode restoration: Gateway uses P.SERVICESTART MSI
  parameter while Hub Service uses ADDLOCAL to preserve installed features

Backward compatibility:
- The hub_service field is optional with skip_serializing_if = Option::is_none
- Gateway API continues to write hub_service: None, maintaining existing behavior
- Each product independently queries its own field; Gateway only checks gateway field
- Old update.json files with only Gateway field deserialize correctly
- Existing Gateway auto-update feature is unaffected and fully compatible
Copy link
Contributor

@pacmancoder pacmancoder left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me! 👍

@CBenoit CBenoit merged commit 330cefe into master Nov 27, 2025
40 checks passed
@CBenoit CBenoit deleted the hub-service-updater branch November 27, 2025 11:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

5 participants