From 3673e96cd032c5100a48b4b7739ada4c0f05a3e9 Mon Sep 17 00:00:00 2001 From: Jared Goodwin Date: Fri, 31 May 2024 10:35:51 -0700 Subject: [PATCH 01/13] Remove occurrences of "remotely.one" --- Agent.Installer.Win/ViewModels/MainWindowViewModel.cs | 2 +- Agent/Agent.csproj | 1 - Agent/Program.cs | 3 --- Desktop.Linux/Desktop.Linux.csproj | 1 - Desktop.Win/Desktop.Win.csproj | 1 - Server/Components/Scripts/SavedScripts.razor | 2 +- Utilities/Example_Nginx_Config.txt | 2 +- 7 files changed, 3 insertions(+), 9 deletions(-) diff --git a/Agent.Installer.Win/ViewModels/MainWindowViewModel.cs b/Agent.Installer.Win/ViewModels/MainWindowViewModel.cs index 2f887031b..86b4c1b89 100644 --- a/Agent.Installer.Win/ViewModels/MainWindowViewModel.cs +++ b/Agent.Installer.Win/ViewModels/MainWindowViewModel.cs @@ -317,7 +317,7 @@ private bool CheckParams() (serverUri.Scheme != Uri.UriSchemeHttp && serverUri.Scheme != Uri.UriSchemeHttps)) { Logger.Write("ServerUrl is not valid."); - MessageBoxEx.Show("Server URL must be a valid Uri (e.g. https://app.remotely.one).", "Invalid Server URL", MessageBoxButton.OK, MessageBoxImage.Error); + MessageBoxEx.Show("Server URL must be a valid Uri (e.g. https://app.example.com).", "Invalid Server URL", MessageBoxButton.OK, MessageBoxImage.Error); return false; } diff --git a/Agent/Agent.csproj b/Agent/Agent.csproj index 4d5b68f83..fb3e32fbe 100644 --- a/Agent/Agent.csproj +++ b/Agent/Agent.csproj @@ -11,7 +11,6 @@ Remotely Agent Immense Networks 1.0.0.0 - https://remotely.one AnyCPU;x86;x64 Remotely_Agent Remotely.Agent diff --git a/Agent/Program.cs b/Agent/Program.cs index 82b8800b2..370400920 100644 --- a/Agent/Program.cs +++ b/Agent/Program.cs @@ -2,13 +2,10 @@ using Microsoft.Extensions.Logging; using Remotely.Agent.Interfaces; using Remotely.Agent.Services; -using Remotely.Shared.Enums; using Remotely.Shared.Utilities; using Remotely.Shared.Services; using System; -using System.Diagnostics; using System.IO; -using System.ServiceProcess; using System.Threading.Tasks; using System.Runtime.Versioning; using Remotely.Agent.Services.Linux; diff --git a/Desktop.Linux/Desktop.Linux.csproj b/Desktop.Linux/Desktop.Linux.csproj index 7faab16c2..15014a8a5 100644 --- a/Desktop.Linux/Desktop.Linux.csproj +++ b/Desktop.Linux/Desktop.Linux.csproj @@ -30,7 +30,6 @@ Remotely Desktop Desktop client for allowing your IT admin to provide remote support. Copyright © 2023 Immense Networks - https://remotely.one enable diff --git a/Desktop.Win/Desktop.Win.csproj b/Desktop.Win/Desktop.Win.csproj index e5532159a..bf4a04b5f 100644 --- a/Desktop.Win/Desktop.Win.csproj +++ b/Desktop.Win/Desktop.Win.csproj @@ -12,7 +12,6 @@ Jared Goodwin Immense Networks Remotely Desktop - https://remotely.one AnyCPU;x86;x64 True diff --git a/Server/Components/Scripts/SavedScripts.razor b/Server/Components/Scripts/SavedScripts.razor index 39bfbc126..a768c94a5 100644 --- a/Server/Components/Scripts/SavedScripts.razor +++ b/Server/Components/Scripts/SavedScripts.razor @@ -122,7 +122,7 @@
Server URL
-
The URL of the server that the device connects to (e.g. https://app.remotely.one).
+
The URL of the server that the device connects to (e.g. https://app.example.com).
diff --git a/Utilities/Example_Nginx_Config.txt b/Utilities/Example_Nginx_Config.txt index 2db1bb9c3..0b82a5c86 100644 --- a/Utilities/Example_Nginx_Config.txt +++ b/Utilities/Example_Nginx_Config.txt @@ -1,6 +1,6 @@ server { listen 80; - server_name app.remotely.one *.app.remotely.one; + server_name app.example.com *.app.example.com; location / { proxy_pass http://localhost:5000; proxy_http_version 1.1; From e06f037c5100638232bab03628701fe0c41c4ad2 Mon Sep 17 00:00:00 2001 From: Jared Goodwin Date: Fri, 31 May 2024 10:46:16 -0700 Subject: [PATCH 02/13] Update NuGet packages. --- Agent/Agent.csproj | 14 +++++----- Remotely.slnLaunch | 37 ++++++++++++++++++++++++++ Server/Server.csproj | 24 ++++++++--------- Shared/Shared.csproj | 4 +-- Tests/LoadTester/LoadTester.csproj | 2 +- Tests/Server.Tests/Server.Tests.csproj | 10 +++---- Tests/Shared.Tests/Shared.Tests.csproj | 8 +++--- 7 files changed, 68 insertions(+), 31 deletions(-) create mode 100644 Remotely.slnLaunch diff --git a/Agent/Agent.csproj b/Agent/Agent.csproj index fb3e32fbe..a754785f5 100644 --- a/Agent/Agent.csproj +++ b/Agent/Agent.csproj @@ -23,18 +23,18 @@
- - + + - - - - - + + + + + diff --git a/Remotely.slnLaunch b/Remotely.slnLaunch new file mode 100644 index 000000000..8732c505f --- /dev/null +++ b/Remotely.slnLaunch @@ -0,0 +1,37 @@ +[ + { + "Name": "Agent \u002B Server \u002B Desktop", + "Projects": [ + { + "Path": "Agent\\Agent.csproj", + "Action": "Start", + "DebugTarget": "Agent" + }, + { + "Path": "Desktop.Win\\Desktop.Win.csproj", + "Action": "Start", + "DebugTarget": "Desktop.Win" + }, + { + "Path": "Server\\Server.csproj", + "Action": "Start", + "DebugTarget": "Server" + } + ] + }, + { + "Name": "Agent \u002B Server", + "Projects": [ + { + "Path": "Agent\\Agent.csproj", + "Action": "Start", + "DebugTarget": "Agent" + }, + { + "Path": "Server\\Server.csproj", + "Action": "Start", + "DebugTarget": "Server" + } + ] + } +] \ No newline at end of file diff --git a/Server/Server.csproj b/Server/Server.csproj index f5d1e7111..e77691750 100644 --- a/Server/Server.csproj +++ b/Server/Server.csproj @@ -14,25 +14,25 @@ - - - - - - - + + + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - + + + + - + diff --git a/Shared/Shared.csproj b/Shared/Shared.csproj index 66ace65bc..1774f3c8f 100644 --- a/Shared/Shared.csproj +++ b/Shared/Shared.csproj @@ -10,10 +10,10 @@ - + - + diff --git a/Tests/LoadTester/LoadTester.csproj b/Tests/LoadTester/LoadTester.csproj index fed703384..a8401d86e 100644 --- a/Tests/LoadTester/LoadTester.csproj +++ b/Tests/LoadTester/LoadTester.csproj @@ -8,7 +8,7 @@ - + diff --git a/Tests/Server.Tests/Server.Tests.csproj b/Tests/Server.Tests/Server.Tests.csproj index 13a5c7116..e1031ed25 100644 --- a/Tests/Server.Tests/Server.Tests.csproj +++ b/Tests/Server.Tests/Server.Tests.csproj @@ -13,12 +13,12 @@ - - - + + + - - + + diff --git a/Tests/Shared.Tests/Shared.Tests.csproj b/Tests/Shared.Tests/Shared.Tests.csproj index 03dc5490a..0bcf9dbca 100644 --- a/Tests/Shared.Tests/Shared.Tests.csproj +++ b/Tests/Shared.Tests/Shared.Tests.csproj @@ -13,10 +13,10 @@ - - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive From b440aba1feb7a73a5a891c6b74f9000fe011c00c Mon Sep 17 00:00:00 2001 From: Jared Goodwin Date: Fri, 31 May 2024 11:10:34 -0700 Subject: [PATCH 03/13] Use default healthcheck times. --- Server/Dockerfile | 2 +- Server/Dockerfile.pipelines | 2 +- submodules/Immense.RemoteControl | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Server/Dockerfile b/Server/Dockerfile index cdb3fc2a1..e42ad611a 100644 --- a/Server/Dockerfile +++ b/Server/Dockerfile @@ -9,5 +9,5 @@ WORKDIR /app ENTRYPOINT ["dotnet", "Remotely_Server.dll"] -HEALTHCHECK --interval=5m --timeout=3s \ +HEALTHCHECK \ CMD curl -f http://localhost:${ASPNETCORE_HTTP_PORTS}/api/healthcheck || exit 1 \ No newline at end of file diff --git a/Server/Dockerfile.pipelines b/Server/Dockerfile.pipelines index 8590ecbd8..3db38ebb2 100644 --- a/Server/Dockerfile.pipelines +++ b/Server/Dockerfile.pipelines @@ -9,5 +9,5 @@ WORKDIR /app ENTRYPOINT ["dotnet", "Remotely_Server.dll"] -HEALTHCHECK --interval=5m --timeout=3s \ +HEALTHCHECK \ CMD curl -f http://localhost:${ASPNETCORE_HTTP_PORTS}/api/healthcheck || exit 1 \ No newline at end of file diff --git a/submodules/Immense.RemoteControl b/submodules/Immense.RemoteControl index da865e7ca..6c77d32dd 160000 --- a/submodules/Immense.RemoteControl +++ b/submodules/Immense.RemoteControl @@ -1 +1 @@ -Subproject commit da865e7ca2de8a9c7eb27e81b60c37e08affdc21 +Subproject commit 6c77d32dd6d307ad60d9acd6bbe2c98f2b9b50a9 From 9ce6e01be17e5a95a2351c9c906d344340fc04ca Mon Sep 17 00:00:00 2001 From: Jared Goodwin Date: Fri, 31 May 2024 13:17:03 -0700 Subject: [PATCH 04/13] Fix two factor enforcement. --- Server/Auth/TwoFactorRequiredHandler.cs | 42 +++++++++++++---------- Server/Components/AuthorizedIndex.razor | 11 ------ Server/Components/Layout/MainLayout.razor | 12 +++++-- 3 files changed, 34 insertions(+), 31 deletions(-) diff --git a/Server/Auth/TwoFactorRequiredHandler.cs b/Server/Auth/TwoFactorRequiredHandler.cs index 0d62fb79a..47fb2dfef 100644 --- a/Server/Auth/TwoFactorRequiredHandler.cs +++ b/Server/Auth/TwoFactorRequiredHandler.cs @@ -1,31 +1,21 @@ using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Identity; +using Remotely.Server.Models; using Remotely.Server.Services; -using Remotely.Shared.Entities; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; +using System.Security.Principal; namespace Remotely.Server.Auth; -public class TwoFactorRequiredHandler : AuthorizationHandler +public class TwoFactorRequiredHandler( + IHttpContextAccessor _contextAccessor, + IDataService _dataService) : AuthorizationHandler { - private readonly IDataService _dataService; - - public TwoFactorRequiredHandler(IDataService dataService) - { - _dataService = dataService; - } - protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, TwoFactorRequiredRequirement requirement) { var settings = await _dataService.GetSettings(); - if (context.User.Identity?.IsAuthenticated == true && - context.User.Identity.Name is not null && - settings.Require2FA) + if (context.User?.Identity is { } identity && + IsTwoFactorRequired(identity, settings)) { - var userResult = await _dataService.GetUserByName(context.User.Identity.Name); + var userResult = await _dataService.GetUserByName(identity.Name!); if (!userResult.IsSuccess || !userResult.Value.TwoFactorEnabled) @@ -36,4 +26,20 @@ context.User.Identity.Name is not null && } context.Succeed(requirement); } + + private bool IsTwoFactorRequired(IIdentity identity, SettingsModel settings) + { + // Account management pages are exempt since they're required + // to set up 2FA. + var path = _contextAccessor.HttpContext?.Request.Path ?? ""; + if (path.StartsWithSegments("/Account/Manage")) + { + return false; + } + + return + settings.Require2FA && + identity.IsAuthenticated && + identity.Name is not null; + } } diff --git a/Server/Components/AuthorizedIndex.razor b/Server/Components/AuthorizedIndex.razor index 2c8a1f363..20eb4ed9f 100644 --- a/Server/Components/AuthorizedIndex.razor +++ b/Server/Components/AuthorizedIndex.razor @@ -19,17 +19,6 @@ @code { private SettingsModel? _settings; - protected override async Task OnAfterRenderAsync(bool firstRender) - { - if (User is not null && - _settings?.Require2FA == true && - !User.TwoFactorEnabled) - { - NavManager.NavigateTo("/TwoFactorRequired"); - } - await base.OnAfterRenderAsync(firstRender); - } - protected override async Task OnInitializedAsync() { _settings = await DataService.GetSettings(); diff --git a/Server/Components/Layout/MainLayout.razor b/Server/Components/Layout/MainLayout.razor index c8a88b432..c4acb02f1 100644 --- a/Server/Components/Layout/MainLayout.razor +++ b/Server/Components/Layout/MainLayout.razor @@ -1,6 +1,7 @@ @using Remotely.Server.Components @using Remotely.Server.Auth @inherits LayoutComponentBase +@inject NavigationManager NavMan @@ -25,7 +26,7 @@

Two-factor authentication is required. Click the button below to set up your authenticator app.

- Enable 2FA +

@@ -42,4 +43,11 @@ - \ No newline at end of file + + +@code { + private void NavigateToTwoFactor() + { + NavMan.NavigateTo("/Account/Manage/TwoFactorAuthentication", true); + } +} \ No newline at end of file From d9d68d7677f950a405cd66af706f6c084606a14d Mon Sep 17 00:00:00 2001 From: Jared Goodwin Date: Fri, 31 May 2024 13:23:27 -0700 Subject: [PATCH 05/13] Make the save button fixed on the server config page. --- Server/Components/Pages/ServerConfig.razor | 4 +--- Server/Components/Pages/ServerConfig.razor.css | 6 ++++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Server/Components/Pages/ServerConfig.razor b/Server/Components/Pages/ServerConfig.razor index 6c322e460..fefe8dc20 100644 --- a/Server/Components/Pages/ServerConfig.razor +++ b/Server/Components/Pages/ServerConfig.razor @@ -345,9 +345,7 @@ -
- -
+ \ No newline at end of file diff --git a/Server/Components/Pages/ServerConfig.razor.css b/Server/Components/Pages/ServerConfig.razor.css index 6a2d3fcb7..0bdd2c908 100644 --- a/Server/Components/Pages/ServerConfig.razor.css +++ b/Server/Components/Pages/ServerConfig.razor.css @@ -6,4 +6,10 @@ overflow-x: hidden; white-space: nowrap; border-radius: 5px; +} + +#saveButton { + position: fixed; + right: 40px; + bottom: 20px; } \ No newline at end of file From d06c8d73330988038ab42fecdac9be3e50ea3c17 Mon Sep 17 00:00:00 2001 From: Jared Goodwin Date: Fri, 31 May 2024 15:38:51 -0700 Subject: [PATCH 06/13] Add deploy page. --- .azure-pipelines/Release Build.yml | 72 +++++++++- Server/Components/Layout/NavMenu.razor | 30 ++-- Server/Components/Pages/Deploy.razor | 126 +++++++++++++++++ Server/Components/Pages/Downloads.razor | 173 +++++------------------- Server/Services/JsInterop.cs | 27 +++- Server/wwwroot/interop.js | 16 +++ Utilities/Publish.ps1 | 4 +- 7 files changed, 288 insertions(+), 160 deletions(-) create mode 100644 Server/Components/Pages/Deploy.razor diff --git a/.azure-pipelines/Release Build.yml b/.azure-pipelines/Release Build.yml index e6eb2ff44..b2424efa8 100644 --- a/.azure-pipelines/Release Build.yml +++ b/.azure-pipelines/Release Build.yml @@ -5,9 +5,71 @@ pr: - master jobs: -- job: Build - displayName: Build +#- job: Mac_Build +# displayName: Mac Build +# timeoutInMinutes: 360 +# pool: +# vmImage: macos-latest +# steps: +# +# - task: InstallSSHKey@0 +# inputs: +# knownHostsEntry: | +# github.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl +# github.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ== +# github.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEmKSENjQEezOmxkZMy7opKgwFB9nkt5YRrYMjNuG5N87uRgg6CLrbo5wAdT/y6v0mKV0U2w0WZ2YB/++Tpockg= +# sshKeySecureFile: 'pipelines_rsa' +# +# - checkout: self +# submodules: recursive +# clean: true +# fetchTags: false +# +# - task: PowerShell@2 +# displayName: Add CurrentVersion Variable +# inputs: +# targetType: inline +# script: | +# $VersionString = git show -s --format=%ci $(Build.SourceVersion) +# $VersionDate = [DateTimeOffset]::Parse($VersionString) +# $CurrentVersion = $VersionDate.ToString("yyyy.MM.dd.HHmm") +# +# [System.Console]::WriteLine("##vso[task.setvariable variable=CurrentVersion]$CurrentVersion") +# +# Write-Host "Setting current version to $CurrentVersion." +# +# - task: UseDotNet@2 +# displayName: Use .NET SDK +# inputs: +# version: 8.x +# +# - task: DotNetCoreCLI@2 +# displayName: dotnet publish x64 +# inputs: +# command: publish +# publishWebProjects: false +# projects: '**/Agent.csproj' +# arguments: -c $(BuildConfiguration) -r osx-x64 -o "$(Build.SourcesDirectory)/Agent/bin/publish" /p:Version=$(CurrentVersion) /p:FileVersion=$(CurrentVersion) +# zipAfterPublish: false +# modifyOutputPath: false +# +# - task: PowerShell@2 +# displayName: PowerShell Script +# inputs: +# targetType: inline +# script: | +# Compress-Archive -Path "$(Build.SourcesDirectory)/Agent/bin/publish/*" -DestinationPath "$(Build.SourcesDirectory)/Agent/bin/Remotely-MacOS-x64.zip" -Force +# +# - task: PublishPipelineArtifact@1 +# displayName: Publish macOS x64 Agent +# inputs: +# path: $(Build.SourcesDirectory)/Agent/bin/Remotely-MacOS-x64.zip +# artifactName: Mac-x64-Agent + +- job: Windows_Build + displayName: Windows Build timeoutInMinutes: 360 + #dependsOn: Mac_Build pool: vmImage: windows-latest @@ -28,6 +90,12 @@ jobs: - task: VisualStudioTestPlatformInstaller@1 displayName: Visual Studio Test Platform Installer +# - task: DownloadPipelineArtifact@2 +# displayName: Download macOS x64 Agent +# inputs: +# artifact: Mac-x64-Agent +# path: $(Build.SourcesDirectory)\Server\wwwroot\Content\ + - task: PowerShell@2 displayName: Add CurrentVersion Variable inputs: diff --git a/Server/Components/Layout/NavMenu.razor b/Server/Components/Layout/NavMenu.razor index 3c016ea0e..b32840781 100644 --- a/Server/Components/Layout/NavMenu.razor +++ b/Server/Components/Layout/NavMenu.razor @@ -25,26 +25,20 @@
-
-

Binaries Only

+

Resident Agents

+

- Archives containing the agent binaries only. + Installable background agents that provide unattended access and remote scripting.

-
- Windows 10 / 11 (64-Bit and 32-Bit) -

- Windows x64 Files Only -
- Windows x86 Files Only -

-
+ @if (!_isAuthenticated) + { +
+
Must be logged in to download.
+
- + } + else + { +
+ Windows 10 / 11 (64-Bit and 32-Bit) +

+ Windows PowerShell Installer +
+ Windows x64 Files Only +
+ Windows x86 Files Only +

- @* There is currently no bandwidth for Mac development. The community will need to pick it up. *@ - @*
- macOS x64 (10.12 - 10.15) -

- macOS x64 Files Only -

-
+

+

Example Quiet Install:
-
- macOS arm64 -

- macOS arm64 Files Only -

-
*@ + + powershell.exe -ExecutionPolicy Bypass -F {path}\Install-Remotely.ps1 + -install + -quiet + -organizationid "0b3d706b-9c5d-41e6-8ae9-5720d16324e6" + -serverurl "https://remotely.mytechshop.com" + +

+

+

Example Quiet Uninstall:
+ + powershell.exe -ExecutionPolicy Bypass -F Install-Remotely.ps1 -uninstall -quiet +

+

+

Example Local Install:
+ + + powershell.exe -ExecutionPolicy Bypass -F {path}\Install-Remotely.ps1 + -install + -quiet + -organizationid "0b3d706b-9c5d-41e6-8ae9-5720d16324e6" + -serverurl "https://remotely.mytechshop.com" + -path "[path]\Remotely-Win-x64.zip" + +

+

+

All Override Options:
+ + + powershell.exe -ExecutionPolicy Bypass -F {path}\Install-Remotely.ps1 + -install -quiet -supportshortcut + -organizationid "0b3d706b-9c5d-41e6-8ae9-5720d16324e6" + -serverurl "https://remotely.mytechshop.com" + -devicegroup "Customer Group 1" + -devicealias "Front Desk" + -deviceuuid "eebb4d87-5459-4976-989e-a7876dffc69c" + +

+
+ +
+ Linux 64-Bit +

+ Ubuntu x64 Bash Installer +
+ Manjaro x64 Bash Installer +
+ Linux x64 Files Only +

+

+

Example Install:
+ + sudo [path]/Install-Ubuntu-x64.sh +

+

+

Example Local Install:
+ + sudo [path]/Install-Ubuntu-x64.sh --path [path]/Remotely-Linux.zip +

+

+

Uninstall:
+ + sudo [path]/Install-Ubuntu-x64.sh --uninstall +

+
+ +
+ macOS x64 (10.12 - 10.15) +

+ macOS x64 Bash Installer +
+ macOS x64 Files Only +

+ +

+

Example Install:
+ + sudo [path]/Install-MacOS-x64.sh +

+

+

Example Local Install:
+ + sudo [path]/Install-MacOS-x64.sh --path [path]/Remotely-MacOS-x64.zip +

+

+

Example Uninstall:
+ + sudo [path]/Install-MacOS-x64.sh --uninstall +

+
+ +
+ macOS arm64 +

+ macOS arm64 Bash Installer +
+ macOS arm64 Files Only +

+ +

+

Example Install:
+ + sudo [path]/Install-MacOS-arm64.sh +

+

+

Example Local Install:
+ + sudo [path]/Install-MacOS-arm64.sh --path [path]/Remotely-MacOS-arm64.zip +

+

+

Example Uninstall:
+ + sudo [path]/Install-MacOS-arm64.sh --uninstall +

+
+ }
diff --git a/Shared/Services/EmbeddedServerDataSearcher.cs b/Shared/Services/EmbeddedServerDataSearcher.cs index 72b948b7b..297e3493a 100644 --- a/Shared/Services/EmbeddedServerDataSearcher.cs +++ b/Shared/Services/EmbeddedServerDataSearcher.cs @@ -19,64 +19,45 @@ namespace Remotely.Shared.Services; public interface IEmbeddedServerDataSearcher { - Task> GetAppendedStream(string filePath, EmbeddedServerData serverData); - Task> TryGetEmbeddedData(string filePath); + string GetEncodedFileName(string filePath, EmbeddedServerData serverData); + Result TryGetEmbeddedData(string filePath); } public class EmbeddedServerDataSearcher() : IEmbeddedServerDataSearcher { public static EmbeddedServerDataSearcher Instance { get; } = new(); - public async Task> TryGetEmbeddedData(string filePath) + public string GetEncodedFileName(string filePath, EmbeddedServerData serverData) { - try - { - if (!File.Exists(filePath)) - { - return Result.Fail($"File path does not exist: {filePath}"); - } - - using var fs = File.Open(filePath, FileMode.Open, FileAccess.Read, FileShare.Read); - using var br = new BinaryReader(fs); - using var sr = new StreamReader(fs); - - fs.Seek(-4, SeekOrigin.End); - var dataSize = br.ReadInt32(); - fs.Seek(-dataSize - 4, SeekOrigin.End); + var fileName = Path.GetFileNameWithoutExtension(filePath); + var ext = Path.GetExtension(filePath); - var buffer = new byte[dataSize]; - await fs.ReadExactlyAsync(buffer); - var json = Encoding.UTF8.GetString(buffer); + // Make the base64 string safe file paths and URIs. + var encodedData = Convert + .ToBase64String(MessagePackSerializer.Serialize(serverData)) + .Replace("/", "_") + .Replace("+", "-"); - var embeddedData = JsonSerializer.Deserialize(json); - - if (embeddedData is null) - { - return Result.Fail("Embedded data is empty."); - } - - return Result.Ok(embeddedData); - } - catch (Exception ex) - { - return Result.Fail(ex); - } + return $"{fileName}[{encodedData}]{ext}"; } - public Task> GetAppendedStream(string filePath, EmbeddedServerData serverData) + public Result TryGetEmbeddedData(string filePath) { try { - var json = JsonSerializer.Serialize(serverData); - var jsonBytes = Encoding.UTF8.GetBytes(json); - byte[] appendPayload = [.. jsonBytes, .. BitConverter.GetBytes(jsonBytes.Length)]; - var fs = File.Open(filePath, FileMode.Open, FileAccess.Read, FileShare.Read); - var appendableStream = new AppendableStream(fs, appendPayload); - return Task.FromResult(Result.Ok(appendableStream)); + var fileName = Path.GetFileNameWithoutExtension(filePath); + var start = fileName.LastIndexOf('[') + 1; + var end = fileName.LastIndexOf(']'); + var base64 = fileName[start..end] + .Replace("_", "/") + .Replace("-", "+"); + + var embeddedData = MessagePackSerializer.Deserialize(Convert.FromBase64String(base64)); + return Result.Ok(embeddedData); } catch (Exception ex) { - return Task.FromResult(Result.Fail(ex)); + return Result.Fail(ex); } } } From cd3057dac1223ea48729de983655c2533a997589 Mon Sep 17 00:00:00 2001 From: Jared Goodwin Date: Tue, 4 Jun 2024 12:34:20 -0700 Subject: [PATCH 08/13] Add ability to upload custom versions of the attended support client. --- .azure-pipelines/Release Build.yml | 132 +++++------ .gitignore | 1 + Desktop.Linux/Program.cs | 4 +- Desktop.Shared/Services/BrandingProvider.cs | 4 +- Desktop.Win/Program.cs | 4 +- Server/API/ClientDownloadsController.cs | 41 ++-- Server/API/CustomBinariesController.cs | 52 +++++ Server/Components/Devices/DeviceCard.razor | 4 +- Server/Components/Devices/DeviceCard.razor.cs | 58 +++-- Server/Components/Pages/Deploy.razor | 220 +++++++++++++++--- Server/Components/_Imports.razor | 3 +- Server/Program.cs | 3 +- Server/Services/DataService.cs | 25 +- ...rcher.cs => EmbeddedServerDataProvider.cs} | 15 +- submodules/Immense.RemoteControl | 2 +- 15 files changed, 408 insertions(+), 160 deletions(-) create mode 100644 Server/API/CustomBinariesController.cs rename Shared/Services/{EmbeddedServerDataSearcher.cs => EmbeddedServerDataProvider.cs} (77%) diff --git a/.azure-pipelines/Release Build.yml b/.azure-pipelines/Release Build.yml index b2424efa8..37d462eb4 100644 --- a/.azure-pipelines/Release Build.yml +++ b/.azure-pipelines/Release Build.yml @@ -5,71 +5,71 @@ pr: - master jobs: -#- job: Mac_Build -# displayName: Mac Build -# timeoutInMinutes: 360 -# pool: -# vmImage: macos-latest -# steps: -# -# - task: InstallSSHKey@0 -# inputs: -# knownHostsEntry: | -# github.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl -# github.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ== -# github.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEmKSENjQEezOmxkZMy7opKgwFB9nkt5YRrYMjNuG5N87uRgg6CLrbo5wAdT/y6v0mKV0U2w0WZ2YB/++Tpockg= -# sshKeySecureFile: 'pipelines_rsa' -# -# - checkout: self -# submodules: recursive -# clean: true -# fetchTags: false -# -# - task: PowerShell@2 -# displayName: Add CurrentVersion Variable -# inputs: -# targetType: inline -# script: | -# $VersionString = git show -s --format=%ci $(Build.SourceVersion) -# $VersionDate = [DateTimeOffset]::Parse($VersionString) -# $CurrentVersion = $VersionDate.ToString("yyyy.MM.dd.HHmm") -# -# [System.Console]::WriteLine("##vso[task.setvariable variable=CurrentVersion]$CurrentVersion") -# -# Write-Host "Setting current version to $CurrentVersion." -# -# - task: UseDotNet@2 -# displayName: Use .NET SDK -# inputs: -# version: 8.x -# -# - task: DotNetCoreCLI@2 -# displayName: dotnet publish x64 -# inputs: -# command: publish -# publishWebProjects: false -# projects: '**/Agent.csproj' -# arguments: -c $(BuildConfiguration) -r osx-x64 -o "$(Build.SourcesDirectory)/Agent/bin/publish" /p:Version=$(CurrentVersion) /p:FileVersion=$(CurrentVersion) -# zipAfterPublish: false -# modifyOutputPath: false -# -# - task: PowerShell@2 -# displayName: PowerShell Script -# inputs: -# targetType: inline -# script: | -# Compress-Archive -Path "$(Build.SourcesDirectory)/Agent/bin/publish/*" -DestinationPath "$(Build.SourcesDirectory)/Agent/bin/Remotely-MacOS-x64.zip" -Force -# -# - task: PublishPipelineArtifact@1 -# displayName: Publish macOS x64 Agent -# inputs: -# path: $(Build.SourcesDirectory)/Agent/bin/Remotely-MacOS-x64.zip -# artifactName: Mac-x64-Agent +- job: Mac_Build + displayName: Mac Build + timeoutInMinutes: 360 + pool: + vmImage: macos-latest + steps: + + - task: InstallSSHKey@0 + inputs: + knownHostsEntry: | + github.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl + github.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ== + github.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEmKSENjQEezOmxkZMy7opKgwFB9nkt5YRrYMjNuG5N87uRgg6CLrbo5wAdT/y6v0mKV0U2w0WZ2YB/++Tpockg= + sshKeySecureFile: 'pipelines_rsa' + + - checkout: self + submodules: recursive + clean: true + fetchTags: false + + - task: PowerShell@2 + displayName: Add CurrentVersion Variable + inputs: + targetType: inline + script: | + $VersionString = git show -s --format=%ci $(Build.SourceVersion) + $VersionDate = [DateTimeOffset]::Parse($VersionString) + $CurrentVersion = $VersionDate.ToString("yyyy.MM.dd.HHmm") + + [System.Console]::WriteLine("##vso[task.setvariable variable=CurrentVersion]$CurrentVersion") + + Write-Host "Setting current version to $CurrentVersion." + + - task: UseDotNet@2 + displayName: Use .NET SDK + inputs: + version: 8.x + + - task: DotNetCoreCLI@2 + displayName: dotnet publish x64 + inputs: + command: publish + publishWebProjects: false + projects: '**/Agent.csproj' + arguments: -c $(BuildConfiguration) -r osx-x64 -o "$(Build.SourcesDirectory)/Agent/bin/publish" /p:Version=$(CurrentVersion) /p:FileVersion=$(CurrentVersion) + zipAfterPublish: false + modifyOutputPath: false + + - task: PowerShell@2 + displayName: PowerShell Script + inputs: + targetType: inline + script: | + Compress-Archive -Path "$(Build.SourcesDirectory)/Agent/bin/publish/*" -DestinationPath "$(Build.SourcesDirectory)/Agent/bin/Remotely-MacOS-x64.zip" -Force + + - task: PublishPipelineArtifact@1 + displayName: Publish macOS x64 Agent + inputs: + path: $(Build.SourcesDirectory)/Agent/bin/Remotely-MacOS-x64.zip + artifactName: Mac-x64-Agent - job: Windows_Build displayName: Windows Build timeoutInMinutes: 360 - #dependsOn: Mac_Build + dependsOn: Mac_Build pool: vmImage: windows-latest @@ -90,11 +90,11 @@ jobs: - task: VisualStudioTestPlatformInstaller@1 displayName: Visual Studio Test Platform Installer -# - task: DownloadPipelineArtifact@2 -# displayName: Download macOS x64 Agent -# inputs: -# artifact: Mac-x64-Agent -# path: $(Build.SourcesDirectory)\Server\wwwroot\Content\ + - task: DownloadPipelineArtifact@2 + displayName: Download macOS x64 Agent + inputs: + artifact: Mac-x64-Agent + path: $(Build.SourcesDirectory)\Server\wwwroot\Content\ - task: PowerShell@2 displayName: Add CurrentVersion Variable diff --git a/.gitignore b/.gitignore index 243775cf0..93d822baf 100644 --- a/.gitignore +++ b/.gitignore @@ -296,3 +296,4 @@ Server.Installer/Properties/launchSettings.json !/.vscode/launch.json !/.vscode/tasks.json /Server/appsettings.Development.json +/Server/AppData diff --git a/Desktop.Linux/Program.cs b/Desktop.Linux/Program.cs index 00db19f0c..72ee7948f 100644 --- a/Desktop.Linux/Program.cs +++ b/Desktop.Linux/Program.cs @@ -33,7 +33,7 @@ public static async Task Main(string[] args) var logger = new FileLogger("Remotely_Desktop", version, "Program.cs"); var filePath = Environment.ProcessPath ?? Environment.GetCommandLineArgs().First(); var serverUrl = Debugger.IsAttached ? "http://localhost:5000" : string.Empty; - var getEmbeddedResult = EmbeddedServerDataSearcher.Instance.TryGetEmbeddedData(filePath); + var getEmbeddedResult = EmbeddedServerDataProvider.Instance.TryGetEmbeddedData(filePath); if (getEmbeddedResult.IsSuccess) { serverUrl = getEmbeddedResult.Value.ServerUrl.AbsoluteUri; @@ -46,7 +46,7 @@ public static async Task Main(string[] args) var services = new ServiceCollection(); services.AddSingleton(); - services.AddSingleton(); + services.AddSingleton(); services.AddRemoteControlLinux( config => diff --git a/Desktop.Shared/Services/BrandingProvider.cs b/Desktop.Shared/Services/BrandingProvider.cs index b57bbbd3a..113cac48a 100644 --- a/Desktop.Shared/Services/BrandingProvider.cs +++ b/Desktop.Shared/Services/BrandingProvider.cs @@ -13,7 +13,7 @@ namespace Desktop.Shared.Services; public class BrandingProvider : IBrandingProvider { private readonly IAppState _appState; - private readonly IEmbeddedServerDataSearcher _embeddedDataSearcher; + private readonly IEmbeddedServerDataProvider _embeddedDataSearcher; private readonly ILogger _logger; private readonly IOrganizationIdProvider _orgIdProvider; private BrandingInfoBase? _brandingInfo; @@ -22,7 +22,7 @@ public class BrandingProvider : IBrandingProvider public BrandingProvider( IAppState appState, IOrganizationIdProvider orgIdProvider, - IEmbeddedServerDataSearcher embeddedServerDataSearcher, + IEmbeddedServerDataProvider embeddedServerDataSearcher, ILogger logger) { _appState = appState; diff --git a/Desktop.Win/Program.cs b/Desktop.Win/Program.cs index c677c90f6..42e63e994 100644 --- a/Desktop.Win/Program.cs +++ b/Desktop.Win/Program.cs @@ -35,7 +35,7 @@ public static async Task Main(string[] args) var logger = new FileLogger("Remotely_Desktop", version, "Program.cs"); var filePath = Environment.ProcessPath ?? Environment.GetCommandLineArgs().First(); var serverUrl = Debugger.IsAttached ? "https://localhost:5001" : string.Empty; - var getEmbeddedResult = EmbeddedServerDataSearcher.Instance.TryGetEmbeddedData(filePath); + var getEmbeddedResult = EmbeddedServerDataProvider.Instance.TryGetEmbeddedData(filePath); if (getEmbeddedResult.IsSuccess) { serverUrl = getEmbeddedResult.Value.ServerUrl.AbsoluteUri; @@ -47,7 +47,7 @@ public static async Task Main(string[] args) var services = new ServiceCollection(); services.AddSingleton(); - services.AddSingleton(EmbeddedServerDataSearcher.Instance); + services.AddSingleton(EmbeddedServerDataProvider.Instance); services.AddRemoteControlWindows( config => diff --git a/Server/API/ClientDownloadsController.cs b/Server/API/ClientDownloadsController.cs index 1cd1aa950..e3944608b 100644 --- a/Server/API/ClientDownloadsController.cs +++ b/Server/API/ClientDownloadsController.cs @@ -15,14 +15,14 @@ namespace Remotely.Server.API; public class ClientDownloadsController : ControllerBase { private readonly IDataService _dataService; - private readonly IEmbeddedServerDataSearcher _embeddedDataSearcher; + private readonly IEmbeddedServerDataProvider _embeddedDataSearcher; private readonly SemaphoreSlim _fileLock = new(1, 1); private readonly IWebHostEnvironment _hostEnv; private readonly ILogger _logger; public ClientDownloadsController( IWebHostEnvironment hostEnv, - IEmbeddedServerDataSearcher embeddedDataSearcher, + IEmbeddedServerDataProvider embeddedDataSearcher, IDataService dataService, ILogger logger) { @@ -39,27 +39,27 @@ public async Task GetDesktop(string platformID) { case "WindowsDesktop-x64": { - var filePath = Path.Combine(_hostEnv.WebRootPath, "Content", "Win-x64", "Remotely_Desktop.exe"); + var filePath = Path.Combine("Content", "Win-x64", "Remotely_Desktop.exe"); return await GetDesktopFile(filePath); } case "WindowsDesktop-x86": { - var filePath = Path.Combine(_hostEnv.WebRootPath, "Content", "Win-x86", "Remotely_Desktop.exe"); + var filePath = Path.Combine("Content", "Win-x86", "Remotely_Desktop.exe"); return await GetDesktopFile(filePath); } case "UbuntuDesktop": { - var filePath = Path.Combine(_hostEnv.WebRootPath, "Content", "Linux-x64", "Remotely_Desktop"); + var filePath = Path.Combine("Content", "Linux-x64", "Remotely_Desktop"); return await GetDesktopFile(filePath); } case "MacOS-x64": { - var filePath = Path.Combine(_hostEnv.WebRootPath, "Content", "MacOS-x64", "Remotely_Desktop"); + var filePath = Path.Combine("Content", "MacOS-x64", "Remotely_Desktop"); return await GetDesktopFile(filePath); } case "MacOS-arm64": { - var filePath = Path.Combine(_hostEnv.WebRootPath, "Content", "MacOS-arm64", "Remotely_Desktop"); + var filePath = Path.Combine("Content", "MacOS-arm64", "Remotely_Desktop"); return await GetDesktopFile(filePath); } default: @@ -75,27 +75,27 @@ public async Task GetDesktop(string platformId, string organizati { case "WindowsDesktop-x64": { - var filePath = Path.Combine(_hostEnv.WebRootPath, "Content", "Win-x64", "Remotely_Desktop.exe"); + var filePath = Path.Combine("Content", "Win-x64", "Remotely_Desktop.exe"); return await GetDesktopFile(filePath, organizationId); } case "WindowsDesktop-x86": { - var filePath = Path.Combine(_hostEnv.WebRootPath, "Content", "Win-x86", "Remotely_Desktop.exe"); + var filePath = Path.Combine("Content", "Win-x86", "Remotely_Desktop.exe"); return await GetDesktopFile(filePath, organizationId); } case "UbuntuDesktop": { - var filePath = Path.Combine(_hostEnv.WebRootPath, "Content", "Linux-x64", "Remotely_Desktop"); + var filePath = Path.Combine("Content", "Linux-x64", "Remotely_Desktop"); return await GetDesktopFile(filePath, organizationId); } case "MacOS-x64": { - var filePath = Path.Combine(_hostEnv.WebRootPath, "Content", "MacOS-x64", "Remotely_Desktop"); + var filePath = Path.Combine("Content", "MacOS-x64", "Remotely_Desktop"); return await GetDesktopFile(filePath); } case "MacOS-arm64": { - var filePath = Path.Combine(_hostEnv.WebRootPath, "Content", "MacOS-arm64", "Remotely_Desktop"); + var filePath = Path.Combine("Content", "MacOS-arm64", "Remotely_Desktop"); return await GetDesktopFile(filePath); } default: @@ -137,14 +137,9 @@ private async Task GetBashInstaller(string fileName, string organ return File(fileBytes, "application/octet-stream", fileName); } - private async Task GetDesktopFile(string filePath, string? organizationId = null) + private async Task GetDesktopFile(string relativeFilePath, string? organizationId = null) { - var settings = await _dataService.GetSettings(); await LogRequest(nameof(GetDesktopFile)); - - var effectiveScheme = settings.ForceClientHttps ? "https" : Request.Scheme; - var serverUrl = $"{effectiveScheme}://{Request.Host}"; - var defaultOrg = await _dataService.GetDefaultOrganization(); // The default org will be used if unspecified, so might as well save the @@ -154,10 +149,14 @@ private async Task GetDesktopFile(string filePath, string? organi { organizationId = null; } + + var settings = await _dataService.GetSettings(); + var effectiveScheme = settings.ForceClientHttps ? "https" : Request.Scheme; + var serverUrl = $"{effectiveScheme}://{Request.Host}"; + var embeddedData = new EmbeddedServerData(new Uri(serverUrl), organizationId); - var fileName = _embeddedDataSearcher.GetEncodedFileName(filePath, embeddedData); - var fileInfo = _hostEnv.WebRootFileProvider.GetFileInfo(filePath.Replace(_hostEnv.WebRootPath, string.Empty)); - return File(fileInfo.CreateReadStream(), "application/octet-stream", fileName); + var fileName = _embeddedDataSearcher.GetEncodedFileName(relativeFilePath, embeddedData); + return File(relativeFilePath, "application/octet-stream", fileName); } private async Task GetInstallFile(string organizationId, string platformID) diff --git a/Server/API/CustomBinariesController.cs b/Server/API/CustomBinariesController.cs new file mode 100644 index 000000000..cd12ff4e5 --- /dev/null +++ b/Server/API/CustomBinariesController.cs @@ -0,0 +1,52 @@ +using Microsoft.AspNetCore.Mvc; +using Remotely.Server.Services; +using Remotely.Shared.Models; +using Remotely.Shared.Services; + +namespace Remotely.Server.API; + +[Route("api/custom-binaries")] +[ApiController] +public class CustomBinariesController( + IDataService _dataService, + IWebHostEnvironment _hostingEnvironment, + IEmbeddedServerDataProvider _embeddedData) : ControllerBase +{ + [HttpGet("win-x86/desktop/{organizationId}")] + public async Task GetWinX86Desktop(string organizationId) + { + var embeddedData = await GetEmbeddedData(organizationId); + var filePath = Path.Combine(_hostingEnvironment.ContentRootPath, "AppData", "Win-x86", "Remotely_Desktop.exe"); + var fileName = _embeddedData.GetEncodedFileName(filePath, embeddedData); + var rs = System.IO.File.Open(filePath, FileMode.Open, FileAccess.Read, FileShare.Read); + return File(rs, "application/octet-stream", fileName); + } + + [HttpGet("win-x64/desktop/{organizationId}")] + public async Task GetWinX64Desktop(string organizationId) + { + var embeddedData = await GetEmbeddedData(organizationId); + var filePath = Path.Combine(_hostingEnvironment.ContentRootPath, "AppData", "Win-x64", "Remotely_Desktop.exe"); + var fileName = _embeddedData.GetEncodedFileName(filePath, embeddedData); + var rs = System.IO.File.Open(filePath, FileMode.Open, FileAccess.Read, FileShare.Read); + return File(rs, "application/octet-stream", fileName); + } + + private async Task GetEmbeddedData(string? organizationId) + { + var defaultOrg = await _dataService.GetDefaultOrganization(); + + // The default org will be used if unspecified, so might as well save the + // space in the file name. + if (defaultOrg.IsSuccess && + defaultOrg.Value.ID.Equals(organizationId, StringComparison.OrdinalIgnoreCase)) + { + organizationId = null; + } + + var settings = await _dataService.GetSettings(); + var effectiveScheme = settings.ForceClientHttps ? "https" : Request.Scheme; + var serverUrl = $"{effectiveScheme}://{Request.Host}"; + return new EmbeddedServerData(new Uri(serverUrl), organizationId); + } +} diff --git a/Server/Components/Devices/DeviceCard.razor b/Server/Components/Devices/DeviceCard.razor index 69a61ae26..c4a18746b 100644 --- a/Server/Components/Devices/DeviceCard.razor +++ b/Server/Components/Devices/DeviceCard.razor @@ -59,7 +59,9 @@ { foreach (var kvp in _fileUploadProgressLookup) { - +
+ @(GetProgressMessage(kvp.Key)) +
} }
diff --git a/Server/Components/Devices/DeviceCard.razor.cs b/Server/Components/Devices/DeviceCard.razor.cs index ca4a4fb55..df5a66913 100644 --- a/Server/Components/Devices/DeviceCard.razor.cs +++ b/Server/Components/Devices/DeviceCard.razor.cs @@ -7,6 +7,7 @@ using Remotely.Server.Models.Messages; using Remotely.Server.Services; using Remotely.Server.Services.Stores; +using Remotely.Shared; using Remotely.Shared.Entities; using Remotely.Shared.Enums; using Remotely.Shared.Utilities; @@ -36,19 +37,22 @@ public partial class DeviceCard : AuthComponentBase [Inject] - private ISelectedCardsStore SelectedCards { get; init; } = null!; + public required ISelectedCardsStore SelectedCards { get; init; } [Inject] - private IThemeProvider ThemeProvider { get; init; } = null!; + public required IThemeProvider ThemeProvider { get; init; } [Inject] - private ICircuitConnection CircuitConnection { get; init; } = null!; + public required ICircuitConnection CircuitConnection { get; init; } [Inject] - private IDataService DataService { get; init; } = null!; + public required IDataService DataService { get; init; } [Inject] - private IChatSessionStore ChatCache { get; init; } = null!; + public required IChatSessionStore ChatCache { get; init; } + + [Inject] + public required ILogger Logger { get; init; } private bool IsExpanded => _state == DeviceCardState.Expanded; @@ -220,23 +224,44 @@ await DataService.UpdateDevice(Device.ID, private async Task OnFileInputChanged(InputFileChangeEventArgs args) { - EnsureUserSet(); - - ToastService.ShowToast("File upload started."); - - var fileId = await DataService.AddSharedFile(args.File, User.OrganizationID, OnFileInputProgress); + try + { + EnsureUserSet(); - var transferId = Guid.NewGuid().ToString(); + ToastService.ShowToast("File upload started."); - var result = await CircuitConnection.TransferFileFromBrowserToAgent(Device.ID, transferId, new[] { fileId }); + if (args.File.Size > AppConstants.MaxUploadFileSize) + { + var maxFileSize = AppConstants.MaxUploadFileSize / 1000 / 1000; + ToastService.ShowToast2($"File size exceeds the maximum allowed size of {maxFileSize}MB.", ToastType.Warning); + return; + } + + var fileId = await DataService.AddSharedFile(args.File, User.OrganizationID, OnFileInputProgress); + var transferId = Guid.NewGuid().ToString(); + var result = await CircuitConnection.TransferFileFromBrowserToAgent(Device.ID, transferId, [fileId]); - if (!result) + if (!result) + { + ToastService.ShowToast("Device not found.", classString: "bg-warning"); + } + else + { + ToastService.ShowToast("File upload completed."); + } + } + catch (Exception ex) { - ToastService.ShowToast("Device not found.", classString: "bg-warning"); + Logger.LogError(ex, "Error while uploading file to device."); + ToastService.ShowToast2("Failed to upload file", ToastType.Error); } - else + finally { - ToastService.ShowToast("File upload completed."); + if (args.File.Name is not null) + { + _ = _fileUploadProgressLookup.TryRemove(args.File.Name, out _); + await InvokeAsync(StateHasChanged); + } } } @@ -252,6 +277,7 @@ private void OnFileInputProgress(double percentComplete, string fileName) _fileUploadProgressLookup.AddOrUpdate(fileName, percentComplete, (k, v) => percentComplete); InvokeAsync(StateHasChanged); } + private void OpenDeviceDetails() { JsInterop.OpenWindow($"/device-details/{Device.ID}", "_blank"); diff --git a/Server/Components/Pages/Deploy.razor b/Server/Components/Pages/Deploy.razor index 11af24b88..d15de1866 100644 --- a/Server/Components/Pages/Deploy.razor +++ b/Server/Components/Pages/Deploy.razor @@ -1,18 +1,29 @@ @page "/deploy" +@using Microsoft.Extensions.FileProviders +@using System.Diagnostics @attribute [Authorize] @inject NavigationManager NavMan @inject IAuthService Auth @inject IDataService DataService @inject IJsInterop JsInterop @inject IToastService Toasts +@inject IWebHostEnvironment HostEnv @inject ILogger Logger -

Deploy Scripts

-

+@if (_isLoading) +{ + +} + +

+ Persistent Agents +

+

Copy and paste on a remote computer to install the agent.

+
Windows 10/11 (32-Bit and 64-Bit) @@ -53,34 +64,126 @@
+

+ Attended Support +

+

+ You can upload custom versions of the attended support client (i.e. "Remotely_Desktop.exe") + and send the download link to end-users when they need support. For example, you could sign + the EXE with a commercial certificate so the end-user (hopefully) doesn't see a SmartScreen + warning about the file. +

+

+ NOTE: These binaries must be manually updated each time a new Docker image is pulled. +

+ +
+
+
+ Windows 10/11 (64-Bit) +
+ + @if (_winX64File?.Exists != true) + { +
+ Note: A custom binary for this file hasn't been uploaded yet. +
+ } + else if (_winX64File?.PhysicalPath is not null) + { +
+ Created Date: @(_winX64CreatedDate) UTC +
+ } + +
+ + + + Upload File + + +
+ +
+ + +
+ +
+ +
+
+ Windows 10/11 (32-Bit) +
+ + @if(_winX86File?.Exists != true) + { +
+ Note: A custom binary for this file hasn't been uploaded yet. +
+ } + else if (_winX86File?.PhysicalPath is not null) + { +
+ Created Date: @(_winX86CreatedDate) UTC +
+ } + +
+ + + + Upload File + + +
+ +
+ + +
+
+
+ @code { - private string? _organizationId; - private bool _isAuthenticated; + private static readonly SemaphoreSlim _writeLock = new(1, 1); + private string _organizationId = string.Empty; private string _windowsScript = string.Empty; private string _ubuntuScript = string.Empty; private string _manjaroScript = string.Empty; + private string _appDataDir = string.Empty; + private IFileInfo? _winX64File; + private IFileInfo? _winX86File; + private string _winX64Uri = string.Empty; + private string _winX86Uri = string.Empty; + private bool _isLoading = false; + private string _loadingMessage = string.Empty; + private DateTime _winX64CreatedDate; + private DateTime _winX86CreatedDate; protected override async Task OnInitializedAsync() { - _isAuthenticated = await Auth.IsAuthenticated(); - - if (_isAuthenticated) + var userResult = await Auth.GetUser(); + if (!userResult.IsSuccess) { - var userResult = await Auth.GetUser(); - if (userResult.IsSuccess) - { - _organizationId = userResult.Value.OrganizationID; - } - } - else - { - var orgResult = await DataService.GetDefaultOrganization(); - if (orgResult.IsSuccess) - { - _organizationId = orgResult.Value.ID; - } + NavMan.NavigateTo("/", false); + return; } + _organizationId = userResult.Value.OrganizationID; + _appDataDir = Path.Combine(HostEnv.ContentRootPath, "AppData"); + _winX64Uri = $"{NavMan.BaseUri}api/custom-binaries/win-x64/desktop/{_organizationId}"; + _winX86Uri = $"{NavMan.BaseUri}api/custom-binaries/win-x86/desktop/{_organizationId}"; + + SetFileInfos(); + SetScriptContent(); await base.OnInitializedAsync(); @@ -104,6 +207,44 @@ Toasts.ShowToast2("Failed to set clipboard content", ToastType.Error); } + + private string GetLinuxScript(string platformId) + { + return + $"sudo rm -f /tmp/Install-Remotely.sh && " + + $"sudo wget -q -O /tmp/Install-Remotely.sh {NavMan.BaseUri}api/ClientDownloads/{platformId}/{_organizationId} && " + + $"sudo chmod +x /tmp/Install-Remotely.sh && " + + $"sudo /tmp/Install-Remotely.sh"; + } + + + private async Task HandleWin64FileChanged(InputFileChangeEventArgs ev) + { + await TrySaveFile(_winX64File, ev); + } + + private async Task HandleWin86FileChanged(InputFileChangeEventArgs ev) + { + await TrySaveFile(_winX86File, ev); + } + + private void SetFileInfos() + { + _winX64File = HostEnv.ContentRootFileProvider.GetFileInfo("AppData/Win-x64/Remotely_Desktop.exe"); + if (_winX64File?.Exists == true && _winX64File?.PhysicalPath is not null) + { + var fileInfo = new FileInfo(_winX64File.PhysicalPath); + _winX64CreatedDate = fileInfo.CreationTimeUtc; + } + + _winX86File = HostEnv.ContentRootFileProvider.GetFileInfo("AppData/Win-x86/Remotely_Desktop.exe"); + if (_winX86File?.Exists == true && _winX86File?.PhysicalPath is not null) + { + var fileInfo = new FileInfo(_winX86File.PhysicalPath); + _winX86CreatedDate = fileInfo.CreationTimeUtc; + } + } + private void SetScriptContent() { _windowsScript = @@ -115,12 +256,39 @@ _manjaroScript = GetLinuxScript("ManjaroInstaller-x64"); } - private string GetLinuxScript(string platformId) + private async Task TrySaveFile(IFileInfo? fileInfo, InputFileChangeEventArgs ev) { - return - $"sudo rm -f /tmp/Install-Remotely.sh && " + - $"sudo wget -q -O /tmp/Install-Remotely.sh {NavMan.BaseUri}api/ClientDownloads/{platformId}/{_organizationId} && " + - $"sudo chmod +x /tmp/Install-Remotely.sh && " + - $"sudo /tmp/Install-Remotely.sh"; + await _writeLock.WaitAsync(); + try + { + if (fileInfo?.PhysicalPath is null) + { + Toasts.ShowToast2("Unable to find save path", ToastType.Error); + return; + } + + _loadingMessage = "Uploading file"; + _isLoading = true; + await InvokeAsync(StateHasChanged); + + _ = Directory.CreateDirectory(Path.GetDirectoryName(fileInfo.PhysicalPath)!); + await using var rs = ev.File.OpenReadStream(500_000_000); + await using var fs = File.Open(fileInfo.PhysicalPath, FileMode.Create); + await rs.CopyToAsync(fs); + Toasts.ShowToast2("Custom binary uploaded successfully", ToastType.Success); + SetFileInfos(); + } + catch (Exception ex) + { + Logger.LogError(ex, "Error while uploading custom binary."); + Toasts.ShowToast2("Failed to upload custom binary", ToastType.Error); + } + finally + { + _writeLock.Release(); + _isLoading = false; + await InvokeAsync(StateHasChanged); + } } + } \ No newline at end of file diff --git a/Server/Components/_Imports.razor b/Server/Components/_Imports.razor index bbc5eac2e..9b1d1830c 100644 --- a/Server/Components/_Imports.razor +++ b/Server/Components/_Imports.razor @@ -25,4 +25,5 @@ @using Remotely.Server.Components.TreeView @using Remotely.Server.Auth @using Remotely.Shared.Entities -@using Remotely.Server.Models \ No newline at end of file +@using Remotely.Server.Models +@using Remotely.Shared.Services; \ No newline at end of file diff --git a/Server/Program.cs b/Server/Program.cs index 8d43320fa..9104f0113 100644 --- a/Server/Program.cs +++ b/Server/Program.cs @@ -193,7 +193,6 @@ services.AddSignalR(options => { options.EnableDetailedErrors = builder.Environment.IsDevelopment(); - options.MaximumParallelInvocationsPerClient = 5; options.MaximumReceiveMessageSize = 100_000; }) .AddJsonProtocol(options => @@ -246,7 +245,7 @@ services.AddScoped(); services.AddScoped(); services.AddSingleton(); -services.AddSingleton(); +services.AddSingleton(); services.AddSingleton(); services.AddScoped(); services.AddScoped(); diff --git a/Server/Services/DataService.cs b/Server/Services/DataService.cs index 65c4d98b1..ed7b8bc3e 100644 --- a/Server/Services/DataService.cs +++ b/Server/Services/DataService.cs @@ -15,6 +15,7 @@ using Remotely.Shared.Dtos; using Remotely.Shared.Entities; using Remotely.Shared.Enums; +using Remotely.Shared.Extensions; using Remotely.Shared.Models; using Remotely.Shared.Utilities; using Remotely.Shared.ViewModels; @@ -552,18 +553,26 @@ public async Task AddScriptRun(ScriptRun scriptRun) public async Task AddSharedFile(IBrowserFile file, string organizationId, Action progressCallback) { - var fileContents = new byte[file.Size]; - using var stream = file.OpenReadStream(AppConstants.MaxUploadFileSize); + var fileSize = file.Size; + var fileName = file.Name; - for (var i = 0; i < file.Size; i += 5_000) - { - var readSize = (int)Math.Min(5_000, file.Size - i); - await stream.ReadAsync(fileContents.AsMemory(i, readSize)); + var fileContents = new byte[fileSize]; + var stream = file.OpenReadStream(AppConstants.MaxUploadFileSize); - progressCallback.Invoke((double)stream.Position / stream.Length, file.Name); + var bytesRead = 0; + while (bytesRead < fileSize) + { + var segmentEnd = Math.Min(50_000, fileSize - bytesRead); + var read = await stream.ReadAsync(fileContents.AsMemory(bytesRead, (int)segmentEnd)); + if (read == 0) + { + break; + } + bytesRead += read; + progressCallback.Invoke((double)bytesRead / fileSize, fileName); } - progressCallback.Invoke(1, file.Name); + progressCallback.Invoke(1, fileName); return await AddSharedFileImpl(file.Name, fileContents, file.ContentType, organizationId); } diff --git a/Shared/Services/EmbeddedServerDataSearcher.cs b/Shared/Services/EmbeddedServerDataProvider.cs similarity index 77% rename from Shared/Services/EmbeddedServerDataSearcher.cs rename to Shared/Services/EmbeddedServerDataProvider.cs index 297e3493a..e42ed109b 100644 --- a/Shared/Services/EmbeddedServerDataSearcher.cs +++ b/Shared/Services/EmbeddedServerDataProvider.cs @@ -2,30 +2,21 @@ using Immense.RemoteControl.Shared; using MessagePack; -using Microsoft.Extensions.Logging; -using Remotely.Shared.Entities; using Remotely.Shared.Models; -using Remotely.Shared.Utilities; using System; -using System.Collections.Generic; -using System.Diagnostics; using System.IO; -using System.Linq; -using System.Text; -using System.Text.Json; -using System.Threading.Tasks; namespace Remotely.Shared.Services; -public interface IEmbeddedServerDataSearcher +public interface IEmbeddedServerDataProvider { string GetEncodedFileName(string filePath, EmbeddedServerData serverData); Result TryGetEmbeddedData(string filePath); } -public class EmbeddedServerDataSearcher() : IEmbeddedServerDataSearcher +public class EmbeddedServerDataProvider : IEmbeddedServerDataProvider { - public static EmbeddedServerDataSearcher Instance { get; } = new(); + public static EmbeddedServerDataProvider Instance { get; } = new(); public string GetEncodedFileName(string filePath, EmbeddedServerData serverData) { diff --git a/submodules/Immense.RemoteControl b/submodules/Immense.RemoteControl index 6c77d32dd..9151b7534 160000 --- a/submodules/Immense.RemoteControl +++ b/submodules/Immense.RemoteControl @@ -1 +1 @@ -Subproject commit 6c77d32dd6d307ad60d9acd6bbe2c98f2b9b50a9 +Subproject commit 9151b7534e56a7fa7f2233fa5a415a14afc9005c From 0bf53841a76517fe23d8f9052c520707164c1719 Mon Sep 17 00:00:00 2001 From: Jared Goodwin Date: Tue, 4 Jun 2024 12:49:20 -0700 Subject: [PATCH 09/13] Lock custom binaries behind server admin privilege. --- Server/Components/Pages/Deploy.razor | 76 +++++++++++++++++++--------- 1 file changed, 51 insertions(+), 25 deletions(-) diff --git a/Server/Components/Pages/Deploy.razor b/Server/Components/Pages/Deploy.razor index d15de1866..0e8b3a88f 100644 --- a/Server/Components/Pages/Deploy.razor +++ b/Server/Components/Pages/Deploy.razor @@ -68,8 +68,8 @@ Attended Support

- You can upload custom versions of the attended support client (i.e. "Remotely_Desktop.exe") - and send the download link to end-users when they need support. For example, you could sign + You can upload custom versions of the attended support client (i.e. "Remotely_Desktop.exe") + and send the download link to end-users when they need support. For example, you could sign the EXE with a commercial certificate so the end-user (hopefully) doesn't see a SmartScreen warning about the file.

@@ -91,20 +91,28 @@ } else if (_winX64File?.PhysicalPath is not null) { -
+
Created Date: @(_winX64CreatedDate) UTC
} -
- - - - Upload File - - -
+ @if (_isServerAdmin) + { +
+ + + + Upload File + + +
+ + Note: Only server admins will have this button. + +
+
+ }
@@ -120,7 +128,7 @@ Windows 10/11 (32-Bit)
- @if(_winX86File?.Exists != true) + @if (_winX86File?.Exists != true) {
Note: A custom binary for this file hasn't been uploaded yet. @@ -128,20 +136,28 @@ } else if (_winX86File?.PhysicalPath is not null) { -
+
Created Date: @(_winX86CreatedDate) UTC
} -
- - - - Upload File - - -
+ @if (_isServerAdmin) + { +
+ + + + Upload File + + +
+ + Note: Only server admins will have this button. + +
+
+ }
@@ -165,6 +181,7 @@ private string _winX86Uri = string.Empty; private bool _isLoading = false; private string _loadingMessage = string.Empty; + private bool _isServerAdmin; private DateTime _winX64CreatedDate; private DateTime _winX86CreatedDate; @@ -177,6 +194,7 @@ return; } + _isServerAdmin = userResult.Value.IsServerAdmin; _organizationId = userResult.Value.OrganizationID; _appDataDir = Path.Combine(HostEnv.ContentRootPath, "AppData"); _winX64Uri = $"{NavMan.BaseUri}api/custom-binaries/win-x64/desktop/{_organizationId}"; @@ -247,8 +265,8 @@ private void SetScriptContent() { - _windowsScript = - $"Invoke-WebRequest -Uri '{NavMan.BaseUri}api/ClientDownloads/WindowsInstaller/{_organizationId}' -OutFile \"${{env:TEMP}}\\Install-Remotely.ps1\" -UseBasicParsing;"+ + _windowsScript = + $"Invoke-WebRequest -Uri '{NavMan.BaseUri}api/ClientDownloads/WindowsInstaller/{_organizationId}' -OutFile \"${{env:TEMP}}\\Install-Remotely.ps1\" -UseBasicParsing;" + "Start-Process -FilePath 'powershell.exe' -ArgumentList (\"-executionpolicy\", \"bypass\", \"-f\", \"${env:TEMP}\\Install-Remotely.ps1\") -Verb RunAs;"; _ubuntuScript = GetLinuxScript("UbuntuInstaller-x64"); @@ -258,6 +276,14 @@ private async Task TrySaveFile(IFileInfo? fileInfo, InputFileChangeEventArgs ev) { + // Since this is server-side Blazor, it would be impossible to + // get to this point without being a server admin. But we'll add + // it here to prevent any issues caused by future changes. + if (!_isServerAdmin) + { + return; + } + await _writeLock.WaitAsync(); try { From d5d56131b2f8ebdf85882a9fd742bcd1c231c9f3 Mon Sep 17 00:00:00 2001 From: Jared Goodwin Date: Tue, 4 Jun 2024 13:09:16 -0700 Subject: [PATCH 10/13] Update Immense.RemoteControl --- submodules/Immense.RemoteControl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/submodules/Immense.RemoteControl b/submodules/Immense.RemoteControl index 9151b7534..4a7d9edde 160000 --- a/submodules/Immense.RemoteControl +++ b/submodules/Immense.RemoteControl @@ -1 +1 @@ -Subproject commit 9151b7534e56a7fa7f2233fa5a415a14afc9005c +Subproject commit 4a7d9eddedb097654ad992d74ae6f521e4ed2ef2 From b8b1969bad2622181811acdd676e68caa705649b Mon Sep 17 00:00:00 2001 From: Jared Goodwin Date: Tue, 4 Jun 2024 13:47:23 -0700 Subject: [PATCH 11/13] Update pipeline. --- .azure-pipelines/Release Build.yml | 73 ------------------------------ Utilities/Publish.ps1 | 4 +- 2 files changed, 2 insertions(+), 75 deletions(-) diff --git a/.azure-pipelines/Release Build.yml b/.azure-pipelines/Release Build.yml index 37d462eb4..2de353e2e 100644 --- a/.azure-pipelines/Release Build.yml +++ b/.azure-pipelines/Release Build.yml @@ -5,67 +5,6 @@ pr: - master jobs: -- job: Mac_Build - displayName: Mac Build - timeoutInMinutes: 360 - pool: - vmImage: macos-latest - steps: - - - task: InstallSSHKey@0 - inputs: - knownHostsEntry: | - github.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl - github.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ== - github.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEmKSENjQEezOmxkZMy7opKgwFB9nkt5YRrYMjNuG5N87uRgg6CLrbo5wAdT/y6v0mKV0U2w0WZ2YB/++Tpockg= - sshKeySecureFile: 'pipelines_rsa' - - - checkout: self - submodules: recursive - clean: true - fetchTags: false - - - task: PowerShell@2 - displayName: Add CurrentVersion Variable - inputs: - targetType: inline - script: | - $VersionString = git show -s --format=%ci $(Build.SourceVersion) - $VersionDate = [DateTimeOffset]::Parse($VersionString) - $CurrentVersion = $VersionDate.ToString("yyyy.MM.dd.HHmm") - - [System.Console]::WriteLine("##vso[task.setvariable variable=CurrentVersion]$CurrentVersion") - - Write-Host "Setting current version to $CurrentVersion." - - - task: UseDotNet@2 - displayName: Use .NET SDK - inputs: - version: 8.x - - - task: DotNetCoreCLI@2 - displayName: dotnet publish x64 - inputs: - command: publish - publishWebProjects: false - projects: '**/Agent.csproj' - arguments: -c $(BuildConfiguration) -r osx-x64 -o "$(Build.SourcesDirectory)/Agent/bin/publish" /p:Version=$(CurrentVersion) /p:FileVersion=$(CurrentVersion) - zipAfterPublish: false - modifyOutputPath: false - - - task: PowerShell@2 - displayName: PowerShell Script - inputs: - targetType: inline - script: | - Compress-Archive -Path "$(Build.SourcesDirectory)/Agent/bin/publish/*" -DestinationPath "$(Build.SourcesDirectory)/Agent/bin/Remotely-MacOS-x64.zip" -Force - - - task: PublishPipelineArtifact@1 - displayName: Publish macOS x64 Agent - inputs: - path: $(Build.SourcesDirectory)/Agent/bin/Remotely-MacOS-x64.zip - artifactName: Mac-x64-Agent - - job: Windows_Build displayName: Windows Build timeoutInMinutes: 360 @@ -90,12 +29,6 @@ jobs: - task: VisualStudioTestPlatformInstaller@1 displayName: Visual Studio Test Platform Installer - - task: DownloadPipelineArtifact@2 - displayName: Download macOS x64 Agent - inputs: - artifact: Mac-x64-Agent - path: $(Build.SourcesDirectory)\Server\wwwroot\Content\ - - task: PowerShell@2 displayName: Add CurrentVersion Variable inputs: @@ -112,12 +45,6 @@ jobs: - task: NuGetToolInstaller@1 displayName: 'Use NuGet' - - task: NuGetCommand@2 - displayName: NuGet restore Agent.Installer.Win - inputs: - solution: Agent.Installer.Win/packages.config - packagesDirectory: $(Build.SourcesDirectory)\packages - - task: UseDotNet@2 displayName: Use .NET SDK inputs: diff --git a/Utilities/Publish.ps1 b/Utilities/Publish.ps1 index db949e9b8..3cdc3bb19 100644 --- a/Utilities/Publish.ps1 +++ b/Utilities/Publish.ps1 @@ -102,8 +102,8 @@ if ((Test-Path -Path "$Root\Agent\bin\publish\linux-x64") -eq $true) { dotnet publish /p:Version=$CurrentVersion /p:FileVersion=$CurrentVersion --runtime win-x64 --self-contained --configuration Release --output "$Root\Agent\bin\publish\win-x64" "$Root\Agent" dotnet publish /p:Version=$CurrentVersion /p:FileVersion=$CurrentVersion --runtime linux-x64 --self-contained --configuration Release --output "$Root\Agent\bin\publish\linux-x64" "$Root\Agent" dotnet publish /p:Version=$CurrentVersion /p:FileVersion=$CurrentVersion --runtime win-x86 --self-contained --configuration Release --output "$Root\Agent\bin\publish\win-x86" "$Root\Agent" -#dotnet publish /p:Version=$CurrentVersion /p:FileVersion=$CurrentVersion --runtime osx-x64 --self-contained --configuration Release --output "$Root\Agent\bin\publish\osx-x64" "$Root\Agent" -#dotnet publish /p:Version=$CurrentVersion /p:FileVersion=$CurrentVersion --runtime osx-arm64 --self-contained --configuration Release --output "$Root\Agent\bin\publish\osx-arm64" "$Root\Agent" +dotnet publish /p:Version=$CurrentVersion /p:FileVersion=$CurrentVersion --runtime osx-x64 --self-contained --configuration Release --output "$Root\Agent\bin\publish\osx-x64" "$Root\Agent" +dotnet publish /p:Version=$CurrentVersion /p:FileVersion=$CurrentVersion --runtime osx-arm64 --self-contained --configuration Release --output "$Root\Agent\bin\publish\osx-arm64" "$Root\Agent" New-Item -Path "$Root\Agent\bin\publish\win-x64\Desktop\" -ItemType Directory -Force New-Item -Path "$Root\Agent\bin\publish\win-x86\Desktop\" -ItemType Directory -Force From a68b3e95bc21c1e2d5fb654f337380d609b06e2a Mon Sep 17 00:00:00 2001 From: Jared Goodwin Date: Wed, 5 Jun 2024 07:55:41 -0700 Subject: [PATCH 12/13] Update Release Build.yml for Azure Pipelines --- .azure-pipelines/Release Build.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.azure-pipelines/Release Build.yml b/.azure-pipelines/Release Build.yml index 2de353e2e..abc064506 100644 --- a/.azure-pipelines/Release Build.yml +++ b/.azure-pipelines/Release Build.yml @@ -8,7 +8,6 @@ jobs: - job: Windows_Build displayName: Windows Build timeoutInMinutes: 360 - dependsOn: Mac_Build pool: vmImage: windows-latest From f8af91486b90ddfa042934257cee5518de20dea7 Mon Sep 17 00:00:00 2001 From: Jared Goodwin Date: Wed, 5 Jun 2024 11:58:00 -0700 Subject: [PATCH 13/13] Changed toast text for uir. --- Server/Components/Pages/Deploy.razor | 31 +++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/Server/Components/Pages/Deploy.razor b/Server/Components/Pages/Deploy.razor index 0e8b3a88f..4971bf410 100644 --- a/Server/Components/Pages/Deploy.razor +++ b/Server/Components/Pages/Deploy.razor @@ -31,7 +31,7 @@
-
@@ -44,7 +44,7 @@
-
@@ -57,7 +57,7 @@
-
@@ -116,7 +116,7 @@
-
@@ -161,7 +161,7 @@
-
@@ -207,11 +207,11 @@ await base.OnInitializedAsync(); } - private async Task CopyToClipboard(string text) + private async Task CopyScriptToClipboard(string script) { try { - var result = await JsInterop.SetClipboardText(text); + var result = await JsInterop.SetClipboardText(script); if (result) { Toasts.ShowToast2("Script copied to clipboard", ToastType.Success); @@ -225,6 +225,23 @@ Toasts.ShowToast2("Failed to set clipboard content", ToastType.Error); } + private async Task CopyUriToClipboard(string uri) + { + try + { + var result = await JsInterop.SetClipboardText(uri); + if (result) + { + Toasts.ShowToast2("URI copied to clipboard", ToastType.Success); + return; + } + } + catch (Exception ex) + { + Logger.LogError(ex, "Error while copying URI to clipboard."); + } + Toasts.ShowToast2("Failed to set clipboard content", ToastType.Error); + } private string GetLinuxScript(string platformId) {