From d82da3dea9ee316bbbea11e311561205754cdcd0 Mon Sep 17 00:00:00 2001 From: Jared Goodwin Date: Thu, 23 Jan 2020 17:43:48 -0800 Subject: [PATCH] Agents auto-update via downloaded script. Pauses for random seconds based on total device count before downloading (to even out the bandwidth). --- Agent/Program.cs | 5 - Agent/Services/DeviceSocket.cs | 2 +- Agent/Services/Updater.cs | 159 +++--------------- Server/API/AgentUpdateController.cs | 41 +++++ Server/API/ClientDownloadsController.cs | 25 ++- Server/API/CoreVersionController.cs | 1 + Server/Pages/Agents.cshtml | 2 +- Server/Services/DataService.cs | 8 + Server/wwwroot/Downloads/Install-Linux-x64.sh | 10 +- Shared/Services/OSUtils.cs | 10 +- 10 files changed, 108 insertions(+), 155 deletions(-) create mode 100644 Server/API/AgentUpdateController.cs diff --git a/Agent/Program.cs b/Agent/Program.cs index 9822334c..0f50f448 100644 --- a/Agent/Program.cs +++ b/Agent/Program.cs @@ -108,11 +108,6 @@ namespace Remotely.Agent SetWorkingDirectory(); var argDict = ProcessArgs(args); - if (argDict.ContainsKey("update")) - { - Services.GetRequiredService().CoreUpdate(); - } - if (!IsDebug && OSUtils.IsWindows) { _ = Task.Run(() => diff --git a/Agent/Services/DeviceSocket.cs b/Agent/Services/DeviceSocket.cs index 5f4081c8..a4b1be26 100644 --- a/Agent/Services/DeviceSocket.cs +++ b/Agent/Services/DeviceSocket.cs @@ -181,7 +181,7 @@ namespace Remotely.Agent.Services IsServerVerified = true; if (!Program.IsDebug) { - Updater.CheckForCoreUpdates(); + Task.Run(Updater.CheckForUpdates); } } else diff --git a/Agent/Services/Updater.cs b/Agent/Services/Updater.cs index 44a1e3fa..27434999 100644 --- a/Agent/Services/Updater.cs +++ b/Agent/Services/Updater.cs @@ -22,52 +22,46 @@ namespace Remotely.Agent.Services private ConfigService ConfigService { get; } - public void CheckForCoreUpdates() + public async Task CheckForUpdates() { try { var wc = new WebClient(); - var response = new HttpClient().GetAsync(ConfigService.GetConnectionInfo().Host + $"/API/CoreVersion/").Result; + var connectionInfo = ConfigService.GetConnectionInfo(); + var response = new HttpClient().GetAsync(connectionInfo.Host + $"/API/AgentUpdate/CurrentVersion").Result; var latestVersion = response.Content.ReadAsStringAsync().Result; var thisVersion = FileVersionInfo.GetVersionInfo("Remotely_Agent.dll").FileVersion.ToString().Trim(); if (thisVersion != latestVersion) { - Logger.Write($"Service Updater: Downloading update. Current Version: {thisVersion}. Latest Version: {latestVersion}."); - var fileName = OSUtils.CoreZipFileName; - var tempFile = Path.Combine(Path.GetTempPath(), fileName); - var tempFolder = Path.Combine(Path.GetTempPath(), "Remotely_Update"); - if (File.Exists(tempFile)) - { - File.Delete(tempFile); - } - if (Directory.Exists(tempFolder)) - { - Directory.Delete(tempFolder, true); - } - wc.DownloadFile(new Uri(ConfigService.GetConnectionInfo().Host + $"/Downloads/{fileName}"), tempFile); + Logger.Write($"Service Updater: Update found. Current Version: {thisVersion}. Latest Version: {latestVersion}."); - Logger.Write($"Service Updater: Extracting files."); + var updateWindow = int.Parse(wc.DownloadString(connectionInfo.Host + $"/API/AgentUpdate/UpdateWindow")); + var waitTime = new Random().Next(1, updateWindow); + Logger.Write($"Waiting {waitTime} seconds before updating."); + await Task.Delay(TimeSpan.FromSeconds(waitTime)); + Logger.Write($"Service Updater: Downloading installer."); if (OSUtils.IsWindows) { - ZipFile.ExtractToDirectory(tempFile, tempFolder, true); - Logger.Write($"Service Updater: Launching extracted process to perform update."); - var psi = new ProcessStartInfo() - { - FileName = Path.Combine(Path.GetTempPath(), "Remotely_Update", OSUtils.ClientExecutableFileName), - Arguments = "-update true", - Verb = "RunAs" - }; - Process.Start(psi); + var filePath = Path.Combine(Path.GetTempPath(), "RemotelyUpdate.ps1"); + + wc.DownloadFile( + ConfigService.GetConnectionInfo().Host + $"/API/ClientDownloads/{connectionInfo.OrganizationID}/{OSUtils.PlatformString}", + filePath); + + Process.Start("powershell.exe", $"-f \"{filePath}\""); } else if (OSUtils.IsLinux) { - Process.Start("sudo", "apt-get install unzip").WaitForExit(); - Process.Start("sudo", $"unzip -o {tempFile} -d /usr/local/bin/Remotely/").WaitForExit(); - Process.Start("sudo", "chmod +x /usr/local/bin/Remotely/Remotely_Agent").WaitForExit(); - Process.Start("sudo", "chmod +x /usr/local/bin/Remotely/ScreenCast/Remotely_ScreenCast.Linux").WaitForExit(); - Logger.Write($"Service Updater: Update complete. Restarting service."); - Process.Start("sudo", "systemctl restart remotely-agent"); + var filePath = Path.Combine(Path.GetTempPath(), "RemotelyUpdate.sh"); + + wc.DownloadFile( + ConfigService.GetConnectionInfo().Host + $"/API/ClientDownloads/{connectionInfo.OrganizationID}/{OSUtils.PlatformString}", + filePath); + + Process.Start("sudo", $"chmod +x {filePath}").WaitForExit(); + + Process.Start("sudo", $"{filePath} & disown"); } } } @@ -76,108 +70,5 @@ namespace Remotely.Agent.Services Logger.Write(ex); } } - public void CoreUpdate() - { - try - { - Logger.Write("Service Updater: Starting update."); - var ps = PowerShell.Create(); - if (OSUtils.IsWindows) - { - ps.AddScript(@"Get-Service | Where-Object {$_.Name -like ""Remotely_Service""} | Stop-Service -Force"); - ps.Invoke(); - ps.Commands.Clear(); - } - else if (OSUtils.IsLinux) - { - Process.Start("sudo", "systemctl stop remotely-agent"); - } - - - foreach (var proc in Process.GetProcesses().Where(x => - (x.ProcessName.Contains("Remotely_Agent") || - x.ProcessName.Contains("Remotely_ScreenCast") || - x.ProcessName.Contains("Remotely_Desktop")) - && x.Id != Process.GetCurrentProcess().Id)) - { - proc.Kill(); - } - - string targetDir = ""; - - if (OSUtils.IsWindows) - { - targetDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "Remotely"); - } - else if (OSUtils.IsLinux) - { - targetDir = "/usr/local/bin/Remotely"; - } - - Logger.Write("Service Updater: Copying new files."); - - - var fileList = Directory.GetFiles(Path.Combine(Path.GetTempPath(), "Remotely_Update")); - foreach (var file in fileList) - { - try - { - var targetPath = Path.Combine(targetDir, Path.GetFileName(file)); - File.Copy(file, targetPath, true); - } - catch (Exception ex) - { - Logger.Write(ex); - continue; - } - } - - - var subdirList = Directory.GetDirectories(Path.Combine(Path.GetTempPath(), "Remotely_Update")); - - foreach (var subdir in subdirList) - { - try - { - var targetPath = Path.Combine(targetDir, Path.GetFileName(subdir.TrimEnd(Path.DirectorySeparatorChar))); - if (Directory.Exists(targetPath)) - { - Directory.Delete(targetPath, true); - while (Directory.Exists(targetPath)) - { - Thread.Sleep(100); - } - } - Directory.Move(subdir, targetPath); - } - catch (Exception ex) - { - Logger.Write(ex); - continue; - } - - } - Logger.Write("Service Updater: Update completed."); - } - catch (Exception ex) - { - Logger.Write(ex); - } - finally - { - Logger.Write("Service Updater: Starting service."); - if (OSUtils.IsWindows) - { - var ps = PowerShell.Create(); - ps.AddScript("Start-Service -Name \"Remotely_Service\""); - ps.Invoke(); - } - else if (OSUtils.IsLinux) - { - Process.Start("sudo", "systemctl restart remotely-agent").WaitForExit(); - } - Environment.Exit(0); - } - } } } diff --git a/Server/API/AgentUpdateController.cs b/Server/API/AgentUpdateController.cs new file mode 100644 index 00000000..a2cd2cd7 --- /dev/null +++ b/Server/API/AgentUpdateController.cs @@ -0,0 +1,41 @@ +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc; +using Remotely.Server.Services; +using System; +using System.Diagnostics; +using System.IO; +using System.IO.Compression; + +namespace Remotely.Server.API +{ + [Route("api/[controller]")] + public class AgentUpdateController : Controller + { + public AgentUpdateController(IWebHostEnvironment hostingEnv, DataService dataService) + { + this.HostingEnv = hostingEnv; + DataService = dataService; + } + + private IWebHostEnvironment HostingEnv { get; } + private DataService DataService { get; } + + // GET: api/ + [HttpGet("[action]")] + public string CurrentVersion() + { + var filePath = Path.Combine(HostingEnv.ContentRootPath, "CurrentVersion.txt"); + if (!System.IO.File.Exists(filePath)) + { + return "0.0.0.0"; + } + return System.IO.File.ReadAllText(filePath).Trim(); + } + + [HttpGet("[action]")] + public int UpdateWindow() + { + return DataService.GetDeviceCount() * 10; + } + } +} diff --git a/Server/API/ClientDownloadsController.cs b/Server/API/ClientDownloadsController.cs index 2d5f1331..742e473c 100644 --- a/Server/API/ClientDownloadsController.cs +++ b/Server/API/ClientDownloadsController.cs @@ -28,9 +28,20 @@ namespace Remotely.Server.API [Authorize] [HttpGet("{platformID}")] - public ActionResult Get(string platformID) + public async Task Get(string platformID) { var user = DataService.GetUserByName(User.Identity.Name); + return await GetInstallFile(user.OrganizationID, platformID); + } + + [HttpGet("{organizationID}/{platformID}")] + public async Task Get(string organizationID, string platformID) + { + return await GetInstallFile(organizationID, platformID); + } + + private async Task GetInstallFile(string organizationID, string platformID) + { var fileContents = new List(); string fileName; byte[] fileBytes; @@ -41,32 +52,32 @@ namespace Remotely.Server.API { fileName = $"Install-{platformID}.ps1"; - fileContents.AddRange(System.IO.File.ReadAllLines(Path.Combine(HostEnv.WebRootPath, "Downloads", $"{fileName}"))); + fileContents.AddRange(await System.IO.File.ReadAllLinesAsync(Path.Combine(HostEnv.WebRootPath, "Downloads", $"{fileName}"))); var hostIndex = fileContents.IndexOf("[string]$HostName = $null"); var orgIndex = fileContents.IndexOf("[string]$Organization = $null"); fileContents[hostIndex] = $"[string]$HostName = \"{Request.Scheme}://{Request.Host}\""; - fileContents[orgIndex] = $"[string]$Organization = \"{user.Organization.ID}\""; + fileContents[orgIndex] = $"[string]$Organization = \"{organizationID}\""; fileBytes = System.Text.Encoding.UTF8.GetBytes(string.Join(Environment.NewLine, fileContents)); break; } - + case "Linux-x64": { fileName = "Install-Linux-x64.sh"; - fileContents.AddRange(System.IO.File.ReadAllLines(Path.Combine(HostEnv.WebRootPath, "Downloads", $"{fileName}"))); + fileContents.AddRange(await System.IO.File.ReadAllLinesAsync(Path.Combine(HostEnv.WebRootPath, "Downloads", $"{fileName}"))); var hostIndex = fileContents.IndexOf("HostName="); var orgIndex = fileContents.IndexOf("Organization="); fileContents[hostIndex] = $"HostName=\"{Request.Scheme}://{Request.Host}\""; - fileContents[orgIndex] = $"Organization=\"{user.Organization.ID}\""; + fileContents[orgIndex] = $"Organization=\"{organizationID}\""; fileBytes = System.Text.Encoding.UTF8.GetBytes(string.Join("\n", fileContents)); break; } - + default: return BadRequest(); } diff --git a/Server/API/CoreVersionController.cs b/Server/API/CoreVersionController.cs index d49b921e..bab08ac4 100644 --- a/Server/API/CoreVersionController.cs +++ b/Server/API/CoreVersionController.cs @@ -7,6 +7,7 @@ using System.IO.Compression; namespace Remotely.Server.API { + // TODO: Delete after a few versions. [Route("api/[controller]")] public class CoreVersionController : Controller { diff --git a/Server/Pages/Agents.cshtml b/Server/Pages/Agents.cshtml index 1683dd94..e004b7ad 100644 --- a/Server/Pages/Agents.cshtml +++ b/Server/Pages/Agents.cshtml @@ -114,7 +114,7 @@ Local Install:
- sudo [path]/Install-Linux-x64.sh --path [path]/Remotely-Win10-x86.zip + sudo [path]/Install-Linux-x64.sh --path [path]/Remotely-Linux.zip

Uninstall: diff --git a/Server/Services/DataService.cs b/Server/Services/DataService.cs index ebd22f00..aa0cc067 100644 --- a/Server/Services/DataService.cs +++ b/Server/Services/DataService.cs @@ -129,6 +129,7 @@ namespace Remotely.Server.Services RemotelyContext.SaveChanges(); return true; } + public string AddSharedFile(IFormFile file, string organizationID) { var expirationDate = DateTime.Now.AddDays(-AppConfig.DataRetentionInDays); @@ -209,6 +210,7 @@ namespace Remotely.Server.Services RemotelyContext.DeviceGroups.Remove(deviceGroup); RemotelyContext.SaveChanges(); } + public void DeleteInvite(string requesterUserName, string inviteID) { var requester = RemotelyContext.Users @@ -277,6 +279,7 @@ namespace Remotely.Server.Services .Where(x => x.OrganizationID == orgID) .OrderByDescending(x => x.TimeStamp); } + public IEnumerable GetAllDevicesForUser(string userID) { var user = RemotelyContext.Users.FirstOrDefault(x => x.Id == userID); @@ -294,6 +297,7 @@ namespace Remotely.Server.Services .Where(x => x.OrganizationID == orgID) .OrderByDescending(x => x.TimeStamp); } + public ICollection GetAllInviteLinks(string userName) { return RemotelyContext.Users @@ -336,6 +340,10 @@ namespace Remotely.Server.Services return AppConfig.DefaultPrompt; } + public int GetDeviceCount() + { + return RemotelyContext.Devices.Count(); + } public Device GetDeviceForUser(string userID, string deviceID) { var user = RemotelyContext.Users.FirstOrDefault(x => x.Id == userID); diff --git a/Server/wwwroot/Downloads/Install-Linux-x64.sh b/Server/wwwroot/Downloads/Install-Linux-x64.sh index 3802b706..0356e129 100644 --- a/Server/wwwroot/Downloads/Install-Linux-x64.sh +++ b/Server/wwwroot/Downloads/Install-Linux-x64.sh @@ -4,8 +4,6 @@ Organization= GUID=$(cat /proc/sys/kernel/random/uuid) systemctl stop remotely-agent -rm -r -f /usr/local/bin/Remotely -rm -f /etc/systemd/system/remotely-agent.service systemctl daemon-reload if [ "$1" = "--uninstall" ]; then @@ -27,6 +25,14 @@ apt-get -y install libc6-dev apt-get -y install libgdiplus apt-get -y install libxtst-dev apt-get -y install xclip +apt-get -y install jq + +if [ -f "/usr/local/bin/Remotely/ConnectionInfo.json" ]; then + GUID=`cat "/usr/local/bin/Remotely/ConnectionInfo.json" | jq -r '.DeviceID'` +fi + +rm -r -f /usr/local/bin/Remotely +rm -f /etc/systemd/system/remotely-agent.service mkdir -p /usr/local/bin/Remotely/ cd /usr/local/bin/Remotely/ diff --git a/Shared/Services/OSUtils.cs b/Shared/Services/OSUtils.cs index c9f86097..3b522627 100644 --- a/Shared/Services/OSUtils.cs +++ b/Shared/Services/OSUtils.cs @@ -56,7 +56,7 @@ namespace Remotely.Shared.Services } } - public static string CoreZipFileName + public static string PlatformString { get { @@ -64,17 +64,17 @@ namespace Remotely.Shared.Services { if (Environment.Is64BitOperatingSystem) { - return "Remotely-Win10-x64.zip"; + return "Win10-x64"; } else { - return "Remotely-Win10-x86.zip"; + return "Win10-x86"; } } - else if (OSUtils.IsLinux) + else if (IsLinux) { - return "Remotely-Linux.zip"; + return "Linux-x64"; } else {