From bec8e7af342e6c410f85fa75afcba0bd3cd41ec0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Thu, 16 Oct 2025 10:26:32 +0200 Subject: [PATCH 01/29] add initial script to fetch provisioning config from entra ID --- .../scripts/Get-ProvisioningConfig.ps1 | 308 ++++++++++++++++++ 1 file changed, 308 insertions(+) create mode 100644 src-tauri/resources-windows/scripts/Get-ProvisioningConfig.ps1 diff --git a/src-tauri/resources-windows/scripts/Get-ProvisioningConfig.ps1 b/src-tauri/resources-windows/scripts/Get-ProvisioningConfig.ps1 new file mode 100644 index 00000000..ea52f066 --- /dev/null +++ b/src-tauri/resources-windows/scripts/Get-ProvisioningConfig.ps1 @@ -0,0 +1,308 @@ +#Requires -Version 5.1 + +<# +.SYNOPSIS + Retrieves user information for the currently logged-in user from AD or Entra ID. + +.DESCRIPTION + This script detects whether the computer is joined to on-premises Active Directory + or Entra ID (Azure AD), then fetches the appropriate user information. + - On-premises AD: Uses Get-ADUser + - Entra ID: Uses Microsoft Graph API + - Workgroup: Exits gracefully + +.PARAMETER Silent + Suppresses interactive authentication prompts for Entra ID (will fail if not already authenticated) +#> + +param( + [switch]$Silent +) + +# Check device join status +function Get-DomainJoinStatus { + try { + $computerSystem = Get-WmiObject -Class Win32_ComputerSystem -ErrorAction Stop + + # Check for traditional domain join + if ($computerSystem.PartOfDomain -eq $true) { + return @{ + JoinType = "OnPremisesAD" + Domain = $computerSystem.Domain + } + } + + # Check for Entra ID (Azure AD) join + $dsregStatus = dsregcmd /status + if ($dsregStatus -match "AzureAdJoined\s*:\s*YES") { + $tenantName = ($dsregStatus | Select-String "TenantName\s*:\s*(.+)").Matches.Groups[1].Value.Trim() + return @{ + JoinType = "EntraID" + Domain = $tenantName + } + } + + # Check for Hybrid join + if ($dsregStatus -match "DomainJoined\s*:\s*YES" -and $dsregStatus -match "AzureAdJoined\s*:\s*YES") { + return @{ + JoinType = "Hybrid" + Domain = $computerSystem.Domain + } + } + + # Not joined to any directory + return @{ + JoinType = "Workgroup" + Domain = $null + } + + } catch { + Write-Host "Unable to determine domain status: $_" -ForegroundColor Yellow + return @{ + JoinType = "Unknown" + Domain = $null + } + } +} + +# Save Defguard enrollment data to JSON +function Save-DefguardEnrollmentData { + param( + [string]$EnrollmentUrl, + [string]$EnrollmentToken, + [string]$UserPrincipalName, + [string]$DisplayName + ) + + # Create Defguard directory in AppData\Roaming + $defguardDir = Join-Path $env:APPDATA "net.defguard" + $jsonOutputPath = Join-Path $defguardDir "enrollment.json" + + try { + # Create directory if it doesn't exist + if (-not (Test-Path -Path $defguardDir)) { + New-Item -ItemType Directory -Path $defguardDir -Force | Out-Null + Write-Host "`nCreated directory: $defguardDir" -ForegroundColor Gray + } + + $jsonData = @{ + enrollmentUrl = $EnrollmentUrl + enrollmentToken = $EnrollmentToken + userPrincipalName = $UserPrincipalName + displayName = $DisplayName + retrievedAt = (Get-Date).ToString("o") + } + + $jsonData | ConvertTo-Json -Depth 10 | Out-File -FilePath $jsonOutputPath -Encoding UTF8 -Force + Write-Host "`nDefguard enrollment data saved to: $jsonOutputPath" -ForegroundColor Green + return $true + } catch { + Write-Host "`nFailed to save JSON file: $_" -ForegroundColor Red + return $false + } +} + +# Get user info from on-premises AD +function Get-OnPremisesADUserInfo { + param([string]$Username) + + # Check if Active Directory module is available + if (-not (Get-Module -ListAvailable -Name ActiveDirectory)) { + Write-Host "Active Directory module is not installed. Please install RSAT tools." -ForegroundColor Red + return $null + } + + # Import the Active Directory module + try { + Import-Module ActiveDirectory -ErrorAction Stop + } catch { + Write-Host "Failed to import Active Directory module: $_" -ForegroundColor Red + return $null + } + + # Fetch AD user information + try { + $adUser = Get-ADUser -Identity $Username -Properties * -ErrorAction Stop + + # Display user information + Write-Host "`n=== On-Premises Active Directory User Information ===" -ForegroundColor Cyan + Write-Host "Display Name: $($adUser.DisplayName)" + Write-Host "Username (SAM): $($adUser.SamAccountName)" + Write-Host "User Principal Name: $($adUser.UserPrincipalName)" + Write-Host "Email: $($adUser.EmailAddress)" + Write-Host "Department: $($adUser.Department)" + Write-Host "Title: $($adUser.Title)" + Write-Host "Office: $($adUser.Office)" + Write-Host "Manager: $($adUser.Manager)" + Write-Host "Enabled: $($adUser.Enabled)" + Write-Host "Last Logon: $($adUser.LastLogonDate)" + Write-Host "Created: $($adUser.Created)" + Write-Host "Distinguished Name: $($adUser.DistinguishedName)" + Write-Host "======================================================`n" -ForegroundColor Cyan + + return $adUser + + } catch { + Write-Host "Failed to retrieve AD user information for '$Username': $_" -ForegroundColor Red + return $null + } +} + +# Get user info from Entra ID +function Get-EntraIDUserInfo { + param([bool]$SilentMode) + + # Check if Microsoft.Graph module is available + if (-not (Get-Module -ListAvailable -Name Microsoft.Graph.Users)) { + Write-Host "Microsoft.Graph.Users module is not installed." -ForegroundColor Yellow + Write-Host "Install it with: Install-Module Microsoft.Graph.Users -Scope CurrentUser" -ForegroundColor Yellow + return $null + } + + # Import the module + try { + Import-Module Microsoft.Graph.Users -ErrorAction Stop + } catch { + Write-Host "Failed to import Microsoft.Graph.Users module: $_" -ForegroundColor Red + return $null + } + + # Connect to Microsoft Graph + try { + $context = Get-MgContext -ErrorAction SilentlyContinue + + if (-not $context) { + if ($SilentMode) { + Write-Host "Not authenticated to Microsoft Graph and silent mode is enabled. Cannot proceed." -ForegroundColor Yellow + return $null + } + + Write-Host "Connecting to Microsoft Graph (authentication required)..." -ForegroundColor Yellow + Write-Host "Note: Requesting additional permissions for custom security attributes..." -ForegroundColor Gray + Connect-MgGraph -Scopes "User.Read", "CustomSecAttributeAssignment.Read.All" -ErrorAction Stop + } else { + # Check if we have the required scope for custom attributes + $hasCustomAttrScope = $context.Scopes -contains "CustomSecAttributeAssignment.Read.All" + if (-not $hasCustomAttrScope) { + Write-Host "Warning: Missing 'CustomSecAttributeAssignment.Read.All' permission." -ForegroundColor Yellow + Write-Host "Custom security attributes will not be available. Reconnect with:" -ForegroundColor Yellow + Write-Host " Connect-MgGraph -Scopes 'User.Read', 'CustomSecAttributeAssignment.Read.All'" -ForegroundColor Gray + } + } + + # Get current user info including custom security attributes + $properties = @( + "DisplayName", + "UserPrincipalName", + "Mail", + "JobTitle", + "Department", + "OfficeLocation", + "AccountEnabled", + "CreatedDateTime", + "Id", + "CustomSecurityAttributes" + ) + + $mgUser = Get-MgUser -UserId (Get-MgContext).Account -Property $properties -ErrorAction Stop + + # Display user information + Write-Host "`n=== Entra ID (Azure AD) User Information ===" -ForegroundColor Cyan + Write-Host "Display Name: $($mgUser.DisplayName)" + Write-Host "User Principal Name: $($mgUser.UserPrincipalName)" + Write-Host "Email: $($mgUser.Mail)" + Write-Host "Department: $($mgUser.Department)" + Write-Host "Title: $($mgUser.JobTitle)" + Write-Host "Office: $($mgUser.OfficeLocation)" + Write-Host "Account Enabled: $($mgUser.AccountEnabled)" + Write-Host "Created: $($mgUser.CreatedDateTime)" + Write-Host "User ID: $($mgUser.Id)" + + # Try to get custom security attributes + if ($mgUser.CustomSecurityAttributes) { + Write-Host "`n--- Custom Security Attributes ---" -ForegroundColor Yellow + + # Access Defguard attributes + if ($mgUser.CustomSecurityAttributes.AdditionalProperties) { + $defguardAttrs = $mgUser.CustomSecurityAttributes.AdditionalProperties["Defguard"] + + if ($defguardAttrs) { + $enrollmentUrl = $defguardAttrs["EnrollmentUrl"] + $enrollmentToken = $defguardAttrs["EnrollmentToken"] + + Write-Host "Defguard Enrollment URL: $enrollmentUrl" + Write-Host "Defguard Enrollment Token: $enrollmentToken" + + # Save enrollment data to JSON file only if both URL and token exist + if ($enrollmentUrl -and $enrollmentToken) { + Save-DefguardEnrollmentData -EnrollmentUrl $enrollmentUrl ` + -EnrollmentToken $enrollmentToken ` + -UserPrincipalName $mgUser.UserPrincipalName ` + -DisplayName $mgUser.DisplayName + } else { + Write-Host "`nWarning: Incomplete Defguard enrollment data. Both URL and token are required." -ForegroundColor Yellow + } + } else { + Write-Host "No Defguard attributes found for this user." -ForegroundColor Gray + } + } else { + Write-Host "No custom security attributes found." -ForegroundColor Gray + } + } else { + Write-Host "`nCustom security attributes not available." -ForegroundColor Gray + Write-Host "(May require additional permissions or attributes not set)" -ForegroundColor Gray + } + + Write-Host "=============================================`n" -ForegroundColor Cyan + + return $mgUser + + } catch { + Write-Host "Failed to retrieve Entra ID user information: $_" -ForegroundColor Red + Write-Host "Error details: $($_.Exception.Message)" -ForegroundColor Red + return $null + } +} + +# Main script execution +Write-Host "Detecting domain join status..." -ForegroundColor Gray + +$joinStatus = Get-DomainJoinStatus +$joinType = $joinStatus.JoinType + +Write-Host "Join Type = '$joinType'" -ForegroundColor Magenta + +if ($joinType -eq "OnPremisesAD") { + Write-Host "Connected to on-premises Active Directory: $($joinStatus.Domain)" -ForegroundColor Green + $currentUser = $env:USERNAME + $userInfo = Get-OnPremisesADUserInfo -Username $currentUser + exit 0 + + +} elseif ($joinType -eq "Hybrid") { + Write-Host "Hybrid join detected (both on-premises AD and Entra ID): $($joinStatus.Domain)" -ForegroundColor Green + Write-Host "Querying on-premises Active Directory..." -ForegroundColor Gray + $currentUser = $env:USERNAME + $userInfo = Get-OnPremisesADUserInfo -Username $currentUser + exit 0 + + +} elseif ($joinType -eq "EntraID") { + Write-Host "Connected to Entra ID (Azure AD)" -ForegroundColor Green + if ($joinStatus.Domain) { + Write-Host " Tenant: $($joinStatus.Domain)" -ForegroundColor Gray + } + $userInfo = Get-EntraIDUserInfo -SilentMode $Silent + exit 0 + + +} elseif ($joinType -eq "Workgroup") { + Write-Host "This computer is not connected to a domain (Workgroup). Exiting." -ForegroundColor Yellow + exit 0 + + +} else { + Write-Host "Unable to determine domain connection status. Exiting." -ForegroundColor Yellow + exit 0 + +} From 89ad24379c1e294d8078e6b77895fc09f6979ff6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Thu, 16 Oct 2025 12:09:00 +0200 Subject: [PATCH 02/29] update naming and return values --- .../scripts/Get-ProvisioningConfig.ps1 | 40 ++++++++----------- 1 file changed, 16 insertions(+), 24 deletions(-) diff --git a/src-tauri/resources-windows/scripts/Get-ProvisioningConfig.ps1 b/src-tauri/resources-windows/scripts/Get-ProvisioningConfig.ps1 index ea52f066..0826b9d8 100644 --- a/src-tauri/resources-windows/scripts/Get-ProvisioningConfig.ps1 +++ b/src-tauri/resources-windows/scripts/Get-ProvisioningConfig.ps1 @@ -102,14 +102,14 @@ function Save-DefguardEnrollmentData { } } -# Get user info from on-premises AD -function Get-OnPremisesADUserInfo { +# Get Defguard client provisioning config from on-premises AD +function Get-OnPremisesADProvisioningConfig { param([string]$Username) # Check if Active Directory module is available if (-not (Get-Module -ListAvailable -Name ActiveDirectory)) { Write-Host "Active Directory module is not installed. Please install RSAT tools." -ForegroundColor Red - return $null + return } # Import the Active Directory module @@ -117,7 +117,7 @@ function Get-OnPremisesADUserInfo { Import-Module ActiveDirectory -ErrorAction Stop } catch { Write-Host "Failed to import Active Directory module: $_" -ForegroundColor Red - return $null + return } # Fetch AD user information @@ -140,23 +140,23 @@ function Get-OnPremisesADUserInfo { Write-Host "Distinguished Name: $($adUser.DistinguishedName)" Write-Host "======================================================`n" -ForegroundColor Cyan - return $adUser + return } catch { Write-Host "Failed to retrieve AD user information for '$Username': $_" -ForegroundColor Red - return $null + return } } -# Get user info from Entra ID -function Get-EntraIDUserInfo { +# Get Defguard client provisioning config from Entra ID +function Get-EntraIDProvisioningConfig { param([bool]$SilentMode) # Check if Microsoft.Graph module is available if (-not (Get-Module -ListAvailable -Name Microsoft.Graph.Users)) { Write-Host "Microsoft.Graph.Users module is not installed." -ForegroundColor Yellow Write-Host "Install it with: Install-Module Microsoft.Graph.Users -Scope CurrentUser" -ForegroundColor Yellow - return $null + return } # Import the module @@ -164,7 +164,7 @@ function Get-EntraIDUserInfo { Import-Module Microsoft.Graph.Users -ErrorAction Stop } catch { Write-Host "Failed to import Microsoft.Graph.Users module: $_" -ForegroundColor Red - return $null + return } # Connect to Microsoft Graph @@ -174,7 +174,7 @@ function Get-EntraIDUserInfo { if (-not $context) { if ($SilentMode) { Write-Host "Not authenticated to Microsoft Graph and silent mode is enabled. Cannot proceed." -ForegroundColor Yellow - return $null + return } Write-Host "Connecting to Microsoft Graph (authentication required)..." -ForegroundColor Yellow @@ -187,6 +187,7 @@ function Get-EntraIDUserInfo { Write-Host "Warning: Missing 'CustomSecAttributeAssignment.Read.All' permission." -ForegroundColor Yellow Write-Host "Custom security attributes will not be available. Reconnect with:" -ForegroundColor Yellow Write-Host " Connect-MgGraph -Scopes 'User.Read', 'CustomSecAttributeAssignment.Read.All'" -ForegroundColor Gray + return } } @@ -195,9 +196,6 @@ function Get-EntraIDUserInfo { "DisplayName", "UserPrincipalName", "Mail", - "JobTitle", - "Department", - "OfficeLocation", "AccountEnabled", "CreatedDateTime", "Id", @@ -211,9 +209,6 @@ function Get-EntraIDUserInfo { Write-Host "Display Name: $($mgUser.DisplayName)" Write-Host "User Principal Name: $($mgUser.UserPrincipalName)" Write-Host "Email: $($mgUser.Mail)" - Write-Host "Department: $($mgUser.Department)" - Write-Host "Title: $($mgUser.JobTitle)" - Write-Host "Office: $($mgUser.OfficeLocation)" Write-Host "Account Enabled: $($mgUser.AccountEnabled)" Write-Host "Created: $($mgUser.CreatedDateTime)" Write-Host "User ID: $($mgUser.Id)" @@ -254,13 +249,10 @@ function Get-EntraIDUserInfo { } Write-Host "=============================================`n" -ForegroundColor Cyan - - return $mgUser - + } catch { Write-Host "Failed to retrieve Entra ID user information: $_" -ForegroundColor Red Write-Host "Error details: $($_.Exception.Message)" -ForegroundColor Red - return $null } } @@ -275,7 +267,7 @@ Write-Host "Join Type = '$joinType'" -ForegroundColor Magenta if ($joinType -eq "OnPremisesAD") { Write-Host "Connected to on-premises Active Directory: $($joinStatus.Domain)" -ForegroundColor Green $currentUser = $env:USERNAME - $userInfo = Get-OnPremisesADUserInfo -Username $currentUser + Get-OnPremisesADUserInfo -Username $currentUser exit 0 @@ -283,7 +275,7 @@ if ($joinType -eq "OnPremisesAD") { Write-Host "Hybrid join detected (both on-premises AD and Entra ID): $($joinStatus.Domain)" -ForegroundColor Green Write-Host "Querying on-premises Active Directory..." -ForegroundColor Gray $currentUser = $env:USERNAME - $userInfo = Get-OnPremisesADUserInfo -Username $currentUser + Get-OnPremisesADUserInfo -Username $currentUser exit 0 @@ -292,7 +284,7 @@ if ($joinType -eq "OnPremisesAD") { if ($joinStatus.Domain) { Write-Host " Tenant: $($joinStatus.Domain)" -ForegroundColor Gray } - $userInfo = Get-EntraIDUserInfo -SilentMode $Silent + Get-EntraIDProvisioningConfig -SilentMode $Silent exit 0 From 511880a7929a759808477f574e5b21778fc70930 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Thu, 16 Oct 2025 12:12:59 +0200 Subject: [PATCH 03/29] fetch provisioning config from AD extension attributes --- .../scripts/Get-ProvisioningConfig.ps1 | 26 +++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/src-tauri/resources-windows/scripts/Get-ProvisioningConfig.ps1 b/src-tauri/resources-windows/scripts/Get-ProvisioningConfig.ps1 index 0826b9d8..fcbf78ef 100644 --- a/src-tauri/resources-windows/scripts/Get-ProvisioningConfig.ps1 +++ b/src-tauri/resources-windows/scripts/Get-ProvisioningConfig.ps1 @@ -130,15 +130,31 @@ function Get-OnPremisesADProvisioningConfig { Write-Host "Username (SAM): $($adUser.SamAccountName)" Write-Host "User Principal Name: $($adUser.UserPrincipalName)" Write-Host "Email: $($adUser.EmailAddress)" - Write-Host "Department: $($adUser.Department)" - Write-Host "Title: $($adUser.Title)" - Write-Host "Office: $($adUser.Office)" - Write-Host "Manager: $($adUser.Manager)" Write-Host "Enabled: $($adUser.Enabled)" - Write-Host "Last Logon: $($adUser.LastLogonDate)" Write-Host "Created: $($adUser.Created)" Write-Host "Distinguished Name: $($adUser.DistinguishedName)" Write-Host "======================================================`n" -ForegroundColor Cyan + + # Check for Defguard custom attributes in extension attributes + Write-Host "`n--- Custom Attributes ---" -ForegroundColor Yellow + $enrollmentUrl = $adUser.extensionAttribute1 + $enrollmentToken = $adUser.extensionAttribute2 + + Write-Host "Defguard Enrollment URL (extensionAttribute1): $enrollmentUrl" + Write-Host "Defguard Enrollment Token (extensionAttribute2): $enrollmentToken" + + # Save enrollment data to JSON file only if both URL and token exist + if ($enrollmentUrl -and $enrollmentToken) { + Save-DefguardEnrollmentData -EnrollmentUrl $enrollmentUrl ` + -EnrollmentToken $enrollmentToken ` + -UserPrincipalName $adUser.UserPrincipalName ` + -DisplayName $adUser.DisplayName + } else { + Write-Host "`nWarning: Incomplete Defguard enrollment data. Both URL and token are required." -ForegroundColor Yellow + } + + Write-Host "======================================================`n" -ForegroundColor Cyan + return From 008f67ecf47d6873dafa011609cff7314679b982 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Thu, 16 Oct 2025 12:18:56 +0200 Subject: [PATCH 04/29] update docstring --- .../resources-windows/scripts/Get-ProvisioningConfig.ps1 | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src-tauri/resources-windows/scripts/Get-ProvisioningConfig.ps1 b/src-tauri/resources-windows/scripts/Get-ProvisioningConfig.ps1 index fcbf78ef..bc56b992 100644 --- a/src-tauri/resources-windows/scripts/Get-ProvisioningConfig.ps1 +++ b/src-tauri/resources-windows/scripts/Get-ProvisioningConfig.ps1 @@ -2,14 +2,15 @@ <# .SYNOPSIS - Retrieves user information for the currently logged-in user from AD or Entra ID. + Retrieves Defguard client provisioning configuration for the currently logged-in user from AD or Entra ID. .DESCRIPTION This script detects whether the computer is joined to on-premises Active Directory - or Entra ID (Azure AD), then fetches the appropriate user information. - - On-premises AD: Uses Get-ADUser - - Entra ID: Uses Microsoft Graph API + or Entra ID (Azure AD), then fetches Defguard provisioning data (URL and enrollment token) from the appropriate source. + - On-premises AD: Reads from extensionAttribute1 and extensionAttribute2 + - Entra ID: Reads from custom security attributes under the 'Defguard' set - Workgroup: Exits gracefully + The retrieved enrollment data is saved to a JSON file for the Defguard client to use. .PARAMETER Silent Suppresses interactive authentication prompts for Entra ID (will fail if not already authenticated) From 3962c5b0286bcac18b62d80dea99a5dbb6c6e667 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Thu, 16 Oct 2025 12:44:46 +0200 Subject: [PATCH 05/29] read data from a single extension attribute --- .../scripts/Get-ProvisioningConfig.ps1 | 41 ++++++++++++++----- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/src-tauri/resources-windows/scripts/Get-ProvisioningConfig.ps1 b/src-tauri/resources-windows/scripts/Get-ProvisioningConfig.ps1 index bc56b992..38e6d0e4 100644 --- a/src-tauri/resources-windows/scripts/Get-ProvisioningConfig.ps1 +++ b/src-tauri/resources-windows/scripts/Get-ProvisioningConfig.ps1 @@ -138,20 +138,39 @@ function Get-OnPremisesADProvisioningConfig { # Check for Defguard custom attributes in extension attributes Write-Host "`n--- Custom Attributes ---" -ForegroundColor Yellow - $enrollmentUrl = $adUser.extensionAttribute1 - $enrollmentToken = $adUser.extensionAttribute2 - Write-Host "Defguard Enrollment URL (extensionAttribute1): $enrollmentUrl" - Write-Host "Defguard Enrollment Token (extensionAttribute2): $enrollmentToken" + # Read JSON data from a single extension attribute (using extensionAttribute1) + $jsonData = $adUser.extensionAttribute1 - # Save enrollment data to JSON file only if both URL and token exist - if ($enrollmentUrl -and $enrollmentToken) { - Save-DefguardEnrollmentData -EnrollmentUrl $enrollmentUrl ` - -EnrollmentToken $enrollmentToken ` - -UserPrincipalName $adUser.UserPrincipalName ` - -DisplayName $adUser.DisplayName + Write-Host "Defguard Enrollment JSON (extensionAttribute1): $jsonData" + + if ($jsonData) { + try { + # Parse the JSON data + $enrollmentConfig = $jsonData | ConvertFrom-Json -ErrorAction Stop + + # Extract URL and token from the parsed JSON + $enrollmentUrl = $enrollmentConfig.enrollmentUrl + $enrollmentToken = $enrollmentConfig.enrollmentToken + + Write-Host "Defguard Enrollment URL: $enrollmentUrl" + Write-Host "Defguard Enrollment Token: $enrollmentToken" + + # Save enrollment data to JSON file only if both URL and token exist + if ($enrollmentUrl -and $enrollmentToken) { + Save-DefguardEnrollmentData -EnrollmentUrl $enrollmentUrl ` + -EnrollmentToken $enrollmentToken ` + -UserPrincipalName $adUser.UserPrincipalName ` + -DisplayName $adUser.DisplayName + } else { + Write-Host "`nWarning: Incomplete Defguard enrollment data in JSON. Both URL and token are required." -ForegroundColor Yellow + } + } catch { + Write-Host "Failed to parse JSON from extension attribute: $_" -ForegroundColor Red + Write-Host "JSON data should be in format: {\`"enrollmentUrl\`":\`"https://...\`",\`"enrollmentToken\`":\`"token-value\`"}" -ForegroundColor Yellow + } } else { - Write-Host "`nWarning: Incomplete Defguard enrollment data. Both URL and token are required." -ForegroundColor Yellow + Write-Host "No Defguard enrollment data found in extension attributes." -ForegroundColor Yellow } Write-Host "======================================================`n" -ForegroundColor Cyan From db00f9871bc922dbeadbafc24d807c3407b96dfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Thu, 16 Oct 2025 12:47:49 +0200 Subject: [PATCH 06/29] pass extension attribute as argument --- .../scripts/Get-ProvisioningConfig.ps1 | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src-tauri/resources-windows/scripts/Get-ProvisioningConfig.ps1 b/src-tauri/resources-windows/scripts/Get-ProvisioningConfig.ps1 index 38e6d0e4..dbd7a679 100644 --- a/src-tauri/resources-windows/scripts/Get-ProvisioningConfig.ps1 +++ b/src-tauri/resources-windows/scripts/Get-ProvisioningConfig.ps1 @@ -14,10 +14,14 @@ .PARAMETER Silent Suppresses interactive authentication prompts for Entra ID (will fail if not already authenticated) + +.PARAMETER ExtensionAttribute + Specifies which extension attribute to read from AD (default: extensionAttribute1) #> param( - [switch]$Silent + [switch]$Silent, + [string]$ExtensionAttribute = "extensionAttribute1" ) # Check device join status @@ -105,7 +109,10 @@ function Save-DefguardEnrollmentData { # Get Defguard client provisioning config from on-premises AD function Get-OnPremisesADProvisioningConfig { - param([string]$Username) + param( + [string]$Username, + [string]$ExtensionAttribute + ) # Check if Active Directory module is available if (-not (Get-Module -ListAvailable -Name ActiveDirectory)) { @@ -139,10 +146,10 @@ function Get-OnPremisesADProvisioningConfig { # Check for Defguard custom attributes in extension attributes Write-Host "`n--- Custom Attributes ---" -ForegroundColor Yellow - # Read JSON data from a single extension attribute (using extensionAttribute1) - $jsonData = $adUser.extensionAttribute1 + # Read JSON data from the specified extension attribute + $jsonData = $adUser.$ExtensionAttribute - Write-Host "Defguard Enrollment JSON (extensionAttribute1): $jsonData" + Write-Host "Defguard Enrollment JSON ($ExtensionAttribute): $jsonData" if ($jsonData) { try { @@ -166,7 +173,7 @@ function Get-OnPremisesADProvisioningConfig { Write-Host "`nWarning: Incomplete Defguard enrollment data in JSON. Both URL and token are required." -ForegroundColor Yellow } } catch { - Write-Host "Failed to parse JSON from extension attribute: $_" -ForegroundColor Red + Write-Host "Failed to parse JSON from extension attribute '$ExtensionAttribute': $_" -ForegroundColor Red Write-Host "JSON data should be in format: {\`"enrollmentUrl\`":\`"https://...\`",\`"enrollmentToken\`":\`"token-value\`"}" -ForegroundColor Yellow } } else { @@ -303,7 +310,7 @@ Write-Host "Join Type = '$joinType'" -ForegroundColor Magenta if ($joinType -eq "OnPremisesAD") { Write-Host "Connected to on-premises Active Directory: $($joinStatus.Domain)" -ForegroundColor Green $currentUser = $env:USERNAME - Get-OnPremisesADUserInfo -Username $currentUser + Get-OnPremisesADProvisioningConfig -Username $currentUser -ExtensionAttribute $ExtensionAttribute exit 0 @@ -311,7 +318,7 @@ if ($joinType -eq "OnPremisesAD") { Write-Host "Hybrid join detected (both on-premises AD and Entra ID): $($joinStatus.Domain)" -ForegroundColor Green Write-Host "Querying on-premises Active Directory..." -ForegroundColor Gray $currentUser = $env:USERNAME - Get-OnPremisesADUserInfo -Username $currentUser + Get-OnPremisesADProvisioningConfig -Username $currentUser -ExtensionAttribute $ExtensionAttribute exit 0 From 75d24eec9a0e2d1f700cb1b26fc33119734b588a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Fri, 17 Oct 2025 14:33:11 +0200 Subject: [PATCH 07/29] us an arbitrary AD attribute --- .../scripts/Get-ProvisioningConfig.ps1 | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src-tauri/resources-windows/scripts/Get-ProvisioningConfig.ps1 b/src-tauri/resources-windows/scripts/Get-ProvisioningConfig.ps1 index dbd7a679..7ed2b94f 100644 --- a/src-tauri/resources-windows/scripts/Get-ProvisioningConfig.ps1 +++ b/src-tauri/resources-windows/scripts/Get-ProvisioningConfig.ps1 @@ -15,13 +15,13 @@ .PARAMETER Silent Suppresses interactive authentication prompts for Entra ID (will fail if not already authenticated) -.PARAMETER ExtensionAttribute - Specifies which extension attribute to read from AD (default: extensionAttribute1) +.PARAMETER ADAttribute + Specifies which Active Directory attribute to read from (default: extensionAttribute1) #> param( [switch]$Silent, - [string]$ExtensionAttribute = "extensionAttribute1" + [string]$ADAttribute = "extensionAttribute1" ) # Check device join status @@ -111,7 +111,7 @@ function Save-DefguardEnrollmentData { function Get-OnPremisesADProvisioningConfig { param( [string]$Username, - [string]$ExtensionAttribute + [string]$ADAttribute ) # Check if Active Directory module is available @@ -143,13 +143,13 @@ function Get-OnPremisesADProvisioningConfig { Write-Host "Distinguished Name: $($adUser.DistinguishedName)" Write-Host "======================================================`n" -ForegroundColor Cyan - # Check for Defguard custom attributes in extension attributes - Write-Host "`n--- Custom Attributes ---" -ForegroundColor Yellow + # Check for Defguard enrollment data in the specified AD attribute + Write-Host "`n--- Active Directory Attribute ---" -ForegroundColor Yellow - # Read JSON data from the specified extension attribute - $jsonData = $adUser.$ExtensionAttribute + # Read JSON data from the specified AD attribute + $jsonData = $adUser.$ADAttribute - Write-Host "Defguard Enrollment JSON ($ExtensionAttribute): $jsonData" + Write-Host "Defguard Enrollment JSON ($ADAttribute): $jsonData" if ($jsonData) { try { @@ -173,11 +173,11 @@ function Get-OnPremisesADProvisioningConfig { Write-Host "`nWarning: Incomplete Defguard enrollment data in JSON. Both URL and token are required." -ForegroundColor Yellow } } catch { - Write-Host "Failed to parse JSON from extension attribute '$ExtensionAttribute': $_" -ForegroundColor Red + Write-Host "Failed to parse JSON from AD attribute '$ADAttribute': $_" -ForegroundColor Red Write-Host "JSON data should be in format: {\`"enrollmentUrl\`":\`"https://...\`",\`"enrollmentToken\`":\`"token-value\`"}" -ForegroundColor Yellow } } else { - Write-Host "No Defguard enrollment data found in extension attributes." -ForegroundColor Yellow + Write-Host "No Defguard enrollment data found in the specified AD attribute." -ForegroundColor Yellow } Write-Host "======================================================`n" -ForegroundColor Cyan @@ -310,7 +310,7 @@ Write-Host "Join Type = '$joinType'" -ForegroundColor Magenta if ($joinType -eq "OnPremisesAD") { Write-Host "Connected to on-premises Active Directory: $($joinStatus.Domain)" -ForegroundColor Green $currentUser = $env:USERNAME - Get-OnPremisesADProvisioningConfig -Username $currentUser -ExtensionAttribute $ExtensionAttribute + Get-OnPremisesADProvisioningConfig -Username $currentUser -ADAttribute $ADAttribute exit 0 @@ -318,7 +318,7 @@ if ($joinType -eq "OnPremisesAD") { Write-Host "Hybrid join detected (both on-premises AD and Entra ID): $($joinStatus.Domain)" -ForegroundColor Green Write-Host "Querying on-premises Active Directory..." -ForegroundColor Gray $currentUser = $env:USERNAME - Get-OnPremisesADProvisioningConfig -Username $currentUser -ExtensionAttribute $ExtensionAttribute + Get-OnPremisesADProvisioningConfig -Username $currentUser -ADAttribute $ADAttribute exit 0 From 40248d8312c3b8cef2b520f202bd096670a95acf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Mon, 20 Oct 2025 08:35:47 +0200 Subject: [PATCH 08/29] remove unnecessary escape chars --- src-tauri/resources-windows/scripts/Get-ProvisioningConfig.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src-tauri/resources-windows/scripts/Get-ProvisioningConfig.ps1 b/src-tauri/resources-windows/scripts/Get-ProvisioningConfig.ps1 index 7ed2b94f..4987cced 100644 --- a/src-tauri/resources-windows/scripts/Get-ProvisioningConfig.ps1 +++ b/src-tauri/resources-windows/scripts/Get-ProvisioningConfig.ps1 @@ -174,7 +174,7 @@ function Get-OnPremisesADProvisioningConfig { } } catch { Write-Host "Failed to parse JSON from AD attribute '$ADAttribute': $_" -ForegroundColor Red - Write-Host "JSON data should be in format: {\`"enrollmentUrl\`":\`"https://...\`",\`"enrollmentToken\`":\`"token-value\`"}" -ForegroundColor Yellow + Write-Host "JSON data should be in format: {`"enrollmentUrl`":`"https://...`",`"enrollmentToken`":`"token-value`"}" -ForegroundColor Yellow } } else { Write-Host "No Defguard enrollment data found in the specified AD attribute." -ForegroundColor Yellow From c282ad8618845894788485f83ad2ea188272ecd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Mon, 20 Oct 2025 11:52:12 +0200 Subject: [PATCH 09/29] run script during MSI install --- .../fragments/provisioning.wxs | 26 +++++++++++++++++++ .../service.wxs} | 2 +- src-tauri/tauri.conf.json | 8 +++--- src-tauri/tauri.windows.conf.json | 7 +++-- 4 files changed, 37 insertions(+), 6 deletions(-) create mode 100644 src-tauri/resources-windows/fragments/provisioning.wxs rename src-tauri/resources-windows/{service-fragment.wxs => fragments/service.wxs} (95%) diff --git a/src-tauri/resources-windows/fragments/provisioning.wxs b/src-tauri/resources-windows/fragments/provisioning.wxs new file mode 100644 index 00000000..d047ba9c --- /dev/null +++ b/src-tauri/resources-windows/fragments/provisioning.wxs @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + NOT REMOVE + + + diff --git a/src-tauri/resources-windows/service-fragment.wxs b/src-tauri/resources-windows/fragments/service.wxs similarity index 95% rename from src-tauri/resources-windows/service-fragment.wxs rename to src-tauri/resources-windows/fragments/service.wxs index 1c16bda7..835720c5 100644 --- a/src-tauri/resources-windows/service-fragment.wxs +++ b/src-tauri/resources-windows/fragments/service.wxs @@ -2,7 +2,7 @@ - + Date: Mon, 20 Oct 2025 12:06:24 +0200 Subject: [PATCH 10/29] add MSI properties to control script execution --- src-tauri/resources-windows/defguard-client.wxs | 6 +++++- .../resources-windows/fragments/provisioning.wxs | 11 ++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src-tauri/resources-windows/defguard-client.wxs b/src-tauri/resources-windows/defguard-client.wxs index a045af0a..168972aa 100644 --- a/src-tauri/resources-windows/defguard-client.wxs +++ b/src-tauri/resources-windows/defguard-client.wxs @@ -36,7 +36,11 @@ Vital="yes" Id="MainPackage" SourceFile="src-tauri\target\release\bundle\msi\defguard-client_$(env.VERSION)_x64_en-US.msi" - /> + > + + + + diff --git a/src-tauri/resources-windows/fragments/provisioning.wxs b/src-tauri/resources-windows/fragments/provisioning.wxs index d047ba9c..e2eabdbf 100644 --- a/src-tauri/resources-windows/fragments/provisioning.wxs +++ b/src-tauri/resources-windows/fragments/provisioning.wxs @@ -10,17 +10,22 @@ + + + + + + ExeCommand="powershell.exe -ExecutionPolicy Bypass -File "[#GetProvisioningConfigScript]" -Silent -ADAttribute "[ADATTRIBUTE]""/> - + - NOT REMOVE + PROVISIONING AND NOT REMOVE From a0ea6fd7e9c40440889d003ebb168a9dbdb8c2b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Tue, 21 Oct 2025 16:30:21 +0200 Subject: [PATCH 11/29] test new MSI build --- .github/workflows/release.yaml | 710 ++++++++++++++++----------------- 1 file changed, 355 insertions(+), 355 deletions(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 333398e7..5cad8c43 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -18,376 +18,376 @@ jobs: draft: true generate_release_notes: true - create-sbom: - needs: [create-release] - uses: ./.github/workflows/sbom.yml - with: - upload_url: ${{ needs.create-release.outputs.upload_url }} + # create-sbom: + # needs: [create-release] + # uses: ./.github/workflows/sbom.yml + # with: + # upload_url: ${{ needs.create-release.outputs.upload_url }} - build-linux: - needs: - - create-release - outputs: - deb_sha256_amd64: ${{ steps.calculate-sha256.outputs.deb_sha256_amd64 }} - runs-on: - - self-hosted - - Linux - - ${{ matrix.architecture }} - strategy: - fail-fast: false - matrix: - architecture: [ARM64, X64] - include: - - architecture: ARM64 - deb_arch: arm64 - binary_arch: aarch64 - - architecture: X64 - deb_arch: amd64 - binary_arch: x86_64 - steps: - - uses: actions/checkout@v5 - with: - submodules: "recursive" - - name: Write release version - run: | - VERSION=$(echo ${GITHUB_REF_NAME#v} | cut -d '-' -f1) - echo Version: $VERSION - echo "VERSION=$VERSION" >> ${GITHUB_ENV} - - uses: actions/setup-node@v5 - with: - node-version: "24" - - uses: pnpm/action-setup@v4 - with: - version: 10.17 - run_install: false - - name: Get pnpm store directory - shell: bash - run: | - echo "STORE_PATH=$(pnpm store path --silent)" >> ${GITHUB_ENV} - - uses: actions/cache@v4 - name: Setup pnpm cache - with: - path: ${{ env.STORE_PATH }} - key: ${{ runner.os }}-pnpm-build-store-${{ hashFiles('**/pnpm-lock.yaml') }} - restore-keys: | - ${{ runner.os }}-pnpm-build-store- - - name: Install Node dependencies - run: pnpm install --frozen-lockfile - - uses: dtolnay/rust-toolchain@stable - - name: Install Linux dependencies - run: | - sudo apt-get update - sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev patchelf libssl-dev libxdo-dev unzip protobuf-compiler libprotobuf-dev rpm - - name: Build packages - uses: tauri-apps/tauri-action@v0 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - args: "--bundles deb,rpm" - - name: Calculate DEB SHA256 - id: calculate-sha256 - if: matrix.deb_arch == 'amd64' - run: | - DEB_FILE="src-tauri/target/release/bundle/deb/defguard-client_${{ env.VERSION }}_${{ matrix.deb_arch }}.deb" - DEB_SHA256=$(sha256sum "$DEB_FILE" | cut -d ' ' -f1) - echo "DEB SHA256: $DEB_SHA256" - echo "DEB_SHA256=$DEB_SHA256" >> ${GITHUB_ENV} - echo "deb_sha256_${{ matrix.deb_arch }}=$DEB_SHA256" >> ${GITHUB_OUTPUT} - - name: Upload RPM - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ needs.create-release.outputs.upload_url }} - asset_path: src-tauri/target/release/bundle/rpm/defguard-client-${{ env.VERSION }}-1.${{ matrix.binary_arch }}.rpm - asset_name: defguard-client-${{ env.VERSION }}-1.${{ matrix.binary_arch }}.rpm - asset_content_type: application/octet-stream - - name: Upload DEB - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ needs.create-release.outputs.upload_url }} - asset_path: src-tauri/target/release/bundle/deb/defguard-client_${{ env.VERSION }}_${{ matrix.deb_arch }}.deb - asset_name: defguard-client_${{ env.VERSION }}_${{ matrix.deb_arch }}.deb - asset_content_type: application/octet-stream - - name: Install ruby with deb-s3 - if: matrix.build != 'freebsd' - run: | - sudo apt-get install -y ruby - gem install deb-s3 - echo "$(ruby -r rubygems -e 'puts Gem.user_dir')/bin" >> $GITHUB_PATH - - name: Upload DEB to APT repository #Add this to ubuntu 22.04 job (on merge dev -> main) with --codename=bookworm - run: | - COMPONENT=$([[ "${{ github.ref_name }}" == *"-"* ]] && echo "pre-release" || echo "release") # if tag contain "-" assume it's pre-release. + # build-linux: + # needs: + # - create-release + # outputs: + # deb_sha256_amd64: ${{ steps.calculate-sha256.outputs.deb_sha256_amd64 }} + # runs-on: + # - self-hosted + # - Linux + # - ${{ matrix.architecture }} + # strategy: + # fail-fast: false + # matrix: + # architecture: [ARM64, X64] + # include: + # - architecture: ARM64 + # deb_arch: arm64 + # binary_arch: aarch64 + # - architecture: X64 + # deb_arch: amd64 + # binary_arch: x86_64 + # steps: + # - uses: actions/checkout@v5 + # with: + # submodules: "recursive" + # - name: Write release version + # run: | + # VERSION=$(echo ${GITHUB_REF_NAME#v} | cut -d '-' -f1) + # echo Version: $VERSION + # echo "VERSION=$VERSION" >> ${GITHUB_ENV} + # - uses: actions/setup-node@v5 + # with: + # node-version: "24" + # - uses: pnpm/action-setup@v4 + # with: + # version: 10.17 + # run_install: false + # - name: Get pnpm store directory + # shell: bash + # run: | + # echo "STORE_PATH=$(pnpm store path --silent)" >> ${GITHUB_ENV} + # - uses: actions/cache@v4 + # name: Setup pnpm cache + # with: + # path: ${{ env.STORE_PATH }} + # key: ${{ runner.os }}-pnpm-build-store-${{ hashFiles('**/pnpm-lock.yaml') }} + # restore-keys: | + # ${{ runner.os }}-pnpm-build-store- + # - name: Install Node dependencies + # run: pnpm install --frozen-lockfile + # - uses: dtolnay/rust-toolchain@stable + # - name: Install Linux dependencies + # run: | + # sudo apt-get update + # sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev patchelf libssl-dev libxdo-dev unzip protobuf-compiler libprotobuf-dev rpm + # - name: Build packages + # uses: tauri-apps/tauri-action@v0 + # env: + # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # with: + # args: "--bundles deb,rpm" + # - name: Calculate DEB SHA256 + # id: calculate-sha256 + # if: matrix.deb_arch == 'amd64' + # run: | + # DEB_FILE="src-tauri/target/release/bundle/deb/defguard-client_${{ env.VERSION }}_${{ matrix.deb_arch }}.deb" + # DEB_SHA256=$(sha256sum "$DEB_FILE" | cut -d ' ' -f1) + # echo "DEB SHA256: $DEB_SHA256" + # echo "DEB_SHA256=$DEB_SHA256" >> ${GITHUB_ENV} + # echo "deb_sha256_${{ matrix.deb_arch }}=$DEB_SHA256" >> ${GITHUB_OUTPUT} + # - name: Upload RPM + # uses: actions/upload-release-asset@v1 + # env: + # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # with: + # upload_url: ${{ needs.create-release.outputs.upload_url }} + # asset_path: src-tauri/target/release/bundle/rpm/defguard-client-${{ env.VERSION }}-1.${{ matrix.binary_arch }}.rpm + # asset_name: defguard-client-${{ env.VERSION }}-1.${{ matrix.binary_arch }}.rpm + # asset_content_type: application/octet-stream + # - name: Upload DEB + # uses: actions/upload-release-asset@v1 + # env: + # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # with: + # upload_url: ${{ needs.create-release.outputs.upload_url }} + # asset_path: src-tauri/target/release/bundle/deb/defguard-client_${{ env.VERSION }}_${{ matrix.deb_arch }}.deb + # asset_name: defguard-client_${{ env.VERSION }}_${{ matrix.deb_arch }}.deb + # asset_content_type: application/octet-stream + # - name: Install ruby with deb-s3 + # if: matrix.build != 'freebsd' + # run: | + # sudo apt-get install -y ruby + # gem install deb-s3 + # echo "$(ruby -r rubygems -e 'puts Gem.user_dir')/bin" >> $GITHUB_PATH + # - name: Upload DEB to APT repository #Add this to ubuntu 22.04 job (on merge dev -> main) with --codename=bookworm + # run: | + # COMPONENT=$([[ "${{ github.ref_name }}" == *"-"* ]] && echo "pre-release" || echo "release") # if tag contain "-" assume it's pre-release. - deb-s3 upload -l --bucket=apt.defguard.net --access-key-id=${{ secrets.AWS_ACCESS_KEY_APT }} --secret-access-key=${{ secrets.AWS_SECRET_KEY_APT }} --s3-region=eu-north-1 --no-fail-if-exists --codename=trixie --component="$COMPONENT" src-tauri/target/release/bundle/deb/defguard-client_${{ env.VERSION }}_${{ matrix.deb_arch }}.deb - - name: Rename client binary - run: mv src-tauri/target/release/defguard-client defguard-client-linux-${{ matrix.binary_arch }}-${{ github.ref_name }} - - name: Tar client binary - uses: a7ul/tar-action@v1.2.0 - with: - command: c - files: | - defguard-client-linux-${{ matrix.binary_arch }}-${{ github.ref_name }} - outPath: defguard-client-linux-${{ matrix.binary_arch }}-${{ github.ref_name }}.tar.gz - - name: Upload client archive - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ needs.create-release.outputs.upload_url }} - asset_path: defguard-client-linux-${{ matrix.binary_arch }}-${{ github.ref_name }}.tar.gz - asset_name: defguard-client-linux-${{ matrix.binary_arch }}-${{ github.ref_name }}.tar.gz - asset_content_type: application/octet-stream - - name: Rename daemon binary - run: mv src-tauri/target/release/defguard-service defguard-service-linux-${{ matrix.binary_arch }}-${{ github.ref_name }} - - name: Tar daemon binary - uses: a7ul/tar-action@v1.2.0 - with: - command: c - files: | - defguard-service-linux-${{ matrix.binary_arch }}-${{ github.ref_name }} - outPath: defguard-service-linux-${{ matrix.binary_arch }}-${{ github.ref_name }}.tar.gz - - name: Upload daemon archive - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ needs.create-release.outputs.upload_url }} - asset_path: defguard-service-linux-${{ matrix.binary_arch }}-${{ github.ref_name }}.tar.gz - asset_name: defguard-service-linux-${{ matrix.binary_arch }}-${{ github.ref_name }}.tar.gz - asset_content_type: application/octet-stream + # deb-s3 upload -l --bucket=apt.defguard.net --access-key-id=${{ secrets.AWS_ACCESS_KEY_APT }} --secret-access-key=${{ secrets.AWS_SECRET_KEY_APT }} --s3-region=eu-north-1 --no-fail-if-exists --codename=trixie --component="$COMPONENT" src-tauri/target/release/bundle/deb/defguard-client_${{ env.VERSION }}_${{ matrix.deb_arch }}.deb + # - name: Rename client binary + # run: mv src-tauri/target/release/defguard-client defguard-client-linux-${{ matrix.binary_arch }}-${{ github.ref_name }} + # - name: Tar client binary + # uses: a7ul/tar-action@v1.2.0 + # with: + # command: c + # files: | + # defguard-client-linux-${{ matrix.binary_arch }}-${{ github.ref_name }} + # outPath: defguard-client-linux-${{ matrix.binary_arch }}-${{ github.ref_name }}.tar.gz + # - name: Upload client archive + # uses: actions/upload-release-asset@v1 + # env: + # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # with: + # upload_url: ${{ needs.create-release.outputs.upload_url }} + # asset_path: defguard-client-linux-${{ matrix.binary_arch }}-${{ github.ref_name }}.tar.gz + # asset_name: defguard-client-linux-${{ matrix.binary_arch }}-${{ github.ref_name }}.tar.gz + # asset_content_type: application/octet-stream + # - name: Rename daemon binary + # run: mv src-tauri/target/release/defguard-service defguard-service-linux-${{ matrix.binary_arch }}-${{ github.ref_name }} + # - name: Tar daemon binary + # uses: a7ul/tar-action@v1.2.0 + # with: + # command: c + # files: | + # defguard-service-linux-${{ matrix.binary_arch }}-${{ github.ref_name }} + # outPath: defguard-service-linux-${{ matrix.binary_arch }}-${{ github.ref_name }}.tar.gz + # - name: Upload daemon archive + # uses: actions/upload-release-asset@v1 + # env: + # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # with: + # upload_url: ${{ needs.create-release.outputs.upload_url }} + # asset_path: defguard-service-linux-${{ matrix.binary_arch }}-${{ github.ref_name }}.tar.gz + # asset_name: defguard-service-linux-${{ matrix.binary_arch }}-${{ github.ref_name }}.tar.gz + # asset_content_type: application/octet-stream - - name: Rename dg binary - run: mv src-tauri/target/release/dg dg-linux-${{ matrix.binary_arch }}-${{ github.ref_name }} - - name: Tar dg binary - uses: a7ul/tar-action@v1.2.0 - with: - command: c - files: | - dg-linux-${{ matrix.binary_arch }}-${{ github.ref_name }} - outPath: dg-linux-${{ matrix.binary_arch }}-${{ github.ref_name }}.tar.gz - - name: Upload dg archive - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ needs.create-release.outputs.upload_url }} - asset_path: dg-linux-${{ matrix.binary_arch }}-${{ github.ref_name }}.tar.gz - asset_name: dg-linux-${{ matrix.binary_arch }}-${{ github.ref_name }}.tar.gz - asset_content_type: application/octet-stream - - name: Build dg deb - uses: defGuard/fpm-action@main - with: - fpm_args: "dg-linux-${{ matrix.binary_arch }}-${{ github.ref_name }}=/usr/sbin/dg dg.service=/usr/lib/systemd/system/dg.service src-tauri/cli/.env=/etc/defguard/dg.conf" - fpm_opts: "--architecture ${{ matrix.binary_arch }} --debug --output-type deb --version ${{ env.VERSION }} --package dg-linux-${{ matrix.binary_arch }}-${{ github.ref_name }}.deb" - - name: Upload DEB - uses: actions/upload-release-asset@v1.0.2 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ needs.create-release.outputs.upload_url }} - asset_path: dg-linux-${{ matrix.binary_arch }}-${{ github.ref_name }}.deb - asset_name: dg-linux-${{ matrix.binary_arch }}-${{ github.ref_name }}.deb - asset_content_type: application/octet-stream - - name: Build dg rpm - uses: defGuard/fpm-action@main - with: - fpm_args: "dg-linux-${{ matrix.binary_arch }}-${{ github.ref_name }}=/usr/sbin/dg dg.service=/usr/lib/systemd/system/dg.service src-tauri/cli/.env=/etc/defguard/dg.conf" - fpm_opts: "--architecture ${{ matrix.binary_arch }} --debug --output-type rpm --version ${{ env.VERSION }} --package dg-linux-${{ matrix.binary_arch }}-${{ github.ref_name }}.rpm" - - name: Upload RPM - uses: actions/upload-release-asset@v1.0.2 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ needs.create-release.outputs.upload_url }} - asset_path: dg-linux-${{ matrix.binary_arch }}-${{ github.ref_name }}.rpm - asset_name: dg-linux-${{ matrix.binary_arch }}-${{ github.ref_name }}.rpm - asset_content_type: application/octet-stream + # - name: Rename dg binary + # run: mv src-tauri/target/release/dg dg-linux-${{ matrix.binary_arch }}-${{ github.ref_name }} + # - name: Tar dg binary + # uses: a7ul/tar-action@v1.2.0 + # with: + # command: c + # files: | + # dg-linux-${{ matrix.binary_arch }}-${{ github.ref_name }} + # outPath: dg-linux-${{ matrix.binary_arch }}-${{ github.ref_name }}.tar.gz + # - name: Upload dg archive + # uses: actions/upload-release-asset@v1 + # env: + # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # with: + # upload_url: ${{ needs.create-release.outputs.upload_url }} + # asset_path: dg-linux-${{ matrix.binary_arch }}-${{ github.ref_name }}.tar.gz + # asset_name: dg-linux-${{ matrix.binary_arch }}-${{ github.ref_name }}.tar.gz + # asset_content_type: application/octet-stream + # - name: Build dg deb + # uses: defGuard/fpm-action@main + # with: + # fpm_args: "dg-linux-${{ matrix.binary_arch }}-${{ github.ref_name }}=/usr/sbin/dg dg.service=/usr/lib/systemd/system/dg.service src-tauri/cli/.env=/etc/defguard/dg.conf" + # fpm_opts: "--architecture ${{ matrix.binary_arch }} --debug --output-type deb --version ${{ env.VERSION }} --package dg-linux-${{ matrix.binary_arch }}-${{ github.ref_name }}.deb" + # - name: Upload DEB + # uses: actions/upload-release-asset@v1.0.2 + # env: + # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # with: + # upload_url: ${{ needs.create-release.outputs.upload_url }} + # asset_path: dg-linux-${{ matrix.binary_arch }}-${{ github.ref_name }}.deb + # asset_name: dg-linux-${{ matrix.binary_arch }}-${{ github.ref_name }}.deb + # asset_content_type: application/octet-stream + # - name: Build dg rpm + # uses: defGuard/fpm-action@main + # with: + # fpm_args: "dg-linux-${{ matrix.binary_arch }}-${{ github.ref_name }}=/usr/sbin/dg dg.service=/usr/lib/systemd/system/dg.service src-tauri/cli/.env=/etc/defguard/dg.conf" + # fpm_opts: "--architecture ${{ matrix.binary_arch }} --debug --output-type rpm --version ${{ env.VERSION }} --package dg-linux-${{ matrix.binary_arch }}-${{ github.ref_name }}.rpm" + # - name: Upload RPM + # uses: actions/upload-release-asset@v1.0.2 + # env: + # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # with: + # upload_url: ${{ needs.create-release.outputs.upload_url }} + # asset_path: dg-linux-${{ matrix.binary_arch }}-${{ github.ref_name }}.rpm + # asset_name: dg-linux-${{ matrix.binary_arch }}-${{ github.ref_name }}.rpm + # asset_content_type: application/octet-stream - apt-sign: - needs: #Add needs: -ubuntu-22-04-build (on merge dev -> main) - - build-linux - runs-on: - - self-hosted - - Linux - - X64 - strategy: - fail-fast: false - steps: - - name: Sign APT repository - run: | - export AWS_ACCESS_KEY_ID=${{ secrets.AWS_ACCESS_KEY_APT }} - export AWS_SECRET_ACCESS_KEY=${{ secrets.AWS_SECRET_KEY_APT }} - export AWS_REGION=eu-north-1 - sudo apt update -y - sudo apt install -y awscli curl jq + # apt-sign: + # needs: #Add needs: -ubuntu-22-04-build (on merge dev -> main) + # - build-linux + # runs-on: + # - self-hosted + # - Linux + # - X64 + # strategy: + # fail-fast: false + # steps: + # - name: Sign APT repository + # run: | + # export AWS_ACCESS_KEY_ID=${{ secrets.AWS_ACCESS_KEY_APT }} + # export AWS_SECRET_ACCESS_KEY=${{ secrets.AWS_SECRET_KEY_APT }} + # export AWS_REGION=eu-north-1 + # sudo apt update -y + # sudo apt install -y awscli curl jq - for DIST in trixie bookworm; do - aws s3 cp s3://apt.defguard.net/dists/${DIST}/Release . + # for DIST in trixie bookworm; do + # aws s3 cp s3://apt.defguard.net/dists/${DIST}/Release . - curl -X POST "${{ secrets.DEFGUARD_SIGNING_URL }}?signature_type=both" \ - -H "Authorization: Bearer ${{ secrets.DEFGUARD_SIGNING_API_KEY }}" \ - -F "file=@Release" \ - -o response.json + # curl -X POST "${{ secrets.DEFGUARD_SIGNING_URL }}?signature_type=both" \ + # -H "Authorization: Bearer ${{ secrets.DEFGUARD_SIGNING_API_KEY }}" \ + # -F "file=@Release" \ + # -o response.json - cat response.json | jq -r '.files["Release.gpg"].content' | base64 --decode > Release.gpg - cat response.json | jq -r '.files.Release.content' | base64 --decode > InRelease + # cat response.json | jq -r '.files["Release.gpg"].content' | base64 --decode > Release.gpg + # cat response.json | jq -r '.files.Release.content' | base64 --decode > InRelease - aws s3 cp Release.gpg s3://apt.defguard.net/dists/${DIST}/ --acl public-read - aws s3 cp InRelease s3://apt.defguard.net/dists/${DIST}/ --acl public-read + # aws s3 cp Release.gpg s3://apt.defguard.net/dists/${DIST}/ --acl public-read + # aws s3 cp InRelease s3://apt.defguard.net/dists/${DIST}/ --acl public-read - aws s3 ls s3://apt.defguard.net/dists/ --recursive | awk '{print ""$4"
"}' > index.html - aws s3 cp index.html s3://apt.defguard.net/ --acl public-read - done + # aws s3 ls s3://apt.defguard.net/dists/ --recursive | awk '{print ""$4"
"}' > index.html + # aws s3 cp index.html s3://apt.defguard.net/ --acl public-read + # done - update-aur: - needs: - - create-release - - build-linux - if: "!contains(github.ref_name, '-')" - runs-on: - - self-hosted - - Linux - - ${{ matrix.architecture }} - container: archlinux:latest - strategy: - fail-fast: false - matrix: - architecture: [X64] - include: - - architecture: X64 - deb_arch: amd64 - binary_arch: x86_64 - steps: - - name: Install dependencies - run: | - pacman -Syu --noconfirm - pacman -S --noconfirm git openssh base-devel - - name: Create non-root user - run: | - useradd -m -G wheel -s /bin/bash builduser - echo 'builduser ALL=(ALL) NOPASSWD: ALL' >> /etc/sudoers - - name: Setup SSH - uses: webfactory/ssh-agent@v0.9.0 - with: - ssh-private-key: ${{ secrets.AUR_SSH_KEY }} - - name: Checkout AUR repository - run: | - mkdir -p ~/.ssh - ssh-keyscan -H aur.archlinux.org >> ~/.ssh/known_hosts - chmod 644 ~/.ssh/known_hosts - export GIT_SSH_COMMAND="ssh -o StrictHostKeyChecking=accept-new" - rm -rf aur-repo - git clone ssh://aur@aur.archlinux.org/defguard-client.git aur-repo - chown -R builduser:builduser aur-repo - - name: Update PKGBUILD version - run: | - cd aur-repo - VERSION=$(echo ${GITHUB_REF_NAME#v} | cut -d '-' -f1) + # update-aur: + # needs: + # - create-release + # - build-linux + # if: "!contains(github.ref_name, '-')" + # runs-on: + # - self-hosted + # - Linux + # - ${{ matrix.architecture }} + # container: archlinux:latest + # strategy: + # fail-fast: false + # matrix: + # architecture: [X64] + # include: + # - architecture: X64 + # deb_arch: amd64 + # binary_arch: x86_64 + # steps: + # - name: Install dependencies + # run: | + # pacman -Syu --noconfirm + # pacman -S --noconfirm git openssh base-devel + # - name: Create non-root user + # run: | + # useradd -m -G wheel -s /bin/bash builduser + # echo 'builduser ALL=(ALL) NOPASSWD: ALL' >> /etc/sudoers + # - name: Setup SSH + # uses: webfactory/ssh-agent@v0.9.0 + # with: + # ssh-private-key: ${{ secrets.AUR_SSH_KEY }} + # - name: Checkout AUR repository + # run: | + # mkdir -p ~/.ssh + # ssh-keyscan -H aur.archlinux.org >> ~/.ssh/known_hosts + # chmod 644 ~/.ssh/known_hosts + # export GIT_SSH_COMMAND="ssh -o StrictHostKeyChecking=accept-new" + # rm -rf aur-repo + # git clone ssh://aur@aur.archlinux.org/defguard-client.git aur-repo + # chown -R builduser:builduser aur-repo + # - name: Update PKGBUILD version + # run: | + # cd aur-repo + # VERSION=$(echo ${GITHUB_REF_NAME#v} | cut -d '-' -f1) - echo "Updating to version: $VERSION" - sed -i "s/^pkgver=.*/pkgver=$VERSION/" PKGBUILD + # echo "Updating to version: $VERSION" + # sed -i "s/^pkgver=.*/pkgver=$VERSION/" PKGBUILD - AMD64_SHA="${{ needs.build-linux.outputs.deb_sha256_amd64 }}" + # AMD64_SHA="${{ needs.build-linux.outputs.deb_sha256_amd64 }}" - echo "AMD64 DEB SHA256: $AMD64_SHA" - sed -i "s/^sha256sums_x86_64=.*/sha256sums_x86_64=('$AMD64_SHA')/" PKGBUILD - - name: Update .SRCINFO - run: | - cd aur-repo - sudo -u builduser makepkg --printsrcinfo > .SRCINFO - - name: Commit and push changes - run: | - cd aur-repo - chown -R builduser:builduser . - sudo -u builduser git config user.name "Defguard Build System" - sudo -u builduser git config user.email "community@defguard.net" - sudo -u builduser git add PKGBUILD .SRCINFO - sudo -u builduser git commit -m "Updated to $VERSION" - sudo -u builduser git push - cat PKGBUILD - cat .SRCINFO + # echo "AMD64 DEB SHA256: $AMD64_SHA" + # sed -i "s/^sha256sums_x86_64=.*/sha256sums_x86_64=('$AMD64_SHA')/" PKGBUILD + # - name: Update .SRCINFO + # run: | + # cd aur-repo + # sudo -u builduser makepkg --printsrcinfo > .SRCINFO + # - name: Commit and push changes + # run: | + # cd aur-repo + # chown -R builduser:builduser . + # sudo -u builduser git config user.name "Defguard Build System" + # sudo -u builduser git config user.email "community@defguard.net" + # sudo -u builduser git add PKGBUILD .SRCINFO + # sudo -u builduser git commit -m "Updated to $VERSION" + # sudo -u builduser git push + # cat PKGBUILD + # cat .SRCINFO - build-macos: - needs: - - create-release - strategy: - fail-fast: false - matrix: - target: [aarch64-apple-darwin, x86_64-apple-darwin] - runs-on: - - self-hosted - - macOS - env: - APPLE_SIGNING_IDENTITY_APPLICATION: "Developer ID Application: defguard sp. z o.o. (82GZ7KN29J)" - APPLE_SIGNING_IDENTITY_INSTALLER: "Developer ID Installer: defguard sp. z o.o. (82GZ7KN29J)" - APPLE_ID: "kamil@defguard.net" - APPLE_TEAM_ID: "82GZ7KN29J" - steps: - - uses: actions/checkout@v5 - with: - submodules: "recursive" - - name: Write release version - run: | - VERSION=$(echo ${GITHUB_REF_NAME#v} | cut -d '-' -f1) - echo Version: $VERSION - echo "VERSION=$VERSION" >> ${GITHUB_ENV} - - uses: actions/setup-node@v4 - with: - node-version: "22" - - uses: pnpm/action-setup@v4 - with: - version: 10 - run_install: false - - name: Get pnpm store directory - shell: bash - run: echo "STORE_PATH=$(pnpm store path --silent)" >> ${GITHUB_ENV} - - uses: actions/cache@v4 - name: Setup pnpm cache - with: - path: ${{ env.STORE_PATH }} - key: ${{ runner.os }}-pnpm-build-store-${{ hashFiles('**/pnpm-lock.yaml') }} - restore-keys: | - ${{ runner.os }}-pnpm-build-store- - - name: Install deps - run: pnpm install --frozen-lockfile - - uses: dtolnay/rust-toolchain@stable - - name: Install protobuf compiler - run: brew install protobuf - - name: Install ARM target - run: rustup target add aarch64-apple-darwin - - name: Unlock keychain - run: security -v unlock-keychain -p "${{ secrets.KEYCHAIN_PASSWORD }}" /Users/admin/Library/Keychains/login.keychain - - name: Build app - uses: tauri-apps/tauri-action@v0 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - APPLE_SIGNING_IDENTITY: ${{ env.APPLE_SIGNING_IDENTITY_APPLICATION }} - APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} - APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} - APPLE_ID: ${{ env.APPLE_ID }} - APPLE_PASSWORD: ${{ secrets.NOTARYTOOL_APP_SPECIFIC_PASSWORD }} - APPLE_TEAM_ID: ${{ env.APPLE_TEAM_ID }} - with: - args: --target ${{ matrix.target }} -v - - name: Build installation package - run: | - bash build-macos-package.sh src-tauri/target/${{ matrix.target }} src-tauri/resources-macos/scripts '${{ env.APPLE_SIGNING_IDENTITY_INSTALLER }}' /Users/admin/Library/Keychains/login.keychain - xcrun notarytool submit --wait --apple-id ${{ env.APPLE_ID }} --password ${{ secrets.NOTARYTOOL_APP_SPECIFIC_PASSWORD }} --team-id ${{ env.APPLE_TEAM_ID }} src-tauri/target/${{ matrix.target }}/product-signed/defguard.pkg - xcrun stapler staple src-tauri/target/${{ matrix.target }}/product-signed/defguard.pkg - - name: Upload installation package - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ needs.create-release.outputs.upload_url }} - asset_path: src-tauri/target/${{ matrix.target }}/product-signed/defguard.pkg - asset_name: defguard-${{ matrix.target }}-${{ env.VERSION }}.pkg - asset_content_type: application/octet-stream + # build-macos: + # needs: + # - create-release + # strategy: + # fail-fast: false + # matrix: + # target: [aarch64-apple-darwin, x86_64-apple-darwin] + # runs-on: + # - self-hosted + # - macOS + # env: + # APPLE_SIGNING_IDENTITY_APPLICATION: "Developer ID Application: defguard sp. z o.o. (82GZ7KN29J)" + # APPLE_SIGNING_IDENTITY_INSTALLER: "Developer ID Installer: defguard sp. z o.o. (82GZ7KN29J)" + # APPLE_ID: "kamil@defguard.net" + # APPLE_TEAM_ID: "82GZ7KN29J" + # steps: + # - uses: actions/checkout@v5 + # with: + # submodules: "recursive" + # - name: Write release version + # run: | + # VERSION=$(echo ${GITHUB_REF_NAME#v} | cut -d '-' -f1) + # echo Version: $VERSION + # echo "VERSION=$VERSION" >> ${GITHUB_ENV} + # - uses: actions/setup-node@v4 + # with: + # node-version: "22" + # - uses: pnpm/action-setup@v4 + # with: + # version: 10 + # run_install: false + # - name: Get pnpm store directory + # shell: bash + # run: echo "STORE_PATH=$(pnpm store path --silent)" >> ${GITHUB_ENV} + # - uses: actions/cache@v4 + # name: Setup pnpm cache + # with: + # path: ${{ env.STORE_PATH }} + # key: ${{ runner.os }}-pnpm-build-store-${{ hashFiles('**/pnpm-lock.yaml') }} + # restore-keys: | + # ${{ runner.os }}-pnpm-build-store- + # - name: Install deps + # run: pnpm install --frozen-lockfile + # - uses: dtolnay/rust-toolchain@stable + # - name: Install protobuf compiler + # run: brew install protobuf + # - name: Install ARM target + # run: rustup target add aarch64-apple-darwin + # - name: Unlock keychain + # run: security -v unlock-keychain -p "${{ secrets.KEYCHAIN_PASSWORD }}" /Users/admin/Library/Keychains/login.keychain + # - name: Build app + # uses: tauri-apps/tauri-action@v0 + # env: + # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # APPLE_SIGNING_IDENTITY: ${{ env.APPLE_SIGNING_IDENTITY_APPLICATION }} + # APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} + # APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} + # APPLE_ID: ${{ env.APPLE_ID }} + # APPLE_PASSWORD: ${{ secrets.NOTARYTOOL_APP_SPECIFIC_PASSWORD }} + # APPLE_TEAM_ID: ${{ env.APPLE_TEAM_ID }} + # with: + # args: --target ${{ matrix.target }} -v + # - name: Build installation package + # run: | + # bash build-macos-package.sh src-tauri/target/${{ matrix.target }} src-tauri/resources-macos/scripts '${{ env.APPLE_SIGNING_IDENTITY_INSTALLER }}' /Users/admin/Library/Keychains/login.keychain + # xcrun notarytool submit --wait --apple-id ${{ env.APPLE_ID }} --password ${{ secrets.NOTARYTOOL_APP_SPECIFIC_PASSWORD }} --team-id ${{ env.APPLE_TEAM_ID }} src-tauri/target/${{ matrix.target }}/product-signed/defguard.pkg + # xcrun stapler staple src-tauri/target/${{ matrix.target }}/product-signed/defguard.pkg + # - name: Upload installation package + # uses: actions/upload-release-asset@v1 + # env: + # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # with: + # upload_url: ${{ needs.create-release.outputs.upload_url }} + # asset_path: src-tauri/target/${{ matrix.target }}/product-signed/defguard.pkg + # asset_name: defguard-${{ matrix.target }}-${{ env.VERSION }}.pkg + # asset_content_type: application/octet-stream - # Builds Windows MSI and uploads it as artifact + # # Builds Windows MSI and uploads it as artifact build-windows: needs: - create-release From 3e1b96777f1d81a7590a6c56e76df213a395b27a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Thu, 23 Oct 2025 08:02:12 +0200 Subject: [PATCH 12/29] remove silent mode switch --- .../scripts/Get-ProvisioningConfig.ps1 | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/src-tauri/resources-windows/scripts/Get-ProvisioningConfig.ps1 b/src-tauri/resources-windows/scripts/Get-ProvisioningConfig.ps1 index 4987cced..c270e72a 100644 --- a/src-tauri/resources-windows/scripts/Get-ProvisioningConfig.ps1 +++ b/src-tauri/resources-windows/scripts/Get-ProvisioningConfig.ps1 @@ -12,15 +12,11 @@ - Workgroup: Exits gracefully The retrieved enrollment data is saved to a JSON file for the Defguard client to use. -.PARAMETER Silent - Suppresses interactive authentication prompts for Entra ID (will fail if not already authenticated) - .PARAMETER ADAttribute Specifies which Active Directory attribute to read from (default: extensionAttribute1) #> param( - [switch]$Silent, [string]$ADAttribute = "extensionAttribute1" ) @@ -193,8 +189,6 @@ function Get-OnPremisesADProvisioningConfig { # Get Defguard client provisioning config from Entra ID function Get-EntraIDProvisioningConfig { - param([bool]$SilentMode) - # Check if Microsoft.Graph module is available if (-not (Get-Module -ListAvailable -Name Microsoft.Graph.Users)) { Write-Host "Microsoft.Graph.Users module is not installed." -ForegroundColor Yellow @@ -215,11 +209,6 @@ function Get-EntraIDProvisioningConfig { $context = Get-MgContext -ErrorAction SilentlyContinue if (-not $context) { - if ($SilentMode) { - Write-Host "Not authenticated to Microsoft Graph and silent mode is enabled. Cannot proceed." -ForegroundColor Yellow - return - } - Write-Host "Connecting to Microsoft Graph (authentication required)..." -ForegroundColor Yellow Write-Host "Note: Requesting additional permissions for custom security attributes..." -ForegroundColor Gray Connect-MgGraph -Scopes "User.Read", "CustomSecAttributeAssignment.Read.All" -ErrorAction Stop @@ -327,7 +316,7 @@ if ($joinType -eq "OnPremisesAD") { if ($joinStatus.Domain) { Write-Host " Tenant: $($joinStatus.Domain)" -ForegroundColor Gray } - Get-EntraIDProvisioningConfig -SilentMode $Silent + Get-EntraIDProvisioningConfig exit 0 From 57dd8fcefac8251a0cd7c70b018dd129b7a13220 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Thu, 23 Oct 2025 09:07:05 +0200 Subject: [PATCH 13/29] fix provisioning fragment --- .../fragments/provisioning.wxs | 17 ++++++----------- src-tauri/tauri.windows.conf.json | 1 - 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/src-tauri/resources-windows/fragments/provisioning.wxs b/src-tauri/resources-windows/fragments/provisioning.wxs index e2eabdbf..a9712e30 100644 --- a/src-tauri/resources-windows/fragments/provisioning.wxs +++ b/src-tauri/resources-windows/fragments/provisioning.wxs @@ -1,17 +1,12 @@ - + - - - - - - - - + + + + - @@ -21,7 +16,7 @@ Execute="deferred" Impersonate="yes" Return="check" - ExeCommand="powershell.exe -ExecutionPolicy Bypass -File "[#GetProvisioningConfigScript]" -Silent -ADAttribute "[ADATTRIBUTE]""/> + ExeCommand="powershell.exe -ExecutionPolicy Bypass -File "[INSTALLDIR]Get-ProvisioningConfig.ps1" -ADAttribute "[ADATTRIBUTE]""/> diff --git a/src-tauri/tauri.windows.conf.json b/src-tauri/tauri.windows.conf.json index 0c67c0db..48f59305 100644 --- a/src-tauri/tauri.windows.conf.json +++ b/src-tauri/tauri.windows.conf.json @@ -5,7 +5,6 @@ ], "resources": [ "resources-windows/binaries/*", - "resources-windows/scripts/*", "resources/icons/*" ] } From 72d0c3bcad7b707a7b4b04a65507e5c22163ca91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Thu, 23 Oct 2025 09:08:44 +0200 Subject: [PATCH 14/29] restore upgrade code --- src-tauri/tauri.conf.json | 1 + 1 file changed, 1 insertion(+) diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 9e677a4a..5acfe58d 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -27,6 +27,7 @@ "digestAlgorithm": "sha256", "timestampUrl": "", "wix": { + "upgradeCode": "923b21f5-7d3f-4f5e-8dcb-43fe1c65fb43", "fragmentPaths": [ "./resources-windows/fragments/service.wxs", "./resources-windows/fragments/provisioning.wxs" From 94eb33a345edf18f7ee2506748511df4a3f2e702 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Thu, 23 Oct 2025 10:01:02 +0200 Subject: [PATCH 15/29] add provisioning script logging --- .../resources-windows/scripts/Get-ProvisioningConfig.ps1 | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src-tauri/resources-windows/scripts/Get-ProvisioningConfig.ps1 b/src-tauri/resources-windows/scripts/Get-ProvisioningConfig.ps1 index c270e72a..ea1cd262 100644 --- a/src-tauri/resources-windows/scripts/Get-ProvisioningConfig.ps1 +++ b/src-tauri/resources-windows/scripts/Get-ProvisioningConfig.ps1 @@ -288,6 +288,11 @@ function Get-EntraIDProvisioningConfig { } } +# Log all script output to file +$defguardDir = Join-Path $env:APPDATA "net.defguard" +$logFilePath = Join-Path $defguardDir "provisioning_log.txt" +Start-Transcript -Path $logFilePath + # Main script execution Write-Host "Detecting domain join status..." -ForegroundColor Gray From 34297b817070685a3a4f8d2ea3379fc7f4b68d8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Thu, 23 Oct 2025 10:03:03 +0200 Subject: [PATCH 16/29] rename provisioning config file --- src-tauri/resources-windows/scripts/Get-ProvisioningConfig.ps1 | 2 +- src-tauri/src/enterprise/provisioning/mod.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src-tauri/resources-windows/scripts/Get-ProvisioningConfig.ps1 b/src-tauri/resources-windows/scripts/Get-ProvisioningConfig.ps1 index ea1cd262..fe62b915 100644 --- a/src-tauri/resources-windows/scripts/Get-ProvisioningConfig.ps1 +++ b/src-tauri/resources-windows/scripts/Get-ProvisioningConfig.ps1 @@ -77,7 +77,7 @@ function Save-DefguardEnrollmentData { # Create Defguard directory in AppData\Roaming $defguardDir = Join-Path $env:APPDATA "net.defguard" - $jsonOutputPath = Join-Path $defguardDir "enrollment.json" + $jsonOutputPath = Join-Path $defguardDir "provisioning.json" try { # Create directory if it doesn't exist diff --git a/src-tauri/src/enterprise/provisioning/mod.rs b/src-tauri/src/enterprise/provisioning/mod.rs index 41cc7caf..8a8ec1d9 100644 --- a/src-tauri/src/enterprise/provisioning/mod.rs +++ b/src-tauri/src/enterprise/provisioning/mod.rs @@ -5,7 +5,7 @@ use tauri::{AppHandle, Manager}; use crate::database::{models::instance::Instance, DB_POOL}; -const CONFIG_FILE_NAME: &str = "enrollment.json"; +const CONFIG_FILE_NAME: &str = "provisioning.json"; #[derive(Clone, Debug, Deserialize, Serialize)] pub struct ProvisioningConfig { From 23310939ca0554102ac38636840de74fb5f620c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Thu, 23 Oct 2025 10:49:30 +0200 Subject: [PATCH 17/29] ensure transcript stop --- .../scripts/Get-ProvisioningConfig.ps1 | 44 +++++++------------ 1 file changed, 16 insertions(+), 28 deletions(-) diff --git a/src-tauri/resources-windows/scripts/Get-ProvisioningConfig.ps1 b/src-tauri/resources-windows/scripts/Get-ProvisioningConfig.ps1 index fe62b915..a7fc7503 100644 --- a/src-tauri/resources-windows/scripts/Get-ProvisioningConfig.ps1 +++ b/src-tauri/resources-windows/scripts/Get-ProvisioningConfig.ps1 @@ -302,36 +302,24 @@ $joinType = $joinStatus.JoinType Write-Host "Join Type = '$joinType'" -ForegroundColor Magenta if ($joinType -eq "OnPremisesAD") { - Write-Host "Connected to on-premises Active Directory: $($joinStatus.Domain)" -ForegroundColor Green - $currentUser = $env:USERNAME - Get-OnPremisesADProvisioningConfig -Username $currentUser -ADAttribute $ADAttribute - exit 0 - - + Write-Host "Connected to on-premises Active Directory: $($joinStatus.Domain)" -ForegroundColor Green + $currentUser = $env:USERNAME + Get-OnPremisesADProvisioningConfig -Username $currentUser -ADAttribute $ADAttribute } elseif ($joinType -eq "Hybrid") { - Write-Host "Hybrid join detected (both on-premises AD and Entra ID): $($joinStatus.Domain)" -ForegroundColor Green - Write-Host "Querying on-premises Active Directory..." -ForegroundColor Gray - $currentUser = $env:USERNAME - Get-OnPremisesADProvisioningConfig -Username $currentUser -ADAttribute $ADAttribute - exit 0 - - + Write-Host "Hybrid join detected (both on-premises AD and Entra ID): $($joinStatus.Domain)" -ForegroundColor Green + Write-Host "Querying on-premises Active Directory..." -ForegroundColor Gray + $currentUser = $env:USERNAME + Get-OnPremisesADProvisioningConfig -Username $currentUser -ADAttribute $ADAttribute } elseif ($joinType -eq "EntraID") { - Write-Host "Connected to Entra ID (Azure AD)" -ForegroundColor Green - if ($joinStatus.Domain) { - Write-Host " Tenant: $($joinStatus.Domain)" -ForegroundColor Gray - } - Get-EntraIDProvisioningConfig - exit 0 - - + Write-Host "Connected to Entra ID (Azure AD)" -ForegroundColor Green + if ($joinStatus.Domain) { + Write-Host " Tenant: $($joinStatus.Domain)" -ForegroundColor Gray + } + Get-EntraIDProvisioningConfig } elseif ($joinType -eq "Workgroup") { - Write-Host "This computer is not connected to a domain (Workgroup). Exiting." -ForegroundColor Yellow - exit 0 - - + Write-Host "This computer is not connected to a domain (Workgroup). Exiting." -ForegroundColor Yellow } else { - Write-Host "Unable to determine domain connection status. Exiting." -ForegroundColor Yellow - exit 0 - + Write-Host "Unable to determine domain connection status. Exiting." -ForegroundColor Yellow } + +Stop-Transcript From 77040afeb084028f3f72a7c89613c6102e183203 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Thu, 23 Oct 2025 11:22:10 +0200 Subject: [PATCH 18/29] don't restore legacy file --- .../resources-windows/defguard-client.wxs | 46 ------------------- 1 file changed, 46 deletions(-) delete mode 100644 src-tauri/resources-windows/defguard-client.wxs diff --git a/src-tauri/resources-windows/defguard-client.wxs b/src-tauri/resources-windows/defguard-client.wxs deleted file mode 100644 index 168972aa..00000000 --- a/src-tauri/resources-windows/defguard-client.wxs +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - From 906ad8123f2fd86413ea054e5b4baff7c553ea5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Thu, 23 Oct 2025 11:54:03 +0200 Subject: [PATCH 19/29] simplify provisioning config file in PS script --- .../scripts/Get-ProvisioningConfig.ps1 | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/src-tauri/resources-windows/scripts/Get-ProvisioningConfig.ps1 b/src-tauri/resources-windows/scripts/Get-ProvisioningConfig.ps1 index a7fc7503..236035c3 100644 --- a/src-tauri/resources-windows/scripts/Get-ProvisioningConfig.ps1 +++ b/src-tauri/resources-windows/scripts/Get-ProvisioningConfig.ps1 @@ -70,9 +70,7 @@ function Get-DomainJoinStatus { function Save-DefguardEnrollmentData { param( [string]$EnrollmentUrl, - [string]$EnrollmentToken, - [string]$UserPrincipalName, - [string]$DisplayName + [string]$EnrollmentToken ) # Create Defguard directory in AppData\Roaming @@ -87,11 +85,8 @@ function Save-DefguardEnrollmentData { } $jsonData = @{ - enrollmentUrl = $EnrollmentUrl - enrollmentToken = $EnrollmentToken - userPrincipalName = $UserPrincipalName - displayName = $DisplayName - retrievedAt = (Get-Date).ToString("o") + enrollment_url = $EnrollmentUrl + enrollment_token = $EnrollmentToken } $jsonData | ConvertTo-Json -Depth 10 | Out-File -FilePath $jsonOutputPath -Encoding UTF8 -Force @@ -162,9 +157,7 @@ function Get-OnPremisesADProvisioningConfig { # Save enrollment data to JSON file only if both URL and token exist if ($enrollmentUrl -and $enrollmentToken) { Save-DefguardEnrollmentData -EnrollmentUrl $enrollmentUrl ` - -EnrollmentToken $enrollmentToken ` - -UserPrincipalName $adUser.UserPrincipalName ` - -DisplayName $adUser.DisplayName + -EnrollmentToken $enrollmentToken } else { Write-Host "`nWarning: Incomplete Defguard enrollment data in JSON. Both URL and token are required." -ForegroundColor Yellow } @@ -263,9 +256,7 @@ function Get-EntraIDProvisioningConfig { # Save enrollment data to JSON file only if both URL and token exist if ($enrollmentUrl -and $enrollmentToken) { Save-DefguardEnrollmentData -EnrollmentUrl $enrollmentUrl ` - -EnrollmentToken $enrollmentToken ` - -UserPrincipalName $mgUser.UserPrincipalName ` - -DisplayName $mgUser.DisplayName + -EnrollmentToken $enrollmentToken } else { Write-Host "`nWarning: Incomplete Defguard enrollment data. Both URL and token are required." -ForegroundColor Yellow } From 50d52bceee8e73502d8568de4cef213dc496d638 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Thu, 23 Oct 2025 12:20:28 +0200 Subject: [PATCH 20/29] restore script resources --- src-tauri/tauri.windows.conf.json | 1 + 1 file changed, 1 insertion(+) diff --git a/src-tauri/tauri.windows.conf.json b/src-tauri/tauri.windows.conf.json index 48f59305..0c67c0db 100644 --- a/src-tauri/tauri.windows.conf.json +++ b/src-tauri/tauri.windows.conf.json @@ -5,6 +5,7 @@ ], "resources": [ "resources-windows/binaries/*", + "resources-windows/scripts/*", "resources/icons/*" ] } From cc5eff271c0b1268e1a959b1e26e117d9be4545a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Fri, 24 Oct 2025 11:18:00 +0200 Subject: [PATCH 21/29] try to sidestep windows encoding issues --- src-tauri/src/enterprise/provisioning/mod.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src-tauri/src/enterprise/provisioning/mod.rs b/src-tauri/src/enterprise/provisioning/mod.rs index 8a8ec1d9..bc6910d3 100644 --- a/src-tauri/src/enterprise/provisioning/mod.rs +++ b/src-tauri/src/enterprise/provisioning/mod.rs @@ -1,4 +1,4 @@ -use std::{fs::OpenOptions, path::Path}; +use std::{fs, path::Path}; use serde::{Deserialize, Serialize}; use tauri::{AppHandle, Manager}; @@ -16,14 +16,16 @@ pub struct ProvisioningConfig { impl ProvisioningConfig { /// Load configuration from a file at `path`. fn load(path: &Path) -> Option { - let file = match OpenOptions::new().read(true).open(path) { - Ok(file) => file, + // read content to string first to handle Windows encoding issues + let file_content = match fs::read_to_string(path) { + Ok(content) => content, Err(err) => { warn!("Failed to open provisioning configuration file at {path:?}. Error details: {err}"); return None; } }; - match serde_json::from_reader::<_, Self>(file) { + + match serde_json::from_str::(&file_content) { Ok(config) => Some(config), Err(err) => { warn!("Failed to parse provisioning configuration file at {path:?}. Error details: {err}"); From f33b5204e65e08f4f72ae3122897ef8d20737f2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Fri, 24 Oct 2025 12:01:36 +0200 Subject: [PATCH 22/29] strip BOM manually --- src-tauri/src/enterprise/provisioning/mod.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src-tauri/src/enterprise/provisioning/mod.rs b/src-tauri/src/enterprise/provisioning/mod.rs index bc6910d3..a69e434b 100644 --- a/src-tauri/src/enterprise/provisioning/mod.rs +++ b/src-tauri/src/enterprise/provisioning/mod.rs @@ -25,7 +25,10 @@ impl ProvisioningConfig { } }; - match serde_json::from_str::(&file_content) { + // strip Windows BOM manually + let file_content = file_content.trim_start_matches('\u{FEFF}'); + + match serde_json::from_str::(file_content) { Ok(config) => Some(config), Err(err) => { warn!("Failed to parse provisioning configuration file at {path:?}. Error details: {err}"); @@ -59,8 +62,7 @@ pub async fn handle_client_initialization(app_handle: &AppHandle) -> Option { - info!("Provisioning config found in {data_dir:?}."); - debug!("Provisioning config: {config:?}"); + info!("Provisioning config found in {data_dir:?}: {config:?}"); return Some(config); } None => { From 576e04d43c96232bbf0e662eefa6583cd76b2ffb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Fri, 24 Oct 2025 12:57:36 +0200 Subject: [PATCH 23/29] test new MSI images --- src-tauri/resources-windows/msi/side_banner.png | Bin 0 -> 92125 bytes src-tauri/resources-windows/msi/top_banner.png | Bin 0 -> 14293 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 src-tauri/resources-windows/msi/side_banner.png create mode 100644 src-tauri/resources-windows/msi/top_banner.png diff --git a/src-tauri/resources-windows/msi/side_banner.png b/src-tauri/resources-windows/msi/side_banner.png new file mode 100644 index 0000000000000000000000000000000000000000..98134bbbe133ef0db0e40c11b1ca1ff3fc585080 GIT binary patch literal 92125 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 literal 0 HcmV?d00001 diff --git a/src-tauri/resources-windows/msi/top_banner.png b/src-tauri/resources-windows/msi/top_banner.png new file mode 100644 index 0000000000000000000000000000000000000000..44ae7491526c1c9f4ed8a18555437eddf24469ea GIT binary patch literal 14293 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 literal 0 HcmV?d00001 From 258bf697495c6926354a10b87e9811098fc9bc3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Fri, 24 Oct 2025 13:12:33 +0200 Subject: [PATCH 24/29] setup MSI banners --- src-tauri/tauri.conf.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 5acfe58d..6855b54f 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -28,6 +28,8 @@ "timestampUrl": "", "wix": { "upgradeCode": "923b21f5-7d3f-4f5e-8dcb-43fe1c65fb43", + "bannerPath": "./resources-windows/msi/top_banner.png", + "dialogImagePath": "./resources-windows/msi/side_banner.png", "fragmentPaths": [ "./resources-windows/fragments/service.wxs", "./resources-windows/fragments/provisioning.wxs" From 0198b439e3c43a03362bd982a60517df927e8f05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Fri, 24 Oct 2025 13:12:56 +0200 Subject: [PATCH 25/29] move DB migrations execution --- src-tauri/src/bin/defguard-client.rs | 12 ++++-------- src-tauri/src/database/mod.rs | 10 ++++++++++ 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src-tauri/src/bin/defguard-client.rs b/src-tauri/src/bin/defguard-client.rs index 609cc4dc..03c36f1e 100644 --- a/src-tauri/src/bin/defguard-client.rs +++ b/src-tauri/src/bin/defguard-client.rs @@ -15,6 +15,7 @@ use defguard_client::{ appstate::AppState, commands::*, database::{ + handle_db_migrations, models::{location_stats::LocationStats, tunnel::TunnelStats}, DB_POOL, }, @@ -39,14 +40,6 @@ const LOGGING_TARGET_IGNORE_LIST: [&str; 5] = ["tauri", "sqlx", "hyper", "h2", " static LOG_INCLUDES: LazyLock> = LazyLock::new(load_log_targets); async fn startup(app_handle: &AppHandle) { - debug!("Running database migrations, if there are any."); - sqlx::migrate!() - .run(&*DB_POOL) - .await - .expect("Failed to apply database migrations."); - debug!("Applied all database migrations that were pending. If any."); - debug!("Database setup has been completed successfully."); - debug!("Purging old stats from the database."); if let Err(err) = LocationStats::purge(&*DB_POOL).await { error!("Failed to purge location stats: {err}"); @@ -246,6 +239,9 @@ fn main() { .build(), )?; + // run DB migrations + tauri::async_runtime::block_on(handle_db_migrations()); + // Check if client needs to be initialized // and try to load provisioning config if necessary let provisioning_config = diff --git a/src-tauri/src/database/mod.rs b/src-tauri/src/database/mod.rs index d6f59736..99866016 100644 --- a/src-tauri/src/database/mod.rs +++ b/src-tauri/src/database/mod.rs @@ -93,3 +93,13 @@ fn prepare_db_url() -> Result { )) } } + +pub async fn handle_db_migrations() { + debug!("Running database migrations, if there are any."); + sqlx::migrate!() + .run(&*DB_POOL) + .await + .expect("Failed to apply database migrations."); + debug!("Applied all database migrations that were pending. If any."); + debug!("Database setup has been completed successfully."); +} From cc5b7fa6bd8b1a0739660952b037970937b732fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Fri, 24 Oct 2025 13:25:00 +0200 Subject: [PATCH 26/29] change default AD attribute --- src-tauri/resources-windows/fragments/provisioning.wxs | 2 +- .../resources-windows/scripts/Get-ProvisioningConfig.ps1 | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src-tauri/resources-windows/fragments/provisioning.wxs b/src-tauri/resources-windows/fragments/provisioning.wxs index a9712e30..06f993a4 100644 --- a/src-tauri/resources-windows/fragments/provisioning.wxs +++ b/src-tauri/resources-windows/fragments/provisioning.wxs @@ -8,7 +8,7 @@ - + param( - [string]$ADAttribute = "extensionAttribute1" + [string]$ADAttribute = "defguardProvisioningConfig" ) # Check device join status From ec973e9c181b93af0bca869c92460b4b6069ac27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Fri, 24 Oct 2025 13:26:57 +0200 Subject: [PATCH 27/29] restore release workflow --- .github/workflows/release.yaml | 710 ++++++++++++++++----------------- 1 file changed, 355 insertions(+), 355 deletions(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index ae56313b..d3db03e4 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -18,376 +18,376 @@ jobs: draft: true generate_release_notes: true - # create-sbom: - # needs: [create-release] - # uses: ./.github/workflows/sbom.yml - # with: - # upload_url: ${{ needs.create-release.outputs.upload_url }} + create-sbom: + needs: [create-release] + uses: ./.github/workflows/sbom.yml + with: + upload_url: ${{ needs.create-release.outputs.upload_url }} - # build-linux: - # needs: - # - create-release - # outputs: - # deb_sha256_amd64: ${{ steps.calculate-sha256.outputs.deb_sha256_amd64 }} - # runs-on: - # - self-hosted - # - Linux - # - ${{ matrix.architecture }} - # strategy: - # fail-fast: false - # matrix: - # architecture: [ARM64, X64] - # include: - # - architecture: ARM64 - # deb_arch: arm64 - # binary_arch: aarch64 - # - architecture: X64 - # deb_arch: amd64 - # binary_arch: x86_64 - # steps: - # - uses: actions/checkout@v5 - # with: - # submodules: "recursive" - # - name: Write release version - # run: | - # VERSION=$(echo ${GITHUB_REF_NAME#v} | cut -d '-' -f1) - # echo Version: $VERSION - # echo "VERSION=$VERSION" >> ${GITHUB_ENV} - # - uses: actions/setup-node@v5 - # with: - # node-version: "24" - # - uses: pnpm/action-setup@v4 - # with: - # version: 10.17 - # run_install: false - # - name: Get pnpm store directory - # shell: bash - # run: | - # echo "STORE_PATH=$(pnpm store path --silent)" >> ${GITHUB_ENV} - # - uses: actions/cache@v4 - # name: Setup pnpm cache - # with: - # path: ${{ env.STORE_PATH }} - # key: ${{ runner.os }}-pnpm-build-store-${{ hashFiles('**/pnpm-lock.yaml') }} - # restore-keys: | - # ${{ runner.os }}-pnpm-build-store- - # - name: Install Node dependencies - # run: pnpm install --frozen-lockfile - # - uses: dtolnay/rust-toolchain@stable - # - name: Install Linux dependencies - # run: | - # sudo apt-get update - # sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev patchelf libssl-dev libxdo-dev unzip protobuf-compiler libprotobuf-dev rpm - # - name: Build packages - # uses: tauri-apps/tauri-action@v0 - # env: - # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - # with: - # args: "--bundles deb,rpm" - # - name: Calculate DEB SHA256 - # id: calculate-sha256 - # if: matrix.deb_arch == 'amd64' - # run: | - # DEB_FILE="src-tauri/target/release/bundle/deb/defguard-client_${{ env.VERSION }}_${{ matrix.deb_arch }}.deb" - # DEB_SHA256=$(sha256sum "$DEB_FILE" | cut -d ' ' -f1) - # echo "DEB SHA256: $DEB_SHA256" - # echo "DEB_SHA256=$DEB_SHA256" >> ${GITHUB_ENV} - # echo "deb_sha256_${{ matrix.deb_arch }}=$DEB_SHA256" >> ${GITHUB_OUTPUT} - # - name: Upload RPM - # uses: actions/upload-release-asset@v1 - # env: - # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - # with: - # upload_url: ${{ needs.create-release.outputs.upload_url }} - # asset_path: src-tauri/target/release/bundle/rpm/defguard-client-${{ env.VERSION }}-1.${{ matrix.binary_arch }}.rpm - # asset_name: defguard-client-${{ env.VERSION }}-1.${{ matrix.binary_arch }}.rpm - # asset_content_type: application/octet-stream - # - name: Upload DEB - # uses: actions/upload-release-asset@v1 - # env: - # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - # with: - # upload_url: ${{ needs.create-release.outputs.upload_url }} - # asset_path: src-tauri/target/release/bundle/deb/defguard-client_${{ env.VERSION }}_${{ matrix.deb_arch }}.deb - # asset_name: defguard-client_${{ env.VERSION }}_${{ matrix.deb_arch }}.deb - # asset_content_type: application/octet-stream - # - name: Install ruby with deb-s3 - # if: matrix.build != 'freebsd' - # run: | - # sudo apt-get install -y ruby - # gem install deb-s3 - # echo "$(ruby -r rubygems -e 'puts Gem.user_dir')/bin" >> $GITHUB_PATH - # - name: Upload DEB to APT repository #Add this to ubuntu 22.04 job (on merge dev -> main) with --codename=bookworm - # run: | - # COMPONENT=$([[ "${{ github.ref_name }}" == *"-"* ]] && echo "pre-release" || echo "release") # if tag contain "-" assume it's pre-release. + build-linux: + needs: + - create-release + outputs: + deb_sha256_amd64: ${{ steps.calculate-sha256.outputs.deb_sha256_amd64 }} + runs-on: + - self-hosted + - Linux + - ${{ matrix.architecture }} + strategy: + fail-fast: false + matrix: + architecture: [ARM64, X64] + include: + - architecture: ARM64 + deb_arch: arm64 + binary_arch: aarch64 + - architecture: X64 + deb_arch: amd64 + binary_arch: x86_64 + steps: + - uses: actions/checkout@v5 + with: + submodules: "recursive" + - name: Write release version + run: | + VERSION=$(echo ${GITHUB_REF_NAME#v} | cut -d '-' -f1) + echo Version: $VERSION + echo "VERSION=$VERSION" >> ${GITHUB_ENV} + - uses: actions/setup-node@v5 + with: + node-version: "24" + - uses: pnpm/action-setup@v4 + with: + version: 10.17 + run_install: false + - name: Get pnpm store directory + shell: bash + run: | + echo "STORE_PATH=$(pnpm store path --silent)" >> ${GITHUB_ENV} + - uses: actions/cache@v4 + name: Setup pnpm cache + with: + path: ${{ env.STORE_PATH }} + key: ${{ runner.os }}-pnpm-build-store-${{ hashFiles('**/pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-pnpm-build-store- + - name: Install Node dependencies + run: pnpm install --frozen-lockfile + - uses: dtolnay/rust-toolchain@stable + - name: Install Linux dependencies + run: | + sudo apt-get update + sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev patchelf libssl-dev libxdo-dev unzip protobuf-compiler libprotobuf-dev rpm + - name: Build packages + uses: tauri-apps/tauri-action@v0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + args: "--bundles deb,rpm" + - name: Calculate DEB SHA256 + id: calculate-sha256 + if: matrix.deb_arch == 'amd64' + run: | + DEB_FILE="src-tauri/target/release/bundle/deb/defguard-client_${{ env.VERSION }}_${{ matrix.deb_arch }}.deb" + DEB_SHA256=$(sha256sum "$DEB_FILE" | cut -d ' ' -f1) + echo "DEB SHA256: $DEB_SHA256" + echo "DEB_SHA256=$DEB_SHA256" >> ${GITHUB_ENV} + echo "deb_sha256_${{ matrix.deb_arch }}=$DEB_SHA256" >> ${GITHUB_OUTPUT} + - name: Upload RPM + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ needs.create-release.outputs.upload_url }} + asset_path: src-tauri/target/release/bundle/rpm/defguard-client-${{ env.VERSION }}-1.${{ matrix.binary_arch }}.rpm + asset_name: defguard-client-${{ env.VERSION }}-1.${{ matrix.binary_arch }}.rpm + asset_content_type: application/octet-stream + - name: Upload DEB + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ needs.create-release.outputs.upload_url }} + asset_path: src-tauri/target/release/bundle/deb/defguard-client_${{ env.VERSION }}_${{ matrix.deb_arch }}.deb + asset_name: defguard-client_${{ env.VERSION }}_${{ matrix.deb_arch }}.deb + asset_content_type: application/octet-stream + - name: Install ruby with deb-s3 + if: matrix.build != 'freebsd' + run: | + sudo apt-get install -y ruby + gem install deb-s3 + echo "$(ruby -r rubygems -e 'puts Gem.user_dir')/bin" >> $GITHUB_PATH + - name: Upload DEB to APT repository #Add this to ubuntu 22.04 job (on merge dev -> main) with --codename=bookworm + run: | + COMPONENT=$([[ "${{ github.ref_name }}" == *"-"* ]] && echo "pre-release" || echo "release") # if tag contain "-" assume it's pre-release. - # deb-s3 upload -l --bucket=apt.defguard.net --access-key-id=${{ secrets.AWS_ACCESS_KEY_APT }} --secret-access-key=${{ secrets.AWS_SECRET_KEY_APT }} --s3-region=eu-north-1 --no-fail-if-exists --codename=trixie --component="$COMPONENT" src-tauri/target/release/bundle/deb/defguard-client_${{ env.VERSION }}_${{ matrix.deb_arch }}.deb - # - name: Rename client binary - # run: mv src-tauri/target/release/defguard-client defguard-client-linux-${{ matrix.binary_arch }}-${{ github.ref_name }} - # - name: Tar client binary - # uses: a7ul/tar-action@v1.2.0 - # with: - # command: c - # files: | - # defguard-client-linux-${{ matrix.binary_arch }}-${{ github.ref_name }} - # outPath: defguard-client-linux-${{ matrix.binary_arch }}-${{ github.ref_name }}.tar.gz - # - name: Upload client archive - # uses: actions/upload-release-asset@v1 - # env: - # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - # with: - # upload_url: ${{ needs.create-release.outputs.upload_url }} - # asset_path: defguard-client-linux-${{ matrix.binary_arch }}-${{ github.ref_name }}.tar.gz - # asset_name: defguard-client-linux-${{ matrix.binary_arch }}-${{ github.ref_name }}.tar.gz - # asset_content_type: application/octet-stream - # - name: Rename daemon binary - # run: mv src-tauri/target/release/defguard-service defguard-service-linux-${{ matrix.binary_arch }}-${{ github.ref_name }} - # - name: Tar daemon binary - # uses: a7ul/tar-action@v1.2.0 - # with: - # command: c - # files: | - # defguard-service-linux-${{ matrix.binary_arch }}-${{ github.ref_name }} - # outPath: defguard-service-linux-${{ matrix.binary_arch }}-${{ github.ref_name }}.tar.gz - # - name: Upload daemon archive - # uses: actions/upload-release-asset@v1 - # env: - # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - # with: - # upload_url: ${{ needs.create-release.outputs.upload_url }} - # asset_path: defguard-service-linux-${{ matrix.binary_arch }}-${{ github.ref_name }}.tar.gz - # asset_name: defguard-service-linux-${{ matrix.binary_arch }}-${{ github.ref_name }}.tar.gz - # asset_content_type: application/octet-stream + deb-s3 upload -l --bucket=apt.defguard.net --access-key-id=${{ secrets.AWS_ACCESS_KEY_APT }} --secret-access-key=${{ secrets.AWS_SECRET_KEY_APT }} --s3-region=eu-north-1 --no-fail-if-exists --codename=trixie --component="$COMPONENT" src-tauri/target/release/bundle/deb/defguard-client_${{ env.VERSION }}_${{ matrix.deb_arch }}.deb + - name: Rename client binary + run: mv src-tauri/target/release/defguard-client defguard-client-linux-${{ matrix.binary_arch }}-${{ github.ref_name }} + - name: Tar client binary + uses: a7ul/tar-action@v1.2.0 + with: + command: c + files: | + defguard-client-linux-${{ matrix.binary_arch }}-${{ github.ref_name }} + outPath: defguard-client-linux-${{ matrix.binary_arch }}-${{ github.ref_name }}.tar.gz + - name: Upload client archive + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ needs.create-release.outputs.upload_url }} + asset_path: defguard-client-linux-${{ matrix.binary_arch }}-${{ github.ref_name }}.tar.gz + asset_name: defguard-client-linux-${{ matrix.binary_arch }}-${{ github.ref_name }}.tar.gz + asset_content_type: application/octet-stream + - name: Rename daemon binary + run: mv src-tauri/target/release/defguard-service defguard-service-linux-${{ matrix.binary_arch }}-${{ github.ref_name }} + - name: Tar daemon binary + uses: a7ul/tar-action@v1.2.0 + with: + command: c + files: | + defguard-service-linux-${{ matrix.binary_arch }}-${{ github.ref_name }} + outPath: defguard-service-linux-${{ matrix.binary_arch }}-${{ github.ref_name }}.tar.gz + - name: Upload daemon archive + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ needs.create-release.outputs.upload_url }} + asset_path: defguard-service-linux-${{ matrix.binary_arch }}-${{ github.ref_name }}.tar.gz + asset_name: defguard-service-linux-${{ matrix.binary_arch }}-${{ github.ref_name }}.tar.gz + asset_content_type: application/octet-stream - # - name: Rename dg binary - # run: mv src-tauri/target/release/dg dg-linux-${{ matrix.binary_arch }}-${{ github.ref_name }} - # - name: Tar dg binary - # uses: a7ul/tar-action@v1.2.0 - # with: - # command: c - # files: | - # dg-linux-${{ matrix.binary_arch }}-${{ github.ref_name }} - # outPath: dg-linux-${{ matrix.binary_arch }}-${{ github.ref_name }}.tar.gz - # - name: Upload dg archive - # uses: actions/upload-release-asset@v1 - # env: - # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - # with: - # upload_url: ${{ needs.create-release.outputs.upload_url }} - # asset_path: dg-linux-${{ matrix.binary_arch }}-${{ github.ref_name }}.tar.gz - # asset_name: dg-linux-${{ matrix.binary_arch }}-${{ github.ref_name }}.tar.gz - # asset_content_type: application/octet-stream - # - name: Build dg deb - # uses: defGuard/fpm-action@main - # with: - # fpm_args: "dg-linux-${{ matrix.binary_arch }}-${{ github.ref_name }}=/usr/sbin/dg dg.service=/usr/lib/systemd/system/dg.service src-tauri/cli/.env=/etc/defguard/dg.conf" - # fpm_opts: "--architecture ${{ matrix.binary_arch }} --debug --output-type deb --version ${{ env.VERSION }} --package dg-linux-${{ matrix.binary_arch }}-${{ github.ref_name }}.deb" - # - name: Upload DEB - # uses: actions/upload-release-asset@v1.0.2 - # env: - # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - # with: - # upload_url: ${{ needs.create-release.outputs.upload_url }} - # asset_path: dg-linux-${{ matrix.binary_arch }}-${{ github.ref_name }}.deb - # asset_name: dg-linux-${{ matrix.binary_arch }}-${{ github.ref_name }}.deb - # asset_content_type: application/octet-stream - # - name: Build dg rpm - # uses: defGuard/fpm-action@main - # with: - # fpm_args: "dg-linux-${{ matrix.binary_arch }}-${{ github.ref_name }}=/usr/sbin/dg dg.service=/usr/lib/systemd/system/dg.service src-tauri/cli/.env=/etc/defguard/dg.conf" - # fpm_opts: "--architecture ${{ matrix.binary_arch }} --debug --output-type rpm --version ${{ env.VERSION }} --package dg-linux-${{ matrix.binary_arch }}-${{ github.ref_name }}.rpm" - # - name: Upload RPM - # uses: actions/upload-release-asset@v1.0.2 - # env: - # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - # with: - # upload_url: ${{ needs.create-release.outputs.upload_url }} - # asset_path: dg-linux-${{ matrix.binary_arch }}-${{ github.ref_name }}.rpm - # asset_name: dg-linux-${{ matrix.binary_arch }}-${{ github.ref_name }}.rpm - # asset_content_type: application/octet-stream + - name: Rename dg binary + run: mv src-tauri/target/release/dg dg-linux-${{ matrix.binary_arch }}-${{ github.ref_name }} + - name: Tar dg binary + uses: a7ul/tar-action@v1.2.0 + with: + command: c + files: | + dg-linux-${{ matrix.binary_arch }}-${{ github.ref_name }} + outPath: dg-linux-${{ matrix.binary_arch }}-${{ github.ref_name }}.tar.gz + - name: Upload dg archive + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ needs.create-release.outputs.upload_url }} + asset_path: dg-linux-${{ matrix.binary_arch }}-${{ github.ref_name }}.tar.gz + asset_name: dg-linux-${{ matrix.binary_arch }}-${{ github.ref_name }}.tar.gz + asset_content_type: application/octet-stream + - name: Build dg deb + uses: defGuard/fpm-action@main + with: + fpm_args: "dg-linux-${{ matrix.binary_arch }}-${{ github.ref_name }}=/usr/sbin/dg dg.service=/usr/lib/systemd/system/dg.service src-tauri/cli/.env=/etc/defguard/dg.conf" + fpm_opts: "--architecture ${{ matrix.binary_arch }} --debug --output-type deb --version ${{ env.VERSION }} --package dg-linux-${{ matrix.binary_arch }}-${{ github.ref_name }}.deb" + - name: Upload DEB + uses: actions/upload-release-asset@v1.0.2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ needs.create-release.outputs.upload_url }} + asset_path: dg-linux-${{ matrix.binary_arch }}-${{ github.ref_name }}.deb + asset_name: dg-linux-${{ matrix.binary_arch }}-${{ github.ref_name }}.deb + asset_content_type: application/octet-stream + - name: Build dg rpm + uses: defGuard/fpm-action@main + with: + fpm_args: "dg-linux-${{ matrix.binary_arch }}-${{ github.ref_name }}=/usr/sbin/dg dg.service=/usr/lib/systemd/system/dg.service src-tauri/cli/.env=/etc/defguard/dg.conf" + fpm_opts: "--architecture ${{ matrix.binary_arch }} --debug --output-type rpm --version ${{ env.VERSION }} --package dg-linux-${{ matrix.binary_arch }}-${{ github.ref_name }}.rpm" + - name: Upload RPM + uses: actions/upload-release-asset@v1.0.2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ needs.create-release.outputs.upload_url }} + asset_path: dg-linux-${{ matrix.binary_arch }}-${{ github.ref_name }}.rpm + asset_name: dg-linux-${{ matrix.binary_arch }}-${{ github.ref_name }}.rpm + asset_content_type: application/octet-stream - # apt-sign: - # needs: #Add needs: -ubuntu-22-04-build (on merge dev -> main) - # - build-linux - # runs-on: - # - self-hosted - # - Linux - # - X64 - # strategy: - # fail-fast: false - # steps: - # - name: Sign APT repository - # run: | - # export AWS_ACCESS_KEY_ID=${{ secrets.AWS_ACCESS_KEY_APT }} - # export AWS_SECRET_ACCESS_KEY=${{ secrets.AWS_SECRET_KEY_APT }} - # export AWS_REGION=eu-north-1 - # sudo apt update -y - # sudo apt install -y awscli curl jq + apt-sign: + needs: #Add needs: -ubuntu-22-04-build (on merge dev -> main) + - build-linux + runs-on: + - self-hosted + - Linux + - X64 + strategy: + fail-fast: false + steps: + - name: Sign APT repository + run: | + export AWS_ACCESS_KEY_ID=${{ secrets.AWS_ACCESS_KEY_APT }} + export AWS_SECRET_ACCESS_KEY=${{ secrets.AWS_SECRET_KEY_APT }} + export AWS_REGION=eu-north-1 + sudo apt update -y + sudo apt install -y awscli curl jq - # for DIST in trixie bookworm; do - # aws s3 cp s3://apt.defguard.net/dists/${DIST}/Release . + for DIST in trixie bookworm; do + aws s3 cp s3://apt.defguard.net/dists/${DIST}/Release . - # curl -X POST "${{ secrets.DEFGUARD_SIGNING_URL }}?signature_type=both" \ - # -H "Authorization: Bearer ${{ secrets.DEFGUARD_SIGNING_API_KEY }}" \ - # -F "file=@Release" \ - # -o response.json + curl -X POST "${{ secrets.DEFGUARD_SIGNING_URL }}?signature_type=both" \ + -H "Authorization: Bearer ${{ secrets.DEFGUARD_SIGNING_API_KEY }}" \ + -F "file=@Release" \ + -o response.json - # cat response.json | jq -r '.files["Release.gpg"].content' | base64 --decode > Release.gpg - # cat response.json | jq -r '.files.Release.content' | base64 --decode > InRelease + cat response.json | jq -r '.files["Release.gpg"].content' | base64 --decode > Release.gpg + cat response.json | jq -r '.files.Release.content' | base64 --decode > InRelease - # aws s3 cp Release.gpg s3://apt.defguard.net/dists/${DIST}/ --acl public-read - # aws s3 cp InRelease s3://apt.defguard.net/dists/${DIST}/ --acl public-read + aws s3 cp Release.gpg s3://apt.defguard.net/dists/${DIST}/ --acl public-read + aws s3 cp InRelease s3://apt.defguard.net/dists/${DIST}/ --acl public-read - # done - # (aws s3 ls s3://apt.defguard.net/dists/ --recursive; aws s3 ls s3://apt.defguard.net/pool/ --recursive) | awk '{print ""$4"
"}' > index.html - # aws s3 cp index.html s3://apt.defguard.net/ --acl public-read + done + (aws s3 ls s3://apt.defguard.net/dists/ --recursive; aws s3 ls s3://apt.defguard.net/pool/ --recursive) | awk '{print ""$4"
"}' > index.html + aws s3 cp index.html s3://apt.defguard.net/ --acl public-read - # update-aur: - # needs: - # - create-release - # - build-linux - # if: "!contains(github.ref_name, '-')" - # runs-on: - # - self-hosted - # - Linux - # - ${{ matrix.architecture }} - # container: archlinux:latest - # strategy: - # fail-fast: false - # matrix: - # architecture: [X64] - # include: - # - architecture: X64 - # deb_arch: amd64 - # binary_arch: x86_64 - # steps: - # - name: Install dependencies - # run: | - # pacman -Syu --noconfirm - # pacman -S --noconfirm git openssh base-devel - # - name: Create non-root user - # run: | - # useradd -m -G wheel -s /bin/bash builduser - # echo 'builduser ALL=(ALL) NOPASSWD: ALL' >> /etc/sudoers - # - name: Setup SSH - # uses: webfactory/ssh-agent@v0.9.0 - # with: - # ssh-private-key: ${{ secrets.AUR_SSH_KEY }} - # - name: Checkout AUR repository - # run: | - # mkdir -p ~/.ssh - # ssh-keyscan -H aur.archlinux.org >> ~/.ssh/known_hosts - # chmod 644 ~/.ssh/known_hosts - # export GIT_SSH_COMMAND="ssh -o StrictHostKeyChecking=accept-new" - # rm -rf aur-repo - # git clone ssh://aur@aur.archlinux.org/defguard-client.git aur-repo - # chown -R builduser:builduser aur-repo - # - name: Update PKGBUILD version - # run: | - # cd aur-repo - # VERSION=$(echo ${GITHUB_REF_NAME#v} | cut -d '-' -f1) + update-aur: + needs: + - create-release + - build-linux + if: "!contains(github.ref_name, '-')" + runs-on: + - self-hosted + - Linux + - ${{ matrix.architecture }} + container: archlinux:latest + strategy: + fail-fast: false + matrix: + architecture: [X64] + include: + - architecture: X64 + deb_arch: amd64 + binary_arch: x86_64 + steps: + - name: Install dependencies + run: | + pacman -Syu --noconfirm + pacman -S --noconfirm git openssh base-devel + - name: Create non-root user + run: | + useradd -m -G wheel -s /bin/bash builduser + echo 'builduser ALL=(ALL) NOPASSWD: ALL' >> /etc/sudoers + - name: Setup SSH + uses: webfactory/ssh-agent@v0.9.0 + with: + ssh-private-key: ${{ secrets.AUR_SSH_KEY }} + - name: Checkout AUR repository + run: | + mkdir -p ~/.ssh + ssh-keyscan -H aur.archlinux.org >> ~/.ssh/known_hosts + chmod 644 ~/.ssh/known_hosts + export GIT_SSH_COMMAND="ssh -o StrictHostKeyChecking=accept-new" + rm -rf aur-repo + git clone ssh://aur@aur.archlinux.org/defguard-client.git aur-repo + chown -R builduser:builduser aur-repo + - name: Update PKGBUILD version + run: | + cd aur-repo + VERSION=$(echo ${GITHUB_REF_NAME#v} | cut -d '-' -f1) - # echo "Updating to version: $VERSION" - # sed -i "s/^pkgver=.*/pkgver=$VERSION/" PKGBUILD + echo "Updating to version: $VERSION" + sed -i "s/^pkgver=.*/pkgver=$VERSION/" PKGBUILD - # AMD64_SHA="${{ needs.build-linux.outputs.deb_sha256_amd64 }}" + AMD64_SHA="${{ needs.build-linux.outputs.deb_sha256_amd64 }}" - # echo "AMD64 DEB SHA256: $AMD64_SHA" - # sed -i "s/^sha256sums_x86_64=.*/sha256sums_x86_64=('$AMD64_SHA')/" PKGBUILD - # - name: Update .SRCINFO - # run: | - # cd aur-repo - # sudo -u builduser makepkg --printsrcinfo > .SRCINFO - # - name: Commit and push changes - # run: | - # cd aur-repo - # chown -R builduser:builduser . - # sudo -u builduser git config user.name "Defguard Build System" - # sudo -u builduser git config user.email "community@defguard.net" - # sudo -u builduser git add PKGBUILD .SRCINFO - # sudo -u builduser git commit -m "Updated to $VERSION" - # sudo -u builduser git push - # cat PKGBUILD - # cat .SRCINFO + echo "AMD64 DEB SHA256: $AMD64_SHA" + sed -i "s/^sha256sums_x86_64=.*/sha256sums_x86_64=('$AMD64_SHA')/" PKGBUILD + - name: Update .SRCINFO + run: | + cd aur-repo + sudo -u builduser makepkg --printsrcinfo > .SRCINFO + - name: Commit and push changes + run: | + cd aur-repo + chown -R builduser:builduser . + sudo -u builduser git config user.name "Defguard Build System" + sudo -u builduser git config user.email "community@defguard.net" + sudo -u builduser git add PKGBUILD .SRCINFO + sudo -u builduser git commit -m "Updated to $VERSION" + sudo -u builduser git push + cat PKGBUILD + cat .SRCINFO - # build-macos: - # needs: - # - create-release - # strategy: - # fail-fast: false - # matrix: - # target: [aarch64-apple-darwin, x86_64-apple-darwin] - # runs-on: - # - self-hosted - # - macOS - # env: - # APPLE_SIGNING_IDENTITY_APPLICATION: "Developer ID Application: defguard sp. z o.o. (82GZ7KN29J)" - # APPLE_SIGNING_IDENTITY_INSTALLER: "Developer ID Installer: defguard sp. z o.o. (82GZ7KN29J)" - # APPLE_ID: "kamil@defguard.net" - # APPLE_TEAM_ID: "82GZ7KN29J" - # steps: - # - uses: actions/checkout@v5 - # with: - # submodules: "recursive" - # - name: Write release version - # run: | - # VERSION=$(echo ${GITHUB_REF_NAME#v} | cut -d '-' -f1) - # echo Version: $VERSION - # echo "VERSION=$VERSION" >> ${GITHUB_ENV} - # - uses: actions/setup-node@v4 - # with: - # node-version: "22" - # - uses: pnpm/action-setup@v4 - # with: - # version: 10 - # run_install: false - # - name: Get pnpm store directory - # shell: bash - # run: echo "STORE_PATH=$(pnpm store path --silent)" >> ${GITHUB_ENV} - # - uses: actions/cache@v4 - # name: Setup pnpm cache - # with: - # path: ${{ env.STORE_PATH }} - # key: ${{ runner.os }}-pnpm-build-store-${{ hashFiles('**/pnpm-lock.yaml') }} - # restore-keys: | - # ${{ runner.os }}-pnpm-build-store- - # - name: Install deps - # run: pnpm install --frozen-lockfile - # - uses: dtolnay/rust-toolchain@stable - # - name: Install protobuf compiler - # run: brew install protobuf - # - name: Install ARM target - # run: rustup target add aarch64-apple-darwin - # - name: Unlock keychain - # run: security -v unlock-keychain -p "${{ secrets.KEYCHAIN_PASSWORD }}" /Users/admin/Library/Keychains/login.keychain - # - name: Build app - # uses: tauri-apps/tauri-action@v0 - # env: - # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - # APPLE_SIGNING_IDENTITY: ${{ env.APPLE_SIGNING_IDENTITY_APPLICATION }} - # APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} - # APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} - # APPLE_ID: ${{ env.APPLE_ID }} - # APPLE_PASSWORD: ${{ secrets.NOTARYTOOL_APP_SPECIFIC_PASSWORD }} - # APPLE_TEAM_ID: ${{ env.APPLE_TEAM_ID }} - # with: - # args: --target ${{ matrix.target }} -v - # - name: Build installation package - # run: | - # bash build-macos-package.sh src-tauri/target/${{ matrix.target }} src-tauri/resources-macos/scripts '${{ env.APPLE_SIGNING_IDENTITY_INSTALLER }}' /Users/admin/Library/Keychains/login.keychain - # xcrun notarytool submit --wait --apple-id ${{ env.APPLE_ID }} --password ${{ secrets.NOTARYTOOL_APP_SPECIFIC_PASSWORD }} --team-id ${{ env.APPLE_TEAM_ID }} src-tauri/target/${{ matrix.target }}/product-signed/defguard.pkg - # xcrun stapler staple src-tauri/target/${{ matrix.target }}/product-signed/defguard.pkg - # - name: Upload installation package - # uses: actions/upload-release-asset@v1 - # env: - # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - # with: - # upload_url: ${{ needs.create-release.outputs.upload_url }} - # asset_path: src-tauri/target/${{ matrix.target }}/product-signed/defguard.pkg - # asset_name: defguard-${{ matrix.target }}-${{ env.VERSION }}.pkg - # asset_content_type: application/octet-stream + build-macos: + needs: + - create-release + strategy: + fail-fast: false + matrix: + target: [aarch64-apple-darwin, x86_64-apple-darwin] + runs-on: + - self-hosted + - macOS + env: + APPLE_SIGNING_IDENTITY_APPLICATION: "Developer ID Application: defguard sp. z o.o. (82GZ7KN29J)" + APPLE_SIGNING_IDENTITY_INSTALLER: "Developer ID Installer: defguard sp. z o.o. (82GZ7KN29J)" + APPLE_ID: "kamil@defguard.net" + APPLE_TEAM_ID: "82GZ7KN29J" + steps: + - uses: actions/checkout@v5 + with: + submodules: "recursive" + - name: Write release version + run: | + VERSION=$(echo ${GITHUB_REF_NAME#v} | cut -d '-' -f1) + echo Version: $VERSION + echo "VERSION=$VERSION" >> ${GITHUB_ENV} + - uses: actions/setup-node@v4 + with: + node-version: "22" + - uses: pnpm/action-setup@v4 + with: + version: 10 + run_install: false + - name: Get pnpm store directory + shell: bash + run: echo "STORE_PATH=$(pnpm store path --silent)" >> ${GITHUB_ENV} + - uses: actions/cache@v4 + name: Setup pnpm cache + with: + path: ${{ env.STORE_PATH }} + key: ${{ runner.os }}-pnpm-build-store-${{ hashFiles('**/pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-pnpm-build-store- + - name: Install deps + run: pnpm install --frozen-lockfile + - uses: dtolnay/rust-toolchain@stable + - name: Install protobuf compiler + run: brew install protobuf + - name: Install ARM target + run: rustup target add aarch64-apple-darwin + - name: Unlock keychain + run: security -v unlock-keychain -p "${{ secrets.KEYCHAIN_PASSWORD }}" /Users/admin/Library/Keychains/login.keychain + - name: Build app + uses: tauri-apps/tauri-action@v0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + APPLE_SIGNING_IDENTITY: ${{ env.APPLE_SIGNING_IDENTITY_APPLICATION }} + APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} + APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} + APPLE_ID: ${{ env.APPLE_ID }} + APPLE_PASSWORD: ${{ secrets.NOTARYTOOL_APP_SPECIFIC_PASSWORD }} + APPLE_TEAM_ID: ${{ env.APPLE_TEAM_ID }} + with: + args: --target ${{ matrix.target }} -v + - name: Build installation package + run: | + bash build-macos-package.sh src-tauri/target/${{ matrix.target }} src-tauri/resources-macos/scripts '${{ env.APPLE_SIGNING_IDENTITY_INSTALLER }}' /Users/admin/Library/Keychains/login.keychain + xcrun notarytool submit --wait --apple-id ${{ env.APPLE_ID }} --password ${{ secrets.NOTARYTOOL_APP_SPECIFIC_PASSWORD }} --team-id ${{ env.APPLE_TEAM_ID }} src-tauri/target/${{ matrix.target }}/product-signed/defguard.pkg + xcrun stapler staple src-tauri/target/${{ matrix.target }}/product-signed/defguard.pkg + - name: Upload installation package + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ needs.create-release.outputs.upload_url }} + asset_path: src-tauri/target/${{ matrix.target }}/product-signed/defguard.pkg + asset_name: defguard-${{ matrix.target }}-${{ env.VERSION }}.pkg + asset_content_type: application/octet-stream - # # Builds Windows MSI and uploads it as artifact + # Builds Windows MSI and uploads it as artifact build-windows: needs: - create-release From 29ee33a47c73faf3fc1664d0deabc19116306d61 Mon Sep 17 00:00:00 2001 From: Maciek <19913370+wojcik91@users.noreply.github.com> Date: Fri, 24 Oct 2025 13:36:56 +0200 Subject: [PATCH 28/29] Update src-tauri/resources-windows/scripts/Get-ProvisioningConfig.ps1 Co-authored-by: Adam --- src-tauri/resources-windows/scripts/Get-ProvisioningConfig.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src-tauri/resources-windows/scripts/Get-ProvisioningConfig.ps1 b/src-tauri/resources-windows/scripts/Get-ProvisioningConfig.ps1 index c1df95aa..9a6d3bf9 100644 --- a/src-tauri/resources-windows/scripts/Get-ProvisioningConfig.ps1 +++ b/src-tauri/resources-windows/scripts/Get-ProvisioningConfig.ps1 @@ -2,7 +2,7 @@ <# .SYNOPSIS - Retrieves Defguard client provisioning configuration for the currently logged-in user from AD or Entra ID. + Retrieves Defguard client provisioning configuration for the currently logged-in user from Active Directory or Entra ID. .DESCRIPTION This script detects whether the computer is joined to on-premises Active Directory From a603b68cfacca4fa9e69ea14f5a629f2253e503f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Fri, 24 Oct 2025 14:11:35 +0200 Subject: [PATCH 29/29] restore EOF newlines --- src-tauri/tauri.conf.json | 2 +- src-tauri/tauri.windows.conf.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 6855b54f..37c3ddfa 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -112,4 +112,4 @@ } } } -} \ No newline at end of file +} diff --git a/src-tauri/tauri.windows.conf.json b/src-tauri/tauri.windows.conf.json index 0c67c0db..826c27e7 100644 --- a/src-tauri/tauri.windows.conf.json +++ b/src-tauri/tauri.windows.conf.json @@ -9,4 +9,4 @@ "resources/icons/*" ] } -} \ No newline at end of file +}