diff --git a/.azure-pipelines/Release Build.yml b/.azure-pipelines/Release Build.yml new file mode 100644 index 000000000..9479d2575 --- /dev/null +++ b/.azure-pipelines/Release Build.yml @@ -0,0 +1,216 @@ +name: Release Build +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) + + $Year = $VersionDate.Year.ToString() + $Month = $VersionDate.Month.ToString().PadLeft(2, "0") + $Day = $VersionDate.Day.ToString().PadLeft(2, "0") + $Hour = $VersionDate.Hour.ToString().PadLeft(2, "0") + $Minute = $VersionDate.Minute.ToString().PadLeft(2, "0") + $CurrentVersion = "$Year.$Month.$Day.$Hour$Minute" + + [System.Console]::WriteLine("##vso[task.setvariable variable=CurrentVersion]$CurrentVersion") + + Write-Host "Setting current version to $CurrentVersion." + + - task: UseDotNet@2 + displayName: Use .NET Core sdk 6.x + inputs: + version: 6.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 + + 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: VisualStudioTestPlatformInstaller@1 + displayName: Visual Studio Test Platform Installer + + - task: NodeTool@0 + inputs: + versionSource: 'spec' + versionSpec: '6.x' + checkLatest: true + + - 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: + targetType: inline + script: | + $VersionString = git show -s --format=%ci $(Build.SourceVersion) + $VersionDate = [DateTimeOffset]::Parse($VersionString) + + $Year = $VersionDate.Year.ToString() + $Month = $VersionDate.Month.ToString().PadLeft(2, "0") + $Day = $VersionDate.Day.ToString().PadLeft(2, "0") + $Hour = $VersionDate.Hour.ToString().PadLeft(2, "0") + $Minute = $VersionDate.Minute.ToString().PadLeft(2, "0") + $CurrentVersion = "$Year.$Month.$Day.$Hour$Minute" + + [System.Console]::WriteLine("##vso[task.setvariable variable=CurrentVersion]$CurrentVersion") + + Write-Host "Setting current version to $CurrentVersion." + + - 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 Core sdk 6.x + inputs: + version: 6.x + + - task: DotNetCoreCLI@2 + displayName: dotnet restore + inputs: + command: restore + projects: '**/*.csproj' + + - task: DotNetCoreCLI@2 + displayName: dotnet test + inputs: + command: test + projects: Tests\Tests.csproj + + - task: PowerShell@2 + displayName: Create Code Signing Cert + env: + SigningCertBase64: $(CODE_SIGNING_CERT_BASE64) + inputs: + targetType: inline + script: | + if (!$env:SigningCertBase64) { + Write-Host "CODE_SIGNING_CERT_BASE64 variable is empty. Skipping cert creation." + return + } + $CertBytes = [System.Convert]::FromBase64String($env:SigningCertBase64) + [System.IO.File]::WriteAllBytes("$(Build.SourcesDirectory)\Utilities\CodeSigningCert.pfx", $CertBytes) + + - task: PowerShell@2 + displayName: Publish.ps1 + inputs: + filePath: Utilities/Publish.ps1 + arguments: -CertificatePath "$(Build.SourcesDirectory)\Utilities\CodeSigningCert.pfx" -CertificatePassword "$(SIGNING_CERT_PASSWORD)" -CurrentVersion "$(CurrentVersion)" + failOnStderr: true + workingDirectory: Utilities + + # The MSBuild TypeScript task doesn't compiled the TS + # files without this build step before publishing. + - task: DotNetCoreCLI@2 + inputs: + command: 'build' + projects: '$(Build.SourcesDirectory)/Server/Server.csproj' + arguments: '-r linux-x64 -c Release' + + - task: DotNetCoreCLI@2 + displayName: dotnet publish linux-x64 + inputs: + command: publish + publishWebProjects: false + projects: '$(Build.SourcesDirectory)/Server/Server.csproj' + arguments: /p:Version=$(CurrentVersion) /p:FileVersion=$(CurrentVersion) --configuration $(BuildConfiguration) --output $(build.artifactstagingdirectory)\linux-x64 --runtime linux-x64 + + + - task: PowerShell@2 + displayName: Write Version File + inputs: + targetType: inline + script: | + Write-Host "Adding Version to File: $(CurrentVersion)" + # This is used in the Release. + New-Item -ItemType File -Value "$(CurrentVersion)" -Path "$(build.artifactstagingdirectory)\Version.txt" -Force + + - task: PowerShell@2 + displayName: Copy Docker Files + inputs: + targetType: inline + script: | + Copy-Item -Path "$(Build.SourcesDirectory)\Server\Dockerfile" -Destination "$(build.artifactstagingdirectory)\Dockerfile" + Copy-Item -Path "$(Build.SourcesDirectory)\Server\DockerMain.sh" -Destination "$(build.artifactstagingdirectory)\DockerMain.sh" + + - task: PublishBuildArtifacts@1 + displayName: Publish Artifact + condition: succeededOrFailed() + inputs: + PathtoPublish: $(build.artifactstagingdirectory) + ArtifactName: Server + TargetPath: '\\my\share\$(Build.DefinitionName)\$(Build.BuildNumber)' + + - task: PostBuildCleanup@3 + displayName: Clean Agent Directories diff --git a/Agent.Installer.Win/Agent.Installer.Win.csproj b/Agent.Installer.Win/Agent.Installer.Win.csproj index ec6d669c7..de9ce28ba 100644 --- a/Agent.Installer.Win/Agent.Installer.Win.csproj +++ b/Agent.Installer.Win/Agent.Installer.Win.csproj @@ -132,9 +132,11 @@ Models\DeviceSetupOptions.cs + + - + diff --git a/Agent.Installer.Win/Models/EmbeddedServerData.cs b/Agent.Installer.Win/Models/EmbeddedServerData.cs new file mode 100644 index 000000000..b4e768fc8 --- /dev/null +++ b/Agent.Installer.Win/Models/EmbeddedServerData.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using System.Runtime.Serialization.Formatters.Binary; +using System.Text; +using System.Threading.Tasks; + +namespace Remotely.Agent.Installer.Models +{ + [DataContract] + public class EmbeddedServerData + { + public EmbeddedServerData(Uri serverUrl, string organizationId) + { + ServerUrl = serverUrl; + OrganizationId = organizationId; + } + + private EmbeddedServerData() { } + + public static EmbeddedServerData Empty { get; } = new EmbeddedServerData(); + + [DataMember] + public string OrganizationId { get; } + + [DataMember] + public Uri ServerUrl { get; } + } +} diff --git a/Agent.Installer.Win/Properties/AssemblyInfo.cs b/Agent.Installer.Win/Properties/AssemblyInfo.cs index d8aa9c65e..08bc29a9b 100644 --- a/Agent.Installer.Win/Properties/AssemblyInfo.cs +++ b/Agent.Installer.Win/Properties/AssemblyInfo.cs @@ -8,9 +8,9 @@ [assembly: AssemblyTitle("Remotely Installer")] [assembly: AssemblyDescription("An installer for the Remotely service, which provides unattended remote access and remote scripting.")] [assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Translucency Software")] +[assembly: AssemblyCompany("Immense Networks")] [assembly: AssemblyProduct("Remotely Installer")] -[assembly: AssemblyCopyright("Copyright © 2020 Translucency Software")] +[assembly: AssemblyCopyright("Copyright © 2020 Immense Networks")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] diff --git a/Agent.Installer.Win/Services/EmbeddedServerDataReader.cs b/Agent.Installer.Win/Services/EmbeddedServerDataReader.cs new file mode 100644 index 000000000..87a604a1a --- /dev/null +++ b/Agent.Installer.Win/Services/EmbeddedServerDataReader.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.Serialization.Json; +using System.Text; +using System.Threading.Tasks; +using System.Web.Script.Serialization; +using Remotely.Agent.Installer.Models; +using Remotely.Agent.Installer.Win.Utilities; +using Remotely.Shared; + +namespace Remotely.Agent.Installer.Win.Services +{ + internal class EmbeddedServerDataReader + { + private readonly JavaScriptSerializer _serializer = new JavaScriptSerializer(); + + public Task TryGetEmbeddedData(string filePath) + { + try + { + if (!File.Exists(filePath)) + { + throw new Exception($"File path does not exist: {filePath}"); + } + + using (var fs = File.Open(filePath, FileMode.Open, FileAccess.Read, FileShare.Read)) + { + var result = SearchBuffer(fs, AppConstants.EmbeddedImmySignature); + if (result == -1) + { + throw new Exception("Signature not found in file buffer."); + } + + fs.Seek(result + AppConstants.EmbeddedImmySignature.Length, SeekOrigin.Begin); + using (var reader = new BinaryReader(fs)) + { + var serializedData = reader.ReadString(); + return Task.FromResult(_serializer.Deserialize(serializedData)); + } + } + } + catch (Exception ex) + { + Logger.Write(ex); + return Task.FromResult(EmbeddedServerData.Empty); + } + } + + private long SearchBuffer(FileStream fileStream, byte[] matchPattern) + { + var matchSize = matchPattern.Length; + var limit = fileStream.Length - matchSize; + + for (var i = 0; i <= limit; i++) + { + var k = 0; + + for (; k < matchSize; k++) + { + if (matchPattern[k] != fileStream.ReadByte()) + { + break; + } + } + + if (k == matchSize) + { + return fileStream.Position - matchSize; + } + } + return -1; + } + } +} diff --git a/Agent.Installer.Win/Services/InstallerService.cs b/Agent.Installer.Win/Services/InstallerService.cs index db82de881..fcb27c9eb 100644 --- a/Agent.Installer.Win/Services/InstallerService.cs +++ b/Agent.Installer.Win/Services/InstallerService.cs @@ -22,12 +22,13 @@ namespace Remotely.Agent.Installer.Win.Services { public class InstallerService { + private readonly string _installPath = Path.Combine(Path.GetPathRoot(Environment.SystemDirectory), "Program Files", "Remotely"); + private readonly string _platform = Environment.Is64BitOperatingSystem ? "x64" : "x86"; + private readonly JavaScriptSerializer _serializer = new JavaScriptSerializer(); + public event EventHandler ProgressMessageChanged; public event EventHandler ProgressValueChanged; - private string InstallPath => Path.Combine(Path.GetPathRoot(Environment.SystemDirectory), "Program Files", "Remotely"); - private string Platform => Environment.Is64BitOperatingSystem ? "x64" : "x86"; - private JavaScriptSerializer Serializer { get; } = new JavaScriptSerializer(); public async Task Install(string serverUrl, string organizationId, string deviceGroup, @@ -55,9 +56,9 @@ public async Task Install(string serverUrl, await DownloadRemotelyAgent(serverUrl); - FileIO.WriteAllText(Path.Combine(InstallPath, "ConnectionInfo.json"), Serializer.Serialize(connectionInfo)); + FileIO.WriteAllText(Path.Combine(_installPath, "ConnectionInfo.json"), _serializer.Serialize(connectionInfo)); - FileIO.Copy(Assembly.GetExecutingAssembly().Location, Path.Combine(InstallPath, "Remotely_Installer.exe")); + FileIO.Copy(Assembly.GetExecutingAssembly().Location, Path.Combine(_installPath, "Remotely_Installer.exe")); await CreateDeviceOnServer(connectionInfo.DeviceID, serverUrl, deviceGroup, deviceAlias, organizationId); @@ -97,7 +98,7 @@ public async Task Uninstall() ProgressMessageChanged?.Invoke(this, "Deleting files."); ClearInstallDirectory(); - ProcessEx.StartHidden("cmd.exe", $"/c timeout 5 & rd /s /q \"{InstallPath}\""); + ProcessEx.StartHidden("cmd.exe", $"/c timeout 5 & rd /s /q \"{_installPath}\""); ProcessEx.StartHidden("netsh", "advfirewall firewall delete rule name=\"Remotely Desktop Unattended\"").WaitForExit(); @@ -114,14 +115,14 @@ public async Task Uninstall() private void AddFirewallRule() { - var desktopExePath = Path.Combine(InstallPath, "Desktop", "Remotely_Desktop.exe"); + var desktopExePath = Path.Combine(_installPath, "Desktop", "Remotely_Desktop.exe"); ProcessEx.StartHidden("netsh", "advfirewall firewall delete rule name=\"Remotely Desktop Unattended\"").WaitForExit(); ProcessEx.StartHidden("netsh", $"advfirewall firewall add rule name=\"Remotely Desktop Unattended\" program=\"{desktopExePath}\" protocol=any dir=in enable=yes action=allow description=\"The agent that allows screen sharing and remote control for Remotely.\"").WaitForExit(); } private void BackupDirectory() { - if (Directory.Exists(InstallPath)) + if (Directory.Exists(_installPath)) { Logger.Write("Backing up current installation."); ProgressMessageChanged?.Invoke(this, "Backing up current installation."); @@ -130,7 +131,7 @@ private void BackupDirectory() { FileIO.Delete(backupPath); } - ZipFile.CreateFromDirectory(InstallPath, backupPath, CompressionLevel.Fastest, false); + ZipFile.CreateFromDirectory(_installPath, backupPath, CompressionLevel.Fastest, false); } } @@ -148,9 +149,9 @@ private bool CheckIsAdministrator() private void ClearInstallDirectory() { - if (Directory.Exists(InstallPath)) + if (Directory.Exists(_installPath)) { - foreach (var entry in Directory.GetFileSystemEntries(InstallPath)) + foreach (var entry in Directory.GetFileSystemEntries(_installPath)) { try { @@ -196,7 +197,7 @@ private async Task CreateDeviceOnServer(string deviceUuid, using (var rs = await wr.GetRequestStreamAsync()) using (var sw = new StreamWriter(rs)) { - await sw.WriteAsync(Serializer.Serialize(setupOptions)); + await sw.WriteAsync(_serializer.Serialize(setupOptions)); } using (var response = await wr.GetResponseAsync() as HttpWebResponse) { @@ -218,10 +219,10 @@ private async Task CreateDeviceOnServer(string deviceUuid, private void CreateSupportShortcut(string serverUrl, string deviceUuid, bool createSupportShortcut) { var shell = new WshShell(); - var shortcutLocation = Path.Combine(InstallPath, "Get Support.lnk"); + var shortcutLocation = Path.Combine(_installPath, "Get Support.lnk"); var shortcut = (IWshShortcut)shell.CreateShortcut(shortcutLocation); shortcut.Description = "Get IT support"; - shortcut.IconLocation = Path.Combine(InstallPath, "Remotely_Agent.exe"); + shortcut.IconLocation = Path.Combine(_installPath, "Remotely_Agent.exe"); shortcut.TargetPath = serverUrl.TrimEnd('/') + $"/GetSupport?deviceID={deviceUuid}"; shortcut.Save(); @@ -234,19 +235,19 @@ private void CreateSupportShortcut(string serverUrl, string deviceUuid, bool cre } private void CreateUninstallKey() { - var version = FileVersionInfo.GetVersionInfo(Path.Combine(InstallPath, "Remotely_Agent.exe")); + var version = FileVersionInfo.GetVersionInfo(Path.Combine(_installPath, "Remotely_Agent.exe")); var baseKey = GetRegistryBaseKey(); var remotelyKey = baseKey.CreateSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Remotely", true); - remotelyKey.SetValue("DisplayIcon", Path.Combine(InstallPath, "Remotely_Agent.exe")); + remotelyKey.SetValue("DisplayIcon", Path.Combine(_installPath, "Remotely_Agent.exe")); remotelyKey.SetValue("DisplayName", "Remotely"); remotelyKey.SetValue("DisplayVersion", version.FileVersion); remotelyKey.SetValue("InstallDate", DateTime.Now.ToShortDateString()); - remotelyKey.SetValue("Publisher", "Translucency Software"); + remotelyKey.SetValue("Publisher", "Immense Networks"); remotelyKey.SetValue("VersionMajor", version.FileMajorPart.ToString(), RegistryValueKind.DWord); remotelyKey.SetValue("VersionMinor", version.FileMinorPart.ToString(), RegistryValueKind.DWord); - remotelyKey.SetValue("UninstallString", Path.Combine(InstallPath, "Remotely_Installer.exe -uninstall -quiet")); - remotelyKey.SetValue("QuietUninstallString", Path.Combine(InstallPath, "Remotely_Installer.exe -uninstall -quiet")); + remotelyKey.SetValue("UninstallString", Path.Combine(_installPath, "Remotely_Installer.exe -uninstall -quiet")); + remotelyKey.SetValue("QuietUninstallString", Path.Combine(_installPath, "Remotely_Installer.exe -uninstall -quiet")); } private async Task DownloadRemotelyAgent(string serverUrl) @@ -268,7 +269,7 @@ private async Task DownloadRemotelyAgent(string serverUrl) ProgressValueChanged?.Invoke(this, args.ProgressPercentage); }; - await client.DownloadFileTaskAsync($"{serverUrl}/Content/Remotely-Win10-{Platform}.zip", targetFile); + await client.DownloadFileTaskAsync($"{serverUrl}/Content/Remotely-Win10-{_platform}.zip", targetFile); } } @@ -281,17 +282,17 @@ private async Task DownloadRemotelyAgent(string serverUrl) Directory.Delete(tempDir, true); } - Directory.CreateDirectory(InstallPath); - while (!Directory.Exists(InstallPath)) + Directory.CreateDirectory(_installPath); + while (!Directory.Exists(_installPath)) { await Task.Delay(10); } - var wr = WebRequest.CreateHttp($"{serverUrl}/Content/Remotely-Win10-{Platform}.zip"); + var wr = WebRequest.CreateHttp($"{serverUrl}/Content/Remotely-Win10-{_platform}.zip"); wr.Method = "Head"; using (var response = (HttpWebResponse)await wr.GetResponseAsync()) { - FileIO.WriteAllText(Path.Combine(InstallPath, "etag.txt"), response.Headers["ETag"]); + FileIO.WriteAllText(Path.Combine(_installPath, "etag.txt"), response.Headers["ETag"]); } ZipFile.ExtractToDirectory(targetFile, tempDir); @@ -304,11 +305,11 @@ private async Task DownloadRemotelyAgent(string serverUrl) var entry = fileSystemEntries[i]; if (FileIO.Exists(entry)) { - FileIO.Copy(entry, Path.Combine(InstallPath, Path.GetFileName(entry)), true); + FileIO.Copy(entry, Path.Combine(_installPath, Path.GetFileName(entry)), true); } else if (Directory.Exists(entry)) { - FileSystem.CopyDirectory(entry, Path.Combine(InstallPath, new DirectoryInfo(entry).Name), true); + FileSystem.CopyDirectory(entry, Path.Combine(_installPath, new DirectoryInfo(entry).Name), true); } await Task.Delay(1); } @@ -323,10 +324,10 @@ private async Task DownloadRemotelyAgent(string serverUrl) private ConnectionInfo GetConnectionInfo(string organizationId, string serverUrl, string deviceUuid) { ConnectionInfo connectionInfo; - var connectionInfoPath = Path.Combine(InstallPath, "ConnectionInfo.json"); + var connectionInfoPath = Path.Combine(_installPath, "ConnectionInfo.json"); if (FileIO.Exists(connectionInfoPath)) { - connectionInfo = Serializer.Deserialize(FileIO.ReadAllText(connectionInfoPath)); + connectionInfo = _serializer.Deserialize(FileIO.ReadAllText(connectionInfoPath)); connectionInfo.ServerVerificationToken = null; } else @@ -370,7 +371,7 @@ private void InstallService() var serv = ServiceController.GetServices().FirstOrDefault(ser => ser.ServiceName == "Remotely_Service"); if (serv == null) { - var command = new string[] { "/assemblypath=" + Path.Combine(InstallPath, "Remotely_Agent.exe") }; + var command = new string[] { "/assemblypath=" + Path.Combine(_installPath, "Remotely_Agent.exe") }; var context = new InstallContext("", command); var serviceInstaller = new ServiceInstaller() { @@ -406,7 +407,7 @@ private void RestoreBackup() { Logger.Write("Restoring backup."); ClearInstallDirectory(); - ZipFile.ExtractToDirectory(backupPath, InstallPath); + ZipFile.ExtractToDirectory(backupPath, _installPath); var serv = ServiceController.GetServices().FirstOrDefault(ser => ser.ServiceName == "Remotely_Service"); if (serv?.Status != ServiceControllerStatus.Running) { diff --git a/Agent.Installer.Win/Services/Executor.cs b/Agent.Installer.Win/Services/RelayCommand.cs similarity index 52% rename from Agent.Installer.Win/Services/Executor.cs rename to Agent.Installer.Win/Services/RelayCommand.cs index 33d39bf2e..eb14f35d7 100644 --- a/Agent.Installer.Win/Services/Executor.cs +++ b/Agent.Installer.Win/Services/RelayCommand.cs @@ -3,12 +3,16 @@ namespace Remotely.Agent.Installer.Win.Services { - public class Executor : ICommand + public class RelayCommand : ICommand { - public Executor(Action executeAction, Predicate isExecutable = null) + private readonly Action _action; + + private readonly Predicate _canExecute; + + public RelayCommand(Action action, Predicate canExecute = null) { - ExecuteAction = executeAction; - IsExecutable = isExecutable; + _action = action; + _canExecute = canExecute; } public event EventHandler CanExecuteChanged @@ -17,21 +21,18 @@ public event EventHandler CanExecuteChanged remove { CommandManager.RequerySuggested -= value; } } - private Action ExecuteAction { get; set; } - - private Predicate IsExecutable { get; set; } public bool CanExecute(object parameter) { - if (IsExecutable == null) + if (_canExecute is null) { return true; } - return IsExecutable.Invoke(parameter); + return _canExecute.Invoke(parameter); } public void Execute(object parameter) { - ExecuteAction.Invoke(parameter); + _action?.Invoke(parameter); } } } diff --git a/Agent.Installer.Win/Utilities/Logger.cs b/Agent.Installer.Win/Utilities/Logger.cs index cc17f4ae3..b983f8f9c 100644 --- a/Agent.Installer.Win/Utilities/Logger.cs +++ b/Agent.Installer.Win/Utilities/Logger.cs @@ -6,16 +6,17 @@ namespace Remotely.Agent.Installer.Win.Utilities { public class Logger { - private static string LogPath => Path.Combine(Path.GetTempPath(), "Remotely_Installer.log"); - private static object WriteLock { get; } = new object(); + private static readonly string _logPath = Path.Combine(Path.GetTempPath(), "Remotely", "Installer.log"); + private static readonly object _writeLock = new object(); + public static void Debug(string message) { - lock (WriteLock) + lock (_writeLock) { #if DEBUG CheckLogFileExists(); - File.AppendAllText(LogPath, $"{DateTimeOffset.Now:yyyy-MM-dd HH:mm:ss.fff}\t[Debug]\t{message}{Environment.NewLine}"); + File.AppendAllText(_logPath, $"{DateTimeOffset.Now:yyyy-MM-dd HH:mm:ss.fff}\t[Debug]\t{message}{Environment.NewLine}"); #endif System.Diagnostics.Debug.WriteLine(message); @@ -27,10 +28,10 @@ public static void Write(string message) { try { - lock (WriteLock) + lock (_writeLock) { CheckLogFileExists(); - File.AppendAllText(LogPath, $"{DateTimeOffset.Now:yyyy-MM-dd HH:mm:ss.fff}\t[Info]\t{message}{Environment.NewLine}"); + File.AppendAllText(_logPath, $"{DateTimeOffset.Now:yyyy-MM-dd HH:mm:ss.fff}\t[Info]\t{message}{Environment.NewLine}"); Console.WriteLine(message); } } @@ -39,7 +40,7 @@ public static void Write(string message) public static void Write(Exception ex) { - lock (WriteLock) + lock (_writeLock) { try { @@ -49,7 +50,7 @@ public static void Write(Exception ex) while (exception != null) { - File.AppendAllText(LogPath, $"{DateTimeOffset.Now:yyyy-MM-dd HH:mm:ss.fff}\t[Error]\t{exception?.Message}\t{exception?.StackTrace}\t{exception?.Source}{Environment.NewLine}"); + File.AppendAllText(_logPath, $"{DateTimeOffset.Now:yyyy-MM-dd HH:mm:ss.fff}\t[Error]\t{exception?.Message}\t{exception?.StackTrace}\t{exception?.Source}{Environment.NewLine}"); Console.WriteLine(exception.Message); exception = exception.InnerException; } @@ -60,19 +61,19 @@ public static void Write(Exception ex) private static void CheckLogFileExists() { - if (!File.Exists(LogPath)) + if (!File.Exists(_logPath)) { - File.Create(LogPath).Close(); + File.Create(_logPath).Close(); } - if (File.Exists(LogPath)) + if (File.Exists(_logPath)) { - var fi = new FileInfo(LogPath); + var fi = new FileInfo(_logPath); while (fi.Length > 1000000) { - var content = File.ReadAllLines(LogPath); - File.WriteAllLines(LogPath, content.Skip(10)); - fi = new FileInfo(LogPath); + var content = File.ReadAllLines(_logPath); + File.WriteAllLines(_logPath, content.Skip(10)); + fi = new FileInfo(_logPath); } } } diff --git a/Agent.Installer.Win/ViewModels/MainWindowViewModel.cs b/Agent.Installer.Win/ViewModels/MainWindowViewModel.cs index 076d38b50..4f30f171c 100644 --- a/Agent.Installer.Win/ViewModels/MainWindowViewModel.cs +++ b/Agent.Installer.Win/ViewModels/MainWindowViewModel.cs @@ -18,15 +18,17 @@ using System.Windows.Media.Imaging; using System.Net; using Remotely.Shared; +using Remotely.Agent.Installer.Models; namespace Remotely.Agent.Installer.Win.ViewModels { public class MainWindowViewModel : ViewModelBase { + private readonly InstallerService _installer; + private readonly EmbeddedServerDataReader _embeddedDataReader; private BrandingInfo _brandingInfo; private bool _createSupportShortcut; private string _headerMessage = "Install the service."; - private bool _isReadyState = true; private bool _isServiceInstalled; @@ -34,16 +36,17 @@ public class MainWindowViewModel : ViewModelBase private int _progress; - private string _serverUrl = AppConstants.ServerUrl; + private string _serverUrl = string.Empty; private string _statusMessage; public MainWindowViewModel() { - Installer = new InstallerService(); + _installer = new InstallerService(); + _embeddedDataReader = new EmbeddedServerDataReader(); CopyCommandLineArgs(); - ExtractDeviceInitInfo().Wait(); + ExtractEmbeddedServerData().Wait(); AddExistingConnectionInfo(); } @@ -77,7 +80,7 @@ public string HeaderMessage public BitmapImage Icon { get; set; } public string InstallButtonText => IsServiceMissing ? "Install" : "Reinstall"; - public ICommand InstallCommand => new Executor(async (param) => { await Install(); }); + public ICommand InstallCommand => new RelayCommand(async (param) => { await Install(); }); public bool IsProgressVisible => Progress > 0; @@ -115,7 +118,7 @@ public ICommand OpenLogsCommand { get { - return new Executor(param => + return new RelayCommand(param => { var logPath = Path.Combine(Path.GetTempPath(), "Remotely_Installer.log"); if (File.Exists(logPath)) @@ -188,19 +191,18 @@ public string StatusMessage public SolidColorBrush TitleBackgroundColor { get; set; } public SolidColorBrush TitleButtonForegroundColor { get; set; } public SolidColorBrush TitleForegroundColor { get; set; } - public ICommand UninstallCommand => new Executor(async (param) => { await Uninstall(); }); + public ICommand UninstallCommand => new RelayCommand(async (param) => { await Uninstall(); }); private string DeviceAlias { get; set; } private string DeviceGroup { get; set; } private string DeviceUuid { get; set; } - private InstallerService Installer { get; } public async Task Init() { - Installer.ProgressMessageChanged += (sender, arg) => + _installer.ProgressMessageChanged += (sender, arg) => { StatusMessage = arg; }; - Installer.ProgressValueChanged += (sender, arg) => + _installer.ProgressValueChanged += (sender, arg) => { Progress = arg; }; @@ -237,10 +239,10 @@ private void AddExistingConnectionInfo() try { var connectionInfoPath = Path.Combine( - Path.GetPathRoot(Environment.SystemDirectory), - "Program Files", - "Remotely", - "ConnectionInfo.json"); + Path.GetPathRoot(Environment.SystemDirectory), + "Program Files", + "Remotely", + "ConnectionInfo.json"); if (File.Exists(connectionInfoPath)) { @@ -375,46 +377,31 @@ private void CopyCommandLineArgs() } } - private async Task ExtractDeviceInitInfo() + private async Task ExtractEmbeddedServerData() { try { - var fileName = Path.GetFileNameWithoutExtension(Assembly.GetExecutingAssembly().Location); - var codeLength = AppConstants.RelayCodeLength + 2; + var filePath = Process.GetCurrentProcess()?.MainModule?.FileName; - for (var i = 0; i < fileName.Length; i++) + if (string.IsNullOrWhiteSpace(filePath)) { - var guid = string.Join("", fileName.Skip(i).Take(36)); - if (Guid.TryParse(guid, out _)) - { - OrganizationID = guid; - break; - } - + Logger.Write("Failed to retrieve executing file name."); + return; + } - var codeSection = string.Join("", fileName.Skip(i).Take(codeLength)); + var embeddedData = await _embeddedDataReader.TryGetEmbeddedData(filePath); - if (codeSection.StartsWith("[") && - codeSection.EndsWith("]") && - !string.IsNullOrWhiteSpace(ServerUrl)) - { - var relayCode = codeSection.Substring(1, 4); - using (var httpClient = new HttpClient()) - { - var response = await httpClient.GetAsync($"{ServerUrl.TrimEnd('/')}/api/relay/{relayCode}").ConfigureAwait(false); - if (response.IsSuccessStatusCode) - { - var organizationId = await response.Content.ReadAsStringAsync(); - OrganizationID = organizationId; - break; - } - } - } + if (embeddedData == EmbeddedServerData.Empty) + { + Logger.Write("Embedded server data is empty. Aborting."); + return; } - if (!string.IsNullOrWhiteSpace(OrganizationID) && - !string.IsNullOrWhiteSpace(ServerUrl)) + OrganizationID = embeddedData.OrganizationId; + ServerUrl = embeddedData.ServerUrl.AbsoluteUri; + + if (!string.IsNullOrWhiteSpace(ServerUrl)) { using (var httpClient = new HttpClient()) { @@ -424,7 +411,7 @@ private async Task ExtractDeviceInitInfo() { var responseString = await response.Content.ReadAsStringAsync(); _brandingInfo = serializer.Deserialize(responseString); - + } } } @@ -472,7 +459,7 @@ private async Task Install() HeaderMessage = "Installing Remotely..."; - if (await Installer.Install(ServerUrl, OrganizationID, DeviceGroup, DeviceAlias, DeviceUuid, CreateSupportShortcut)) + if (await _installer.Install(ServerUrl, OrganizationID, DeviceGroup, DeviceAlias, DeviceUuid, CreateSupportShortcut)) { IsServiceInstalled = true; Progress = 0; @@ -508,7 +495,7 @@ private async Task Uninstall() HeaderMessage = "Uninstalling Remotely..."; - if (await Installer.Uninstall()) + if (await _installer.Uninstall()) { IsServiceInstalled = false; Progress = 0; diff --git a/Agent/Agent.csproj b/Agent/Agent.csproj index 97a6399fb..3f3a9ec4a 100644 --- a/Agent/Agent.csproj +++ b/Agent/Agent.csproj @@ -5,11 +5,11 @@ net6.0 true false - Copyright © 2021 Translucency Software + Copyright © 2023 Immense Networks Background service that maintains a connection to the Remotely server. The service is used for remote support and maintenance by this computer's administrators. Jared Goodwin Remotely Agent - Translucency Software + Immense Networks 1.0.0.0 https://remotely.one AnyCPU;x86;x64 diff --git a/Desktop.Linux/Desktop.Linux.csproj b/Desktop.Linux/Desktop.Linux.csproj index 0942d1652..aa7d0a9d7 100644 --- a/Desktop.Linux/Desktop.Linux.csproj +++ b/Desktop.Linux/Desktop.Linux.csproj @@ -26,10 +26,10 @@ true Remotely Desktop Jared Goodwin - Translucency Software + Immense Networks Remotely Desktop Desktop client for allowing your IT admin to provide remote support. - Copyright © 2021 Translucency Software + Copyright © 2023 Immense Networks https://remotely.one diff --git a/Desktop.Linux/Program.cs b/Desktop.Linux/Program.cs index 140eba13a..8079f961d 100644 --- a/Desktop.Linux/Program.cs +++ b/Desktop.Linux/Program.cs @@ -12,6 +12,20 @@ using Immense.RemoteControl.Desktop.Shared.Enums; using Immense.RemoteControl.Desktop.UI.Services; using Remotely.Shared; +using System.Diagnostics; + +var logger = new FileLogger("Program.cs"); +var filePath = Process.GetCurrentProcess()?.MainModule?.FileName; +var serverUrl = Debugger.IsAttached ? "https://localhost:5001" : string.Empty; +var getEmbeddedResult = await EmbeddedServerDataSearcher.Instance.TryGetEmbeddedData(filePath); +if (getEmbeddedResult.IsSuccess) +{ + serverUrl = getEmbeddedResult.Value.ServerUrl.AbsoluteUri; +} +else +{ + logger.LogWarning(getEmbeddedResult.Exception, "Failed to extract embedded server data."); +} var provider = await Startup.UseRemoteControlClient( args, @@ -30,8 +44,9 @@ }); services.AddSingleton(); + services.AddSingleton(); }, - async services => + services => { var appState = services.GetRequiredService(); if (appState.ArgDict.TryGetValue("org-id", out var orgId)) @@ -39,15 +54,9 @@ var orgIdProvider = services.GetRequiredService(); orgIdProvider.OrganizationId = orgId; } - - var brandingProvider = services.GetRequiredService(); - if (brandingProvider is BrandingProvider branding) - { - await branding.TrySetFromApi(); - } - + return Task.CompletedTask; }, - AppConstants.ServerUrl); + serverUrl); Console.WriteLine("Press Ctrl + C to exit."); diff --git a/Desktop.Shared/Services/BrandingProvider.cs b/Desktop.Shared/Services/BrandingProvider.cs index de973a5d9..ab432c188 100644 --- a/Desktop.Shared/Services/BrandingProvider.cs +++ b/Desktop.Shared/Services/BrandingProvider.cs @@ -1,9 +1,11 @@ using Immense.RemoteControl.Desktop.Shared.Abstractions; using Immense.RemoteControl.Desktop.Shared.Services; using Immense.RemoteControl.Shared.Models; +using Microsoft.Extensions.Logging; using Remotely.Shared; using Remotely.Shared.Enums; using Remotely.Shared.Models; +using Remotely.Shared.Services; using Remotely.Shared.Utilities; using System; using System.Collections.Generic; @@ -21,26 +23,47 @@ public class BrandingProvider : IBrandingProvider { private readonly IAppState _appState; private readonly IOrganizationIdProvider _orgIdProvider; + private readonly IEmbeddedServerDataSearcher _embeddedDataSearcher; + private readonly ILogger _logger; private BrandingInfoBase _brandingInfo = new() { Product = "Remote Control" }; - public BrandingProvider(IAppState appState, IOrganizationIdProvider orgIdProvider) + public BrandingProvider( + IAppState appState, + IOrganizationIdProvider orgIdProvider, + IEmbeddedServerDataSearcher embeddedServerDataSearcher, + ILogger logger) { _appState = appState; _orgIdProvider = orgIdProvider; - - using var mrs = typeof(BrandingProvider).Assembly.GetManifestResourceStream("Desktop.Shared.Assets.Remotely_Icon.png"); - using var ms = new MemoryStream(); - mrs!.CopyTo(ms); - - _brandingInfo.Icon = ms.ToArray(); + _embeddedDataSearcher = embeddedServerDataSearcher; + _logger = logger; } - public Task GetBrandingInfo() + public async Task GetBrandingInfo() { - return Task.FromResult(_brandingInfo); + var result = await TryGetBrandingInfo(); + + if (result.IsSuccess) + { + _brandingInfo = result.Value; + } + else + { + _logger.LogWarning(result.Exception, "Failed to extract embedded service data."); + } + + if (!_brandingInfo.Icon.Any()) + { + using var mrs = typeof(BrandingProvider).Assembly.GetManifestResourceStream("Desktop.Shared.Assets.Remotely_Icon.png"); + using var ms = new MemoryStream(); + mrs!.CopyTo(ms); + + _brandingInfo.Icon = ms.ToArray(); + } + return _brandingInfo; } public void SetBrandingInfo(BrandingInfoBase brandingInfo) @@ -48,72 +71,59 @@ public void SetBrandingInfo(BrandingInfoBase brandingInfo) _brandingInfo = brandingInfo; } - public async Task TrySetFromApi() + private async Task> TryGetBrandingInfo() { try { - if (string.IsNullOrWhiteSpace(_appState.Host)) + if (string.IsNullOrWhiteSpace(_orgIdProvider.OrganizationId) || + string.IsNullOrWhiteSpace(_appState.Host)) { - return; - } + var filePath = Process.GetCurrentProcess()?.MainModule?.FileName; - var host = _appState.Host; - - using var httpClient = new HttpClient(); + if (string.IsNullOrWhiteSpace(filePath)) + { + return Result.Fail("Failed to retrieve executing file name."); + } - var fileName = Path.GetFileNameWithoutExtension(Process.GetCurrentProcess()?.MainModule?.FileName); + var result = await _embeddedDataSearcher.TryGetEmbeddedData(filePath); - if (string.IsNullOrWhiteSpace(fileName)) - { - return; - } + if (!result.IsSuccess) + { + return Result.Fail(result.Exception); + } - if (fileName.Contains('[') && - fileName.Contains(']') && - !string.IsNullOrWhiteSpace(host)) - { - var codeLength = AppConstants.RelayCodeLength + 2; + if (!string.IsNullOrWhiteSpace(result.Value.OrganizationId)) + { + _orgIdProvider.OrganizationId = result.Value.OrganizationId; + } - for (var i = 0; i < fileName.Length; i++) + if (result.Value.ServerUrl is not null) { - var codeSection = string.Join("", fileName.Skip(i).Take(codeLength)); - if (codeSection.StartsWith("[") && codeSection.EndsWith("]")) - { - var relayCode = codeSection[1..5]; - - using var response = await httpClient.GetAsync($"{host.TrimEnd('/')}/api/Relay/{relayCode}").ConfigureAwait(false); - if (response.IsSuccessStatusCode) - { - var organizationId = await response.Content.ReadAsStringAsync(); - _orgIdProvider.OrganizationId = organizationId; - - var brandingUrl = $"{host.TrimEnd('/')}/api/branding/{organizationId}"; - var result = await httpClient.GetFromJsonAsync(brandingUrl).ConfigureAwait(false); - if (result is not null) - { - _brandingInfo = result; - } - return; - } - } + _appState.Host = result.Value.ServerUrl.AbsoluteUri; } } - if (!string.IsNullOrWhiteSpace(host) && !string.IsNullOrWhiteSpace(_orgIdProvider.OrganizationId)) + if (string.IsNullOrWhiteSpace(_appState.Host)) { - var brandingUrl = $"{host.TrimEnd('/')}/api/branding/{_orgIdProvider.OrganizationId}"; - var result = await httpClient.GetFromJsonAsync(brandingUrl).ConfigureAwait(false); - if (result is not null) - { - _brandingInfo = result; - } + return Result.Fail("ServerUrl is empty."); + } + + using var httpClient = new HttpClient(); + + var brandingUrl = $"{_appState.Host.TrimEnd('/')}/api/branding/{_orgIdProvider.OrganizationId}"; + var httpResult = await httpClient.GetFromJsonAsync(brandingUrl).ConfigureAwait(false); + if (httpResult is null) + { + return Result.Fail("Branding API HTTP result is null."); } + + return Result.Ok(httpResult); } catch (Exception ex) { - Logger.Write(ex, "Failed to resolve init params.", EventType.Warning); + _logger.LogError(ex, "Failed to get branding info."); + return Result.Fail(ex); } } - } } diff --git a/Desktop.Win/Desktop.Win.csproj b/Desktop.Win/Desktop.Win.csproj index 154db9c01..cac037cae 100644 --- a/Desktop.Win/Desktop.Win.csproj +++ b/Desktop.Win/Desktop.Win.csproj @@ -9,10 +9,10 @@ Remotely.Desktop.Win Assets\favicon.ico Desktop client for allowing your IT admin to provide remote support. - Copyright © 2021 Translucency Software + Copyright © 2023 Immense Networks Remotely Desktop Jared Goodwin - Translucency Software + Immense Networks Remotely Desktop https://remotely.one AnyCPU;x86;x64 diff --git a/Desktop.Win/Program.cs b/Desktop.Win/Program.cs index b3bf21d38..a75ecfe5e 100644 --- a/Desktop.Win/Program.cs +++ b/Desktop.Win/Program.cs @@ -9,9 +9,20 @@ using Microsoft.Extensions.Logging; using Remotely.Shared.Services; using Immense.RemoteControl.Desktop.Shared.Services; -using Microsoft.Extensions.DependencyInjection.Extensions; -using Immense.RemoteControl.Desktop.Shared.Enums; -using Remotely.Shared; +using System.Diagnostics; + +var logger = new FileLogger("Program.cs"); +var filePath = Process.GetCurrentProcess()?.MainModule?.FileName; +var serverUrl = Debugger.IsAttached ? "https://localhost:5001" : string.Empty; +var getEmbeddedResult = await EmbeddedServerDataSearcher.Instance.TryGetEmbeddedData(filePath); +if (getEmbeddedResult.IsSuccess) +{ + serverUrl = getEmbeddedResult.Value.ServerUrl.AbsoluteUri; +} +else +{ + logger.LogWarning(getEmbeddedResult.Exception, "Failed to extract embedded server data."); +} var provider = await Startup.UseRemoteControlClient( args, @@ -30,26 +41,31 @@ }); services.AddSingleton(); + services.AddSingleton(EmbeddedServerDataSearcher.Instance); }, - async services => + services => { var appState = services.GetRequiredService(); - if (appState.ArgDict.TryGetValue("org-id", out var orgId)) + var orgIdProvider = services.GetRequiredService(); + + if (getEmbeddedResult.IsSuccess) { - var orgIdProvider = services.GetRequiredService(); - orgIdProvider.OrganizationId = orgId; + orgIdProvider.OrganizationId = getEmbeddedResult.Value.OrganizationId; + appState.Host = getEmbeddedResult.Value.ServerUrl.AbsoluteUri; } - var brandingProvider = services.GetRequiredService(); - if (brandingProvider is BrandingProvider branding) + if (appState.ArgDict.TryGetValue("org-id", out var orgId)) { - await branding.TrySetFromApi(); + orgIdProvider.OrganizationId = orgId; } + return Task.CompletedTask; }, - AppConstants.ServerUrl); + serverUrl); + var dispatcher = provider.GetRequiredService(); + try { await Task.Delay(Timeout.InfiniteTimeSpan, dispatcher.ApplicationExitingToken); diff --git a/README.md b/README.md index 78e683582..885d7636d 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,9 @@ # Remotely A remote control and remote scripting solution, built with .NET 6, Blazor, and SignalR Core. -[![Build Status](https://dev.azure.com/translucency/Remotely/_apis/build/status/Remotely-ReleaseBuild?branchName=master)](https://dev.azure.com/translucency/Remotely/_build/latest?definitionId=17&branchName=master) +[![Build Status](https://dev.azure.com/translucency/Remotely/_apis/build/status/immense.Remotely?branchName=master)](https://dev.azure.com/translucency/Remotely/_build/latest?definitionId=32&branchName=master) [![Tests](https://github.com/immense/Remotely/actions/workflows/run_tests.yml/badge.svg?branch=master)](https://github.com/immense/Remotely/actions/workflows/run_tests.yml) -## Update from Author -### 2022-12-26 -I'm currently working on updates to Remotely's deployment processes. Once finished, Docker will be the only official method of self-hosting Remotely. This will help us deliver a consistently reliable deployment experience. - -These updates will include a solution for the inability, when using the Docker image, to hardcode your server's URL into the executables. - -The `Publish.ps1` script will be used in our workflow and will continue to function the same. So if you're using this to build and deploy, you won't be affected by these changes. - -In the meantime, please don't attempt to deploy changes made to the `master` branch. When everything is completed, this readme will be updated, and there will be a new release. - -Hope everyone is having a wonderful holiday! Cheers! - # Status ## 2022-05-19 Immense Networks is the proud new owner of Remotely. We intend to keep Remotely fully open-source. @@ -32,66 +20,24 @@ ImmyBot experienced explosive growth this year and we only have 4 developers whi ## Project Links Subreddit: https://www.reddit.com/r/remotely_app/ -Docker: https://hub.docker.com/r/translucency/remotely +Docker: https://hub.docker.com/r/immybot/remotely Video Tutorials: https://remotely.one/Tutorials ![image](.github/media/ask-remote.png) -## Disclaimer -Hosting a Remotely server requires running an ASP.NET Core web app behind IIS (Windows), Nginx (Ubuntu), or Caddy Server (any OS). It's expected that the person deploying and maintaining the server is familiar with this process. Since this is a hobby project that I develop in between working full time and raising a family, there simply isn't time available to provide support in this capacity. - -## GitHub Actions -GitHub Actions allows you to build and deploy Remotely for free from their cloud servers. Since the Windows agent can only be built on Windows, and the Mac agent can only be built on Mac, using a build platform like GitHub Actions or Azure Pipelines is the only reasonable way to build the whole project. The definitions for the build processes are located in `/.github/workflows/` folder. - -I've created a cross-platform command line tool that can leverage the GitHub Actions REST API to build the project and install it on your private server. This process will also embed your server's URL into the desktop clients, so that they won't need to prompt the end user to enter it. - -Branding will not work for the agent installer or quick support clients (in most cases) unless the server URL is embedded this way. There is no way for the self-contained EXE to know what server to contact unless it's been compiled into it. - -However, you can also choose to install the pre-built packages that do not have any server URLs embedded. These don't require you to fork the repository on GitHub. - -## Installation Instructions: -- Before attempting installation, verify that your domain name is resolving to your server's IP address. - - For example, I can use the command `ping remotely.lucency.co` and see the IP address to which it resolves. -- Find and download the `Remotely_Server_Installer[.exe]` CLI tool for the latest release on the [Releases page](https://github.com/immense/Remotely/releases). - - You will run it on the server where you'll be hosting Remotely. - - You need to run it with elevation (e.g. sudo or "Run as admin"). - - Use `--help` argument to see all the command line arguments. - - If values are provided for all arguments, it will run non-interactive. - - You can choose between installing the pre-built release package, or entering GitHub credentials to build and install a customized server. - - The pre-built package will not have your server's URL embedded in the clients. End users will need to enter it manually. -- If you want to use the pre-built package, run the installer now, and you're done! - - Otherwise, follow the below steps for setting up the GitHub Actions integration, then run the installer afterward. -- Fork the repo if you haven't already. - - If you've already forked the repo and haven't updated your fork recently, you'll need to do so first. - - You can use the following commands to pull the latest changes, merge them, and push them back up to your repo ([git](https://git-scm.com/downloads) required). Make sure to replace `{your-username}` with your GitHub username. This example assumes you've added your SSH key to your GitHub account. - ``` - git clone git@github.com:{your-username}/remotely - cd ./remotely - git remote add upstream https://github.com/immense/Remotely - git pull upstream master - git push origin master - ``` -- Go to the Actions tab in your forked repo and make sure you can see the Build workflows. - - Before you can use Actions for the first time, there will be prompt that you must accept on this page. -- Create a Personal Access Token that the installer will use to authorize with GitHub. - - Located here: https://github.com/settings/tokens - - It needs to have the `repo` scope. - - Save the PAT when it's displayed. It will only be shown once. -- By default, the server will be built from the author's repo. - - If you want to build from your fork, comment out the `repository` line in `Build.yml` (in your repo). There's a comment in the file that points out the line. -- Now run the installer, as described above. +## Quickstart +``` +mkdir -p /var/www/remotely +docker run -d --name remotely --restart unless-stopped -p 5000:5000 -v /var/www/remotely:/remotely-data immybot/remotely:latest +``` + ## After Installation -- In the site's content directory, make a copy of the `appsettings.json` file and name it `appsettings.Production.json`. - - The server will use this new file for reading/writing its settings, and it won't be overwritten by future ugprades. -- If using Caddy, a TLS cert will be installed automatically. - - When installling on Nginx, the script will use Certbot and prompt you to install a cert. - - For Windows IIS, you'll need to use a separate program that integrates with Let's Encrypt. - - Resources: https://letsencrypt.org/docs/client-options/#clients-windows-/-iis -- By default, SQLite is used for the database. - - The "Remotely.db" database file is automatically created in the root folder of your site. - - You can browse and modify the contents using [DB Browser for SQLite](https://sqlitebrowser.org/). -- Create your account by clicking the `Register` button on the main page. +- Data for Remotely will be saved in `/var/www/remotely/` within two files: appsettings.json and Remotely.db. + - These files will persist through teardown and setup of new Remotely containers. + - If upgrading from a non-Docker version of Remotely, overwrite these files with the ones from your previous installation. +- Use a reverse proxy like Nginx or Caddy if you want to expose the site to the internet. +- If this is the first run, create your account by clicking the `Register` button on the main page. - This account will be both the server admin and organization admin. - An organization is automatically created for the account. - Organizations are used to group together users, devices, and other data items into a single pool. @@ -100,19 +46,18 @@ However, you can also choose to install the pre-built packages that do not have - People will no longer be able to create accounts on their own. - To allow self-registration, increase the `MaxOrganizationCount` or set it to -1 (see Configuration section). -## Upgrading -* To upgrade a server, do any of the below to copy the new Server application files. - * Run one of the GitHub Actions workflows, then copy the ZIP contents to the site's content folder. - * Build from source as described above and `rsync`/`robocopy` the output files to the server directory. - * Build from source and deploy to IIS (e.g. `dotnet publish /p:PublishProfile=MyProfile`) -* For Linux, you'll need to restart the Remotely service in systemd after overwriting the files. -* For Windows, you'll need to shut down the site's Application Pool in IIS before copying the files. - * Windows won't let you overwrite files that are in use. -* The only things that shouldn't be overwritten are the database DB file (if using SQLite) and the `appsettings.Production.json`. These files should never exist in the publish output. + +## Update the Docker Container +``` +docker stop remotely +docker rm remotely +docker pull immybot/remotely:latest +docker run -d --name remotely --restart unless-stopped -p 5000:5000 -v /var/www/remotely:/remotely-data immybot/remotely:latest +``` ## Build and Debug Instructions (Windows 10) The following steps will configure your Windows 10 machine for building the Remotely server and clients. -* Install Visual Studio 2019. +* Install Visual Studio 2022. * Link: https://visualstudio.microsoft.com/downloads/ * You should have the following Workloads selected: * ASP.NET and web development diff --git a/Remotely.sln b/Remotely.sln index 54b9182bd..742e53ec7 100644 --- a/Remotely.sln +++ b/Remotely.sln @@ -45,8 +45,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Server", "Server\Server.csproj", "{75EC5DCD-DC76-4799-8623-206B1F73CA95}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Server.Installer", "Server.Installer\Server.Installer.csproj", "{BE7799D6-204C-4D35-83E7-7FB24DEBAE94}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{0754E195-7080-4AAC-B5A3-A9923B1283CE}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Shared.Tests", "Tests\Shared.Tests\Shared.Tests.csproj", "{B6C1030D-1F74-4143-BB70-FC79C0274653}" @@ -71,6 +69,14 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Desktop.UI.WPF", "submodule EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Desktop.UI", "submodules\Immense.RemoteControl\Desktop.UI\Desktop.UI.csproj", "{A53091D5-29A9-4EBF-B4E5-26DCFF0A516C}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Docker", "Docker", "{963B5555-30AE-428E-9686-59C4B6FEC052}" + ProjectSection(SolutionItems) = preProject + .docker\Dockerfile = .docker\Dockerfile + .docker\Dockerfile-old = .docker\Dockerfile-old + .docker\Dockerfile-rootless = .docker\Dockerfile-rootless + .docker\DockerMain.sh = .docker\DockerMain.sh + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -149,8 +155,8 @@ Global {48D9D0E6-5781-44A9-84C0-56F56C2A1193}.Debug|x86.Build.0 = Debug|x86 {48D9D0E6-5781-44A9-84C0-56F56C2A1193}.Release|Any CPU.ActiveCfg = Release|Any CPU {48D9D0E6-5781-44A9-84C0-56F56C2A1193}.Release|Any CPU.Build.0 = Release|Any CPU - {48D9D0E6-5781-44A9-84C0-56F56C2A1193}.Release|x64.ActiveCfg = Release|x64 - {48D9D0E6-5781-44A9-84C0-56F56C2A1193}.Release|x64.Build.0 = Release|x64 + {48D9D0E6-5781-44A9-84C0-56F56C2A1193}.Release|x64.ActiveCfg = Release|Any CPU + {48D9D0E6-5781-44A9-84C0-56F56C2A1193}.Release|x64.Build.0 = Release|Any CPU {48D9D0E6-5781-44A9-84C0-56F56C2A1193}.Release|x86.ActiveCfg = Release|x86 {48D9D0E6-5781-44A9-84C0-56F56C2A1193}.Release|x86.Build.0 = Release|x86 {6C25240C-613D-4A86-A04E-784BA6726094}.Debug|Any CPU.ActiveCfg = Debug|Any CPU @@ -177,18 +183,6 @@ Global {75EC5DCD-DC76-4799-8623-206B1F73CA95}.Release|x64.Build.0 = Release|Any CPU {75EC5DCD-DC76-4799-8623-206B1F73CA95}.Release|x86.ActiveCfg = Release|Any CPU {75EC5DCD-DC76-4799-8623-206B1F73CA95}.Release|x86.Build.0 = Release|Any CPU - {BE7799D6-204C-4D35-83E7-7FB24DEBAE94}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {BE7799D6-204C-4D35-83E7-7FB24DEBAE94}.Debug|Any CPU.Build.0 = Debug|Any CPU - {BE7799D6-204C-4D35-83E7-7FB24DEBAE94}.Debug|x64.ActiveCfg = Debug|Any CPU - {BE7799D6-204C-4D35-83E7-7FB24DEBAE94}.Debug|x64.Build.0 = Debug|Any CPU - {BE7799D6-204C-4D35-83E7-7FB24DEBAE94}.Debug|x86.ActiveCfg = Debug|Any CPU - {BE7799D6-204C-4D35-83E7-7FB24DEBAE94}.Debug|x86.Build.0 = Debug|Any CPU - {BE7799D6-204C-4D35-83E7-7FB24DEBAE94}.Release|Any CPU.ActiveCfg = Release|Any CPU - {BE7799D6-204C-4D35-83E7-7FB24DEBAE94}.Release|Any CPU.Build.0 = Release|Any CPU - {BE7799D6-204C-4D35-83E7-7FB24DEBAE94}.Release|x64.ActiveCfg = Release|Any CPU - {BE7799D6-204C-4D35-83E7-7FB24DEBAE94}.Release|x64.Build.0 = Release|Any CPU - {BE7799D6-204C-4D35-83E7-7FB24DEBAE94}.Release|x86.ActiveCfg = Release|Any CPU - {BE7799D6-204C-4D35-83E7-7FB24DEBAE94}.Release|x86.Build.0 = Release|Any CPU {B6C1030D-1F74-4143-BB70-FC79C0274653}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B6C1030D-1F74-4143-BB70-FC79C0274653}.Debug|Any CPU.Build.0 = Debug|Any CPU {B6C1030D-1F74-4143-BB70-FC79C0274653}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -325,6 +319,7 @@ Global {60173A96-0886-4DB0-903A-74ABA9F64C2C} = {48C738FB-359E-43DB-B338-FD7CB1CCF6A8} {1F3338AA-7141-4864-991C-A60196C92111} = {48C738FB-359E-43DB-B338-FD7CB1CCF6A8} {A53091D5-29A9-4EBF-B4E5-26DCFF0A516C} = {48C738FB-359E-43DB-B338-FD7CB1CCF6A8} + {963B5555-30AE-428E-9686-59C4B6FEC052} = {2176596E-12DA-4766-96E1-4D23EA7DBEC8} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {EAE10B28-119B-437C-9E68-06F0EE3F968A} diff --git a/Server.Installer/Models/CliParams.cs b/Server.Installer/Models/CliParams.cs deleted file mode 100644 index 3853ca5c7..000000000 --- a/Server.Installer/Models/CliParams.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Server.Installer.Models -{ - public class CliParams - { - public bool? CreateNew { get; set; } - public string GitHubPat { get; set; } - public string GitHubUsername { get; set; } - public string InstallDirectory { get; set; } - public string Reference { get; set; } - public Uri ServerUrl { get; set; } - public bool? UsePrebuiltPackage { get; set; } - public WebServerType? WebServer { get; set; } - } -} diff --git a/Server.Installer/Models/GitHubArtifactsResponsePayload.cs b/Server.Installer/Models/GitHubArtifactsResponsePayload.cs deleted file mode 100644 index 5ba21554e..000000000 --- a/Server.Installer/Models/GitHubArtifactsResponsePayload.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Server.Installer.Models -{ - public class ArtifactsResponsePayload - { - public int total_count { get; set; } - public Artifact[] artifacts { get; set; } - } - - public class Artifact - { - public int id { get; set; } - public string node_id { get; set; } - public string name { get; set; } - public int size_in_bytes { get; set; } - public string url { get; set; } - public string archive_download_url { get; set; } - public bool expired { get; set; } - public DateTime created_at { get; set; } - public DateTime expires_at { get; set; } - public DateTime updated_at { get; set; } - } -} diff --git a/Server.Installer/Models/GitHubReleasesResponse.cs b/Server.Installer/Models/GitHubReleasesResponse.cs deleted file mode 100644 index e37c9c7fe..000000000 --- a/Server.Installer/Models/GitHubReleasesResponse.cs +++ /dev/null @@ -1,93 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Remotely.Server.Installer.Models -{ - - public class GitHubReleasesResponsePayload - { - public string url { get; set; } - public string assets_url { get; set; } - public string upload_url { get; set; } - public string html_url { get; set; } - public int id { get; set; } - public Author author { get; set; } - public string node_id { get; set; } - public string tag_name { get; set; } - public string target_commitish { get; set; } - public string name { get; set; } - public bool draft { get; set; } - public bool prerelease { get; set; } - public DateTime created_at { get; set; } - public DateTime published_at { get; set; } - public Asset[] assets { get; set; } - public string tarball_url { get; set; } - public string zipball_url { get; set; } - public string body { get; set; } - } - - public class Author - { - public string login { get; set; } - public int id { get; set; } - public string node_id { get; set; } - public string avatar_url { get; set; } - public string gravatar_id { get; set; } - public string url { get; set; } - public string html_url { get; set; } - public string followers_url { get; set; } - public string following_url { get; set; } - public string gists_url { get; set; } - public string starred_url { get; set; } - public string subscriptions_url { get; set; } - public string organizations_url { get; set; } - public string repos_url { get; set; } - public string events_url { get; set; } - public string received_events_url { get; set; } - public string type { get; set; } - public bool site_admin { get; set; } - } - - public class Asset - { - public string url { get; set; } - public int id { get; set; } - public string node_id { get; set; } - public string name { get; set; } - public string label { get; set; } - public Uploader uploader { get; set; } - public string content_type { get; set; } - public string state { get; set; } - public int size { get; set; } - public int download_count { get; set; } - public DateTime created_at { get; set; } - public DateTime updated_at { get; set; } - public string browser_download_url { get; set; } - } - - public class Uploader - { - public string login { get; set; } - public int id { get; set; } - public string node_id { get; set; } - public string avatar_url { get; set; } - public string gravatar_id { get; set; } - public string url { get; set; } - public string html_url { get; set; } - public string followers_url { get; set; } - public string following_url { get; set; } - public string gists_url { get; set; } - public string starred_url { get; set; } - public string subscriptions_url { get; set; } - public string organizations_url { get; set; } - public string repos_url { get; set; } - public string events_url { get; set; } - public string received_events_url { get; set; } - public string type { get; set; } - public bool site_admin { get; set; } - } - -} diff --git a/Server.Installer/Models/WebServerType.cs b/Server.Installer/Models/WebServerType.cs deleted file mode 100644 index e7ad4db3a..000000000 --- a/Server.Installer/Models/WebServerType.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.Linq; -using System.Runtime.Serialization; -using System.Text; -using System.Threading.Tasks; - -namespace Server.Installer.Models -{ - public enum WebServerType - { - UbuntuCaddy, - UbuntuNginx, - CentOsCaddy, - CentOsNginx, - IisWindows - } -} diff --git a/Server.Installer/Program.cs b/Server.Installer/Program.cs deleted file mode 100644 index 3dd44c5a0..000000000 --- a/Server.Installer/Program.cs +++ /dev/null @@ -1,361 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Remotely.Shared; -using Remotely.Shared.Enums; -using Remotely.Shared.Services; -using Remotely.Shared.Utilities; -using Server.Installer.Models; -using Server.Installer.Services; -using System; -using System.IO; -using System.Threading.Tasks; - -namespace Server.Installer -{ - - public class Program - { - public static IServiceProvider Services { get; set; } - - public static async Task Main(string[] args) - { - Console.ForegroundColor = ConsoleColor.Cyan; - Console.WriteLine(AppConstants.RemotelyAscii); - Console.ForegroundColor = ConsoleColor.Gray; - Console.WriteLine("(https://remotely.one)"); - Console.WriteLine(); - Console.WriteLine(); - - - if (!ParseCliParams(args, out var cliParams)) - { - ShowHelpText(); - return; - } - - if (EnvironmentHelper.Platform != Platform.Windows && - EnvironmentHelper.Platform != Platform.Linux) - { - ConsoleHelper.WriteError("Remotely Server can only be installed on Linux or Windows."); - return; - } - - BuildServices(); - - var elevationDetector = Services.GetRequiredService(); - var serverInstaller = Services.GetRequiredService(); - var githubApi = Services.GetRequiredService(); - - if (!elevationDetector.IsElevated()) - { - ConsoleHelper.WriteError("The installer process must be elevated. On Linux, run with sudo. " + - "On Windows, run from a command line that was opened with \"Run as admin\"."); - return; - } - - ConsoleHelper.WriteLine("Thank you for trying Remotely! This installer will guide you " + - "through deploying the Remotely server onto this machine."); - - ConsoleHelper.WriteLine("There are two ways to create the server files. You can download the pre-built package " + - "from the latest public release, or you can use your GitHub credentials to build a customized package through " + - "an integration with GitHub Actions. The pre-built packages will not have your server's URL embedded in the " + - "desktop clients, and end users will need to type it in manually."); - - ConsoleHelper.WriteLine("If using GitHub Actions, you will need to enter a GitHub Personal Access Token, " + - "which will allow this app to access your fork of the Remotely repo. You can generate a PAT at " + - "https://github.com/settings/tokens. You need to give it the \"repo\" scope."); - - ConsoleHelper.WriteLine("Be sure to retain your GitHub Personal Access Token if you want to re-use it " + - "for upgrading in the future. The installer does not save it locally."); - - ConsoleHelper.WriteLine("If you haven't already, please go to the Actions tab in your Remotely repo " + - "and enable them. If not, this process will fail."); - - - while (cliParams.UsePrebuiltPackage is null) - { - var usePrebuiltPackage = ConsoleHelper.ReadLine("Download pre-built package (yes/no)?", - subprompt: "If no, a customized server package will be created through GitHub Actions."); - - if (ConsoleHelper.TryParseBoolLike(usePrebuiltPackage, out var result)) - { - cliParams.UsePrebuiltPackage = result; - } - } - - while (string.IsNullOrWhiteSpace(cliParams.InstallDirectory)) - { - cliParams.InstallDirectory = ConsoleHelper.ReadLine("Which directory should the server files be extracted to (e.g. /var/www/remotely/)?").Trim(); - } - - while (cliParams.ServerUrl is null) - { - var url = ConsoleHelper.ReadLine("What is your server's public URL (e.g. https://app.remotely.one)?").Trim(); - if (Uri.TryCreate(url, UriKind.Absolute, out var serverUrl)) - { - cliParams.ServerUrl = serverUrl; - } - } - - while (cliParams.WebServer is null) - { - var webServerType = ConsoleHelper.GetSelection("Which web server will be used?", - "Caddy on Ubuntu", - "Nginx on Ubuntu", - "Caddy on CentOS", - "Nginx on CentOS", - "IIS on Windows Server 2016+"); - - if (Enum.TryParse(webServerType, out var result)) - { - cliParams.WebServer = result; - } - } - - - if (cliParams.UsePrebuiltPackage == false) - { - while (string.IsNullOrWhiteSpace(cliParams.GitHubUsername)) - { - cliParams.GitHubUsername = ConsoleHelper.ReadLine("What is your GitHub username?").Trim(); - } - - while (string.IsNullOrWhiteSpace(cliParams.GitHubPat)) - { - cliParams.GitHubPat = ConsoleHelper.ReadLine("What GitHub Personal Access Token should be used?").Trim(); - } - - while (cliParams.CreateNew is null) - { - var createNew = ConsoleHelper.ReadLine("Create new build (yes/no)?", - subprompt: "If no, the latest existing build artifact on GitHub will be used."); - - if (ConsoleHelper.TryParseBoolLike(createNew, out var result)) - { - cliParams.CreateNew = result; - } - } - - if (cliParams.CreateNew == true) - { - if (cliParams.Reference?.Contains("latest", StringComparison.OrdinalIgnoreCase) == true) - { - cliParams.Reference = await githubApi.GetLatestReleaseTag(); - } - - while (string.IsNullOrWhiteSpace(cliParams.Reference)) - { - var selection = ConsoleHelper.GetSelection("Which version would you like to build?", - "Latest official release", - "Preview changes (i.e. master branch)", - "Specific release"); - - if (int.TryParse(selection, out var result)) - { - switch (result) - { - case 0: - cliParams.Reference = await githubApi.GetLatestReleaseTag(); - break; - case 1: - cliParams.Reference = "master"; - break; - case 2: - ConsoleHelper.WriteLine("Enter the GitHub branch or tag name from which to build " + - "(e.g. \"master\" or \"v2021.04.25.0953\")."); - cliParams.Reference = ConsoleHelper.ReadLine("Input Reference").Trim(); - break; - default: - break; - } - } - - - } - - } - - ConsoleHelper.WriteLine($"Performing server install. GitHub User: {cliParams.GitHubUsername}. " + - $"Server URL: {cliParams.ServerUrl}. Installation Directory: {cliParams.InstallDirectory}. " + - $"Web Server: {cliParams.WebServer}. Create New Build: {cliParams.CreateNew}. " + - $"Git Reference: {cliParams.Reference}"); - } - else - { - ConsoleHelper.WriteLine($"Server URL: {cliParams.ServerUrl}. " + - $"Installation Directory: {cliParams.InstallDirectory}. Web Server: {cliParams.WebServer}."); - } - - await serverInstaller.PerformInstall(cliParams); - - ConsoleHelper.WriteLine("Installation completed."); - } - - private static void BuildServices() - { - var services = new ServiceCollection(); - - services.AddSingleton(); - services.AddSingleton(); - - if (EnvironmentHelper.IsWindows) - { - services.AddSingleton(); - } - else if (EnvironmentHelper.IsLinux) - { - services.AddSingleton(); - } - else - { - throw new NotSupportedException("Operating system not supported."); - } - - Services = services.BuildServiceProvider(); - } - - private static bool ParseCliParams(string[] args, out CliParams cliParams) - { - cliParams = new CliParams(); - - for (var i = 0; i < args.Length; i += 2) - { - try - { - var key = args[i].Trim(); - - if (i == args.Length - 1) - { - ConsoleHelper.WriteError("An argument is missing a value."); - return false; - } - - var value = args[i + 1].Trim(); - - switch (key) - { - case "--help": - case "-h": - case "/h": - ShowHelpText(); - Environment.Exit(0); - break; - case "--use-prebuilt": - case "-b": - { - if (bool.TryParse(value, out var result)) - { - cliParams.UsePrebuiltPackage = result; - continue; - } - ConsoleHelper.WriteError("--use-prebuilt parameter is invalid. Must be true/yes or false/no."); - return false; - } - case "--web-server": - case "-w": - { - if (int.TryParse(value, out var webServerResult)) - { - cliParams.WebServer = (WebServerType)webServerResult; - continue; - } - ConsoleHelper.WriteError($"--web-server parameter is invalid. Must be a " + - $"number (0 - {Enum.GetValues().Length})."); - return false; - } - case "--server-url": - case "-s": - { - if (Uri.TryCreate(value, UriKind.Absolute, out var result)) - { - cliParams.ServerUrl = result; - continue; - } - ConsoleHelper.WriteError("--server-url parameter is invalid. Must be a valid URL (e.g. https://app.remotely.one)."); - return false; - } - case "--install-directory": - case "-i": - cliParams.InstallDirectory = value; - continue; - case "--github-username": - case "-u": - cliParams.GitHubUsername = value; - continue; - case "--github-pat": - case "-p": - cliParams.GitHubPat = value; - continue; - case "--reference": - case "-r": - cliParams.Reference = value; - continue; - case "--create-new": - case "-c": - { - if (bool.TryParse(value, out var result)) - { - cliParams.CreateNew = result; - continue; - } - ConsoleHelper.WriteError("--create-new parameter is invalid. Must be true/yes or false/no."); - return false; - } - default: - return false; - } - } - catch (Exception ex) - { - ConsoleHelper.WriteError($"Error while parsing command line arguments: {ex.Message}"); - return false; - } - - } - return true; - } - - private static void ShowHelpText() - { - ConsoleHelper.WriteLine("Remotely Server Installer", 0, ConsoleColor.Cyan); - ConsoleHelper.WriteLine("Builds a customized Remotely server using GitHub actions " + - "and installs the server on the local machine.", 1); - - ConsoleHelper.WriteLine("Usage:"); - - ConsoleHelper.WriteLine("\tNo Parameters - Run the installer interactively.", 2); - - ConsoleHelper.WriteLine("\t--use-prebuilt, -b (true/false or yes/no) Whether to use the pre-built server package from the " + - "latest public release, or to create a customized package through GitHub Actions. The pre-built package " + - "will not contain your server's URL in the desktop clients, and end users will need to type it in manually.", 1); - - ConsoleHelper.WriteLine("\t--github-username, -u Your GitHub username, where the forked Remotely repo exists.", 1); - - ConsoleHelper.WriteLine("\t--github-pat, -p The GitHub Personal Access Token to use for authentication. " + - "Create one at ttps://github.com/settings/tokens.", 1); - - ConsoleHelper.WriteLine("\t--server-url, -s The public URL where your Remotely server will be accessed (e.g. https://app.remotely.one).", 1); - - ConsoleHelper.WriteLine("\t--install-directory, -i The directory path where the server files will be installed (e.g. /var/www/remotely/).", 1); - - ConsoleHelper.WriteLine("Enter the GitHub branch or tag name from which to build. For example, you can enter " + - " \"master\" to build the latest changes from the default branch. Or you can enter a release tag like \"v2021.04.13.1604\".", 1); - - ConsoleHelper.WriteLine("\t--reference, -r Use \"latest\" to build from the latest full release. Otherwise, you can enter the " + - "name of a branch or tag from which to build. For example, you can enter \"master\" to build the most recent preview changes " + - "from the default branch. Or you can enter a specific release tag like \"v2021.04.13.1604\".", 1); - - ConsoleHelper.WriteLine("\t--create-new, -c (true/false or yes/no) Whether to run a new build. If false, the latest existing build artifact will be used.", 1); - - ConsoleHelper.WriteLine("\t--web-server, -w Number. The web server that will be used as a reverse proxy to forward " + - "requests to the Remotely server. Select the appropriate option for your operating system and web server. " + - "0 = Caddy on Ubuntu. 1 = Nginx on Ubuntu. 2 = Caddy on CentOS. 3 = Nginx on CentOS. 4 = IIS on Windows Server 2016+.", 1); - - ConsoleHelper.WriteLine("Example (build latest release):"); - ConsoleHelper.WriteLine("sudo ./Remotely_Server_Installer -b false -u lucent-sea -p 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 -s https://app.remotely.one -i /var/www/remotely/ -r latest -c true -w 0", 1); - - ConsoleHelper.WriteLine("Example (use pre-built package):"); - ConsoleHelper.WriteLine("sudo ./Remotely_Server_Installer -b true -s https://app.remotely.one -i /var/www/remotely/ -w 0"); - } - } -} diff --git a/Server.Installer/Properties/PublishProfiles/Folder-Linux-x64.pubxml b/Server.Installer/Properties/PublishProfiles/Folder-Linux-x64.pubxml deleted file mode 100644 index 732c21703..000000000 --- a/Server.Installer/Properties/PublishProfiles/Folder-Linux-x64.pubxml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - Release - x64 - publish\ - FileSystem - net6.0 - linux-x64 - true - True - False - true - - \ No newline at end of file diff --git a/Server.Installer/Properties/PublishProfiles/Folder-Win-x64.pubxml b/Server.Installer/Properties/PublishProfiles/Folder-Win-x64.pubxml deleted file mode 100644 index 7e25b09ec..000000000 --- a/Server.Installer/Properties/PublishProfiles/Folder-Win-x64.pubxml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - Release - x64 - publish\ - FileSystem - net6.0 - win-x64 - true - True - False - False - true - - \ No newline at end of file diff --git a/Server.Installer/Remotely_Icon.ico b/Server.Installer/Remotely_Icon.ico deleted file mode 100644 index 55aaa8ca3..000000000 Binary files a/Server.Installer/Remotely_Icon.ico and /dev/null differ diff --git a/Server.Installer/Resources/CentOS_Caddy_Install.sh b/Server.Installer/Resources/CentOS_Caddy_Install.sh deleted file mode 100644 index a9caa96c7..000000000 --- a/Server.Installer/Resources/CentOS_Caddy_Install.sh +++ /dev/null @@ -1,103 +0,0 @@ -#!/bin/bash -echo "Thanks for trying Remotely!" -echo - - -Args=( "$@" ) -ArgLength=${#Args[@]} - -for (( i=0; i<${ArgLength}; i+=2 )); -do - if [ "${Args[$i]}" = "--host" ]; then - HostName="${Args[$i+1]}" - elif [ "${Args[$i]}" = "--approot" ]; then - AppRoot="${Args[$i+1]}" - fi -done - -if [ -z "$AppRoot" ]; then - read -p "Enter path where the Remotely server files should be installed (typically /var/www/remotely): " AppRoot - if [ -z "$AppRoot" ]; then - AppRoot="/var/www/remotely" - fi -fi - -if [ -z "$HostName" ]; then - read -p "Enter server host (e.g. remotely.yourdomainname.com): " HostName -fi - -chmod +x "$AppRoot/Remotely_Server" - -echo "Using $AppRoot as the Remotely website's content directory." - -yum update -yum -y install curl -yum -y install software-properties-common -yum -y install gnupg - -# Install .NET Core Runtime. -sudo rpm -Uvh https://packages.microsoft.com/config/centos/7/packages-microsoft-prod.rpm - -yum -y install apt-transport-https -yum -y update -yum -y install aspnetcore-runtime-6.0 - - - # Install other prerequisites. -yum -y install https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm -yum -y install yum-utils -yum-config-manager --enable rhui-REGION-rhel-server-extras rhui-REGION-rhel-server-optional -yum -y install unzip -yum -y install acl -yum -y install libc6-dev -yum -y install libgdiplus - - -# Install Caddy -yum install yum-plugin-copr -yum copr enable @caddy/caddy -yum install caddy - - -# Configure Caddy -caddyConfig=" -$HostName { - reverse_proxy 127.0.0.1:5000 -} -" - -echo "$caddyConfig" > /etc/caddy/Caddyfile - - -# Create service. - -serviceConfig="[Unit] -Description=Remotely Server - -[Service] -WorkingDirectory=$AppRoot -ExecStart=/usr/bin/dotnet $AppRoot/Remotely_Server.dll -Restart=always -# Restart service after 10 seconds if the dotnet service crashes: -RestartSec=10 -SyslogIdentifier=remotely -Environment=ASPNETCORE_ENVIRONMENT=Production -Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false - -[Install] -WantedBy=multi-user.target" - -echo "$serviceConfig" > /etc/systemd/system/remotely.service - - -# Enable service. -systemctl enable remotely.service -# Start service. -systemctl start remotely.service - -firewall-cmd --permanent --zone=public --add-service=http -firewall-cmd --permanent --zone=public --add-service=https -firewall-cmd --reload - -# Restart caddy -systemctl restart caddy \ No newline at end of file diff --git a/Server.Installer/Resources/CentOS_Nginx_Install.sh b/Server.Installer/Resources/CentOS_Nginx_Install.sh deleted file mode 100644 index d3e6b4228..000000000 --- a/Server.Installer/Resources/CentOS_Nginx_Install.sh +++ /dev/null @@ -1,157 +0,0 @@ -#!/bin/bash -echo "Thanks for trying Remotely!" -echo - -Args=( "$@" ) -ArgLength=${#Args[@]} - -for (( i=0; i<${ArgLength}; i+=2 )); -do - if [ "${Args[$i]}" = "--host" ]; then - HostName="${Args[$i+1]}" - elif [ "${Args[$i]}" = "--approot" ]; then - AppRoot="${Args[$i+1]}" - fi -done - -if [ -z "$AppRoot" ]; then - read -p "Enter path where the Remotely server files should be installed (typically /var/www/remotely): " AppRoot - if [ -z "$AppRoot" ]; then - AppRoot="/var/www/remotely" - fi -fi - -if [ -z "$HostName" ]; then - read -p "Enter server host (e.g. remotely.yourdomainname.com): " HostName -fi - -echo "Using $AppRoot as the Remotely website's content directory." - -yum update -yum -y install curl -yum -y install software-properties-common -yum -y install gnupg - -# Install .NET Core Runtime. -sudo rpm -Uvh https://packages.microsoft.com/config/centos/7/packages-microsoft-prod.rpm - -yum -y install apt-transport-https -yum -y update -yum -y install aspnetcore-runtime-6.0 - - - # Install other prerequisites. -yum -y install https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm -yum -y install yum-utils -yum-config-manager --enable rhui-REGION-rhel-server-extras rhui-REGION-rhel-server-optional -yum -y install unzip -yum -y install acl -yum -y install libc6-dev -yum -y install libgdiplus - - -# Set permissions on Remotely files. -setfacl -R -m u:apache:rwx $AppRoot -chown -R apache:apache $AppRoot -chmod +x "$AppRoot/Remotely_Server" - - -# Install Nginx -yum -y install nginx - -systemctl start nginx - - -# Configure Nginx -nginxConfig="server { - listen 80; - server_name $HostName *.$HostName; - location / { - proxy_pass http://localhost:5000; - proxy_http_version 1.1; - proxy_set_header Upgrade \$http_upgrade; - proxy_set_header Connection close; - proxy_set_header Host \$host; - proxy_cache_bypass \$http_upgrade; - proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto \$scheme; - } - - location /_blazor { - proxy_pass http://localhost:5000; - proxy_http_version 1.1; - proxy_set_header Upgrade \$http_upgrade; - proxy_set_header Connection \"Upgrade\"; - proxy_set_header Host \$host; - proxy_cache_bypass \$http_upgrade; - } - location /AgentHub { - proxy_pass http://localhost:5000; - proxy_http_version 1.1; - proxy_set_header Upgrade \$http_upgrade; - proxy_set_header Connection \"Upgrade\"; - proxy_set_header Host \$host; - proxy_cache_bypass \$http_upgrade; - } - - location /ViewerHub { - proxy_pass http://localhost:5000; - proxy_http_version 1.1; - proxy_set_header Upgrade \$http_upgrade; - proxy_set_header Connection \"Upgrade\"; - proxy_set_header Host \$host; - proxy_cache_bypass \$http_upgrade; - } - location /CasterHub { - proxy_pass http://localhost:5000; - proxy_http_version 1.1; - proxy_set_header Upgrade \$http_upgrade; - proxy_set_header Connection \"Upgrade\"; - proxy_set_header Host \$host; - proxy_cache_bypass \$http_upgrade; - } -}" - -echo "$nginxConfig" > /etc/nginx/conf.d/remotely.conf - -# Test config. -nginx -t - -# Reload. -nginx -s reload - - -# Create service. - -serviceConfig="[Unit] -Description=Remotely Server - -[Service] -WorkingDirectory=$AppRoot -ExecStart=/usr/bin/dotnet $AppRoot/Remotely_Server.dll -Restart=always -# Restart service after 10 seconds if the dotnet service crashes: -RestartSec=10 -SyslogIdentifier=remotely -Environment=ASPNETCORE_ENVIRONMENT=Production -Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false - -[Install] -WantedBy=multi-user.target" - -echo "$serviceConfig" > /etc/systemd/system/remotely.service - - -# Enable service. -systemctl enable remotely.service -# Start service. -systemctl start remotely.service - -firewall-cmd --permanent --zone=public --add-service=http -firewall-cmd --permanent --zone=public --add-service=https -firewall-cmd --reload - -# Install Certbot and get SSL cert. -yum -y install certbot python3-certbot-nginx - -certbot --nginx \ No newline at end of file diff --git a/Server.Installer/Resources/IIS_Windows_Install.ps1 b/Server.Installer/Resources/IIS_Windows_Install.ps1 deleted file mode 100644 index 99a57846e..000000000 --- a/Server.Installer/Resources/IIS_Windows_Install.ps1 +++ /dev/null @@ -1,306 +0,0 @@ -<# -.SYNOPSIS - Configures IIS and installs the Remotely server. -.COPYRIGHT - Copyright © 2020 Translucency Software. All rights reserved. -#> -param ( - # The host name (excluding scheme) for the server that will run Remotely. - [Parameter(Mandatory=$True)] - [string]$HostName, - # The name to use for the IIS Application Pool for the Remotely site. - [Parameter(Mandatory=$True)] - [string]$AppPoolName, - # The name to use for the IIS site. - [Parameter(Mandatory=$True)] - [string]$SiteName, - # The folder path where the Remotely server files are located. - [Parameter(Mandatory=$True)] - [string]$SitePath, - # Whether to run the script without any prompts. - [switch]$Quiet, - # The path to Windows ACME Simple (wacs.exe) to use for automatically obtaining and - # installing a Let's Encrypt certificate. - # (Project and downloads: https://github.com/win-acme/win-acme) - [string]$WacsPath, - # The email address to use when registering the certificate with WACS. - [string]$EmailAddress -) - -$Host.UI.RawUI.WindowTitle = "Remotely Setup" -Clear-Host - -#region Variables -$FirewallSet = $false -$CopyErrors = $false -if ($PSScriptRoot -eq ""){ - $PSScriptRoot = (Get-Location) -} - -$Root = (Get-Item -Path $PSScriptRoot).Parent.FullName -#endregion - -#region Functions -function Do-Pause { - if (!$Quiet){ - pause - } -} -function Wrap-Host -{ - [CmdletBinding()] - [Alias()] - [OutputType([int])] - Param - ( - # The text to write. - [Parameter(Mandatory=$false, - ValueFromPipelineByPropertyName=$true, - Position=0)] - [String] - $Text, - - # Param2 help description - [Parameter(Mandatory=$false, - ValueFromPipelineByPropertyName=$true, - Position=1)] - [ConsoleColor] - $ForegroundColor - ) - - Begin - { - } - Process - { - if (!$Text){ - Write-Host - return - } - $Width = $Host.UI.RawUI.BufferSize.Width - $SB = New-Object System.Text.StringBuilder - while ($Text.Length -gt $Width) { - [int]$LastSpace = $Text.Substring(0, $Width).LastIndexOf(" ") - $SB.AppendLine($Text.Substring(0, $LastSpace).Trim()) | Out-Null - $Text = $Text.Substring(($LastSpace), $Text.Length - $LastSpace).Trim() - } - $SB.Append($Text) | Out-Null - if ($ForegroundColor) - { - Write-Host $SB.ToString() -ForegroundColor $ForegroundColor - } - else - { - Write-Host $SB.ToString() - } - - } - End - { - } -} - -#endregion - - -#region Prerequisite Tests -### Test if process is elevated. ### -$User = [Security.Principal.WindowsIdentity]::GetCurrent() -if ((New-Object Security.Principal.WindowsPrincipal $User).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator) -eq $false) { - Wrap-Host - Wrap-Host "Error: This installation needs to be run from an elevated process (Run as Administrator)." -ForegroundColor Red - Do-Pause - return -} -### Check PS version. ### -if ((Get-Host).Version.Major -lt 5) { - Wrap-Host - Wrap-Host "Error: PowerShell 5 is required. Please install it via the Windows Management Framework 5.1 download from Microsoft." -ForegroundColor Red - Do-Pause - return -} -### Check Script Root ### -if (!$PSScriptRoot) { - Wrap-Host - Wrap-Host "Error: Unable to determine working directory. Please make sure you're running the full script and not just a section." -ForegroundColor Red - Do-Pause - return -} - -### Check OS version. ### -$OS = Get-WmiObject -Class Win32_OperatingSystem -if ($OS.Name.ToLower().Contains("home") -or $OS.Caption.ToLower().Contains("home")) { - Wrap-Host - Wrap-Host "Error: Windows Home version does not have the necessary features to run Remotely." -ForegroundColor Red - Do-Pause - return -} - -### Check if PostgreSQL is installed. ### -##if ((Get-Package -Name "*PostgreSQL*" -ErrorAction SilentlyContinue) -eq $null){ -## Wrap-Host -## Wrap-Host "ERROR: PostgreSQL was not found. Please install it from https://postgresql.org." -ForegroundColor Red -## Wrap-Host -## Do-Pause -## return -##} - -#endregion - - -### Intro ### -Clear-Host -Wrap-Host -Wrap-Host "**********************************" -Wrap-Host " Remotely Setup" -ForegroundColor Cyan -Wrap-Host "**********************************" -Wrap-Host -Wrap-Host "Hello, and thank you for trying out Remotely!" -ForegroundColor Green -Wrap-Host -Wrap-Host "This setup script will create an IIS site and install Remotely on this machine." -ForegroundColor Green -Wrap-Host -Do-Pause -Clear-Host - - -### Automatic IIS Setup ### -$RebootRequired = $false -Wrap-Host -Wrap-Host "Installing IIS components..." -ForegroundColor Green -Write-Progress -Activity "IIS Component Installation" -Status "Installing web server" -PercentComplete (1/7*100) -$Result = Add-WindowsFeature Web-Server -if ($Result.RestartNeeded -like "Yes") -{ - $RebootRequired = $true -} -#Write-Progress -Activity "IIS Component Installation" -Status "Installing ASP.NET" -PercentComplete (2/7*100) -#$Result = Add-WindowsFeature Web-Asp-Net -#if ($Result.RestartNeeded -like "Yes") -#{ -# $RebootRequired = $true -#} -#Write-Progress -Activity "IIS Component Installation" -Status "Installing ASP.NET 4.5" -PercentComplete (3/7*100) -#$Result = Add-WindowsFeature Web-Asp-Net45 -#if ($Result.RestartNeeded -like "Yes") -#{ -# $RebootRequired = $true -#} -Write-Progress -Activity "IIS Component Installation" -Status "Installing web sockets" -PercentComplete (4/7*100) -$Result = Add-WindowsFeature Web-WebSockets -if ($Result.RestartNeeded -like "Yes") -{ - $RebootRequired = $true -} -Write-Progress -Activity "IIS Component Installation" -Status "Installing IIS management tools" -PercentComplete (5/7*100) -$Result = Add-WindowsFeature Web-Mgmt-Tools -if ($Result.RestartNeeded -like "Yes") -{ - $RebootRequired = $true -} -Write-Progress -Activity "IIS Component Installation" -Status "Installing web filtering" -PercentComplete (6/7*100) -$Result = Add-WindowsFeature Web-Filtering -if ($Result.RestartNeeded -like "Yes") -{ - $RebootRequired = $true -} - -Write-Progress -Activity "IIS Component Installation" -Status "IIS setup completed" -PercentComplete (7/7*100) -Completed -Start-Sleep 2 -Clear-Host - -### Create IIS Site ## - -if ((Get-IISAppPool -Name $AppPoolName) -eq $null) { - New-WebAppPool -Name $AppPoolName - Set-ItemProperty -Path "IIS:\AppPools\$AppPoolName" -name processModel.identityType -Value 4 - Set-ItemProperty -Path "IIS:\AppPools\$AppPoolName" -name processModel.loadUserProfile -Value $true -} - -if ((Get-Website -Name $SiteName) -eq $null) { - New-Website -Name $SiteName -PhysicalPath $SitePath -HostHeader $HostName -ApplicationPool $AppPoolName -} - - -### Set ACL on website folders and files ### -Wrap-Host -Wrap-Host "Setting ACLs..." -ForegroundColor Green -$Acl = Get-Acl -Path $SitePath -$Rule = New-Object System.Security.AccessControl.FileSystemAccessRule("BUILTIN\IIS_IUSRS", "Modify", "ContainerInherit,ObjectInherit", "None", "Allow") -$Acl.AddAccessRule($Rule) -$Acl.SetOwner((New-Object System.Security.Principal.NTAccount("Administrators"))) -Set-Acl -Path $SitePath -AclObject $Acl -Get-ChildItem -Path $SitePath -Recurse | ForEach-Object { - Set-Acl -Path $_.FullName -AclObject $Acl -} - -### Firewall Rules ### -Wrap-Host -Wrap-Host "Checking firewall rules for HTTP/HTTPS..." -ForegroundColor Green -try -{ - Enable-NetFirewallRule -Name "IIS-WebServerRole-HTTP-In-TCP" - Enable-NetFirewallRule -Name "IIS-WebServerRole-HTTPS-In-TCP" - if ((Get-NetFirewallRule -Name "IIS-WebServerRole-HTTP-In-TCP").Enabled -like "False" -or (Get-NetFirewallRule -Name "IIS-WebServerRole-HTTP-In-TCP").Enabled -like "False") - { - $FirewallSet = $false - } - else - { - $FirewallSet = $true - } -} -catch -{ - $FirewallSet = $false -} - -# Start website. -Start-WebAppPool -Name $AppPoolName -Start-Website -Name $SiteName - - -### SSL certificate installation. ### -if ($WacsPath) { - if (Test-Path -Path $WacsPath) { - &"$WacsPath" --target iis --siteid (Get-Website -Name $SiteName).ID --installation iis --emailaddress $EmailAddress --accepttos - } -} - -Wrap-Host -Wrap-Host -Wrap-Host -Wrap-Host "**********************************" -Wrap-Host " Server setup complete!" -ForegroundColor Green -Wrap-Host "**********************************" -Wrap-Host -Wrap-Host -Wrap-Host -Wrap-Host "**********************************" -Wrap-Host " IMPORTANT FOLLOW-UP STEPS" -ForegroundColor Yellow -Wrap-Host "**********************************" -Wrap-Host -Wrap-Host "If you haven't already, you must install the latest .NET Hosting Bundle for IIS." -Wrap-Host "Please download and install it from the following link. Click 'Download .NET Runtime', then 'Download Hosting Bundle'." -Wrap-Host -Wrap-Host "https://dotnet.microsoft.com/download" -Wrap-Host -Wrap-Host -Wrap-Host "You should also install a TLS certificate for your site, if you haven't already. I recommend checking out Let's Encrypt for free, automated SSL certificates." -ForegroundColor Green - -if ($RebootRequired) { - Wrap-Host - Wrap-Host "A reboot is required for the new IIS components to work properly. Please reboot your computer at your earliest convenience." -ForegroundColor Red -} -if ($FirewallSet -eq $false) -{ - Wrap-Host - Wrap-Host "Firewall rules were not properly set. Please ensure that ports 80 (HTTP) and 443 (HTTPS) are open. Windows Firewall has predefined rules for these called ""World Wide Web Services (HTTP(S) Traffic-In)""." -ForegroundColor Red -} - -if ($CopyErrors) -{ - Wrap-Host - Wrap-Host "There were errors copying some of the server files. Please try deleting all files in the website directory and trying again." -ForegroundColor Red -} -Wrap-Host -Do-Pause \ No newline at end of file diff --git a/Server.Installer/Resources/Ubuntu_Caddy_Install.sh b/Server.Installer/Resources/Ubuntu_Caddy_Install.sh deleted file mode 100644 index a1f89b1da..000000000 --- a/Server.Installer/Resources/Ubuntu_Caddy_Install.sh +++ /dev/null @@ -1,101 +0,0 @@ -#!/bin/bash -echo "Thanks for trying Remotely!" -echo - -Args=( "$@" ) -ArgLength=${#Args[@]} - -for (( i=0; i<${ArgLength}; i+=2 )); -do - if [ "${Args[$i]}" = "--host" ]; then - HostName="${Args[$i+1]}" - elif [ "${Args[$i]}" = "--approot" ]; then - AppRoot="${Args[$i+1]}" - fi -done - -if [ -z "$AppRoot" ]; then - read -p "Enter path where the Remotely server files should be installed (typically /var/www/remotely): " AppRoot - if [ -z "$AppRoot" ]; then - AppRoot="/var/www/remotely" - fi -fi - -if [ -z "$HostName" ]; then - read -p "Enter server host (e.g. remotely.yourdomainname.com): " HostName -fi - -chmod +x "$AppRoot/Remotely_Server" - -echo "Using $AppRoot as the Remotely website's content directory." - -apt-get -y install curl -apt-get -y install software-properties-common -apt-get -y install gnupg - -UbuntuVersion=$(lsb_release -r -s) -UbuntuVersionInt=$(("${UbuntuVersion/./}")) - -# Install .NET Core Runtime. -if [ $UbuntuVersionInt -ge 2204 ]; then - apt-get install -y aspnetcore-runtime-6.0 -else - wget -q https://packages.microsoft.com/config/ubuntu/$UbuntuVersion/packages-microsoft-prod.deb - dpkg -i packages-microsoft-prod.deb - add-apt-repository universe - apt-get update - apt-get -y install apt-transport-https - apt-get -y install aspnetcore-runtime-6.0 - rm packages-microsoft-prod.deb -fi - - - - -# Install Caddy -apt install -y debian-keyring debian-archive-keyring apt-transport-https -curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg -curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | tee /etc/apt/sources.list.d/caddy-stable.list -apt update -apt install caddy - - -# Configure Caddy -caddyConfig=" -$HostName { - reverse_proxy 127.0.0.1:5000 -} -" - -echo "$caddyConfig" > /etc/caddy/Caddyfile - - -# Create Remotely service. - -serviceConfig="[Unit] -Description=Remotely Server - -[Service] -WorkingDirectory=$AppRoot -ExecStart=/usr/bin/dotnet $AppRoot/Remotely_Server.dll -Restart=always -# Restart service after 10 seconds if the dotnet service crashes: -RestartSec=10 -SyslogIdentifier=remotely -Environment=ASPNETCORE_ENVIRONMENT=Production -Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false - -[Install] -WantedBy=multi-user.target" - -echo "$serviceConfig" > /etc/systemd/system/remotely.service - - -# Enable service. -systemctl enable remotely.service -# Start service. -systemctl restart remotely.service - - -# Restart caddy -systemctl restart caddy \ No newline at end of file diff --git a/Server.Installer/Resources/Ubuntu_Nginx_Install.sh b/Server.Installer/Resources/Ubuntu_Nginx_Install.sh deleted file mode 100644 index 77e4b0808..000000000 --- a/Server.Installer/Resources/Ubuntu_Nginx_Install.sh +++ /dev/null @@ -1,177 +0,0 @@ -#!/bin/bash -echo "Thanks for trying Remotely!" -echo - -Args=( "$@" ) -ArgLength=${#Args[@]} - -for (( i=0; i<${ArgLength}; i+=2 )); -do - if [ "${Args[$i]}" = "--host" ]; then - HostName="${Args[$i+1]}" - elif [ "${Args[$i]}" = "--approot" ]; then - AppRoot="${Args[$i+1]}" - fi -done - -if [ -z "$AppRoot" ]; then - read -p "Enter path where the Remotely server files should be installed (typically /var/www/remotely): " AppRoot - if [ -z "$AppRoot" ]; then - AppRoot="/var/www/remotely" - fi -fi - -if [ -z "$HostName" ]; then - read -p "Enter server host (e.g. remotely.yourdomainname.com): " HostName -fi - -chmod +x "$AppRoot/Remotely_Server" - -echo "Using $AppRoot as the Remotely website's content directory." - -apt-get -y install curl -apt-get -y install software-properties-common -apt-get -y install gnupg - -UbuntuVersion=$(lsb_release -r -s) -UbuntuVersionInt=$(("${UbuntuVersion/./}")) - -# Install .NET Core Runtime. -if [ $UbuntuVersionInt -ge 2204 ]; then - apt-get install -y aspnetcore-runtime-6.0 -else - wget -q https://packages.microsoft.com/config/ubuntu/$UbuntuVersion/packages-microsoft-prod.deb - dpkg -i packages-microsoft-prod.deb - add-apt-repository universe - apt-get update - apt-get -y install apt-transport-https - apt-get -y install aspnetcore-runtime-6.0 - rm packages-microsoft-prod.deb -fi - - - - # Install other prerequisites. -apt-get -y install unzip -apt-get -y install acl -apt-get -y install libc6-dev -apt-get -y install libgdiplus - - -# Set permissions on Remotely files. -setfacl -R -m u:www-data:rwx $AppRoot -chown -R "$USER":www-data $AppRoot -chmod +x "$AppRoot/Remotely_Server" - - -# Install Nginx -apt-get update -apt-get -y install nginx - -systemctl start nginx - - -# Configure Nginx -nginxConfig=" - -server { - listen 80; - server_name $HostName *.$HostName; - location / { - proxy_pass http://localhost:5000; - proxy_http_version 1.1; - proxy_set_header Upgrade \$http_upgrade; - proxy_set_header Connection keep-alive; - proxy_set_header Host \$host; - proxy_cache_bypass \$http_upgrade; - proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto \$scheme; - } - - location /_blazor { - proxy_pass http://localhost:5000; - proxy_http_version 1.1; - proxy_set_header Upgrade \$http_upgrade; - proxy_set_header Connection \"upgrade\"; - proxy_set_header Host \$host; - proxy_cache_bypass \$http_upgrade; - proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto \$scheme; - } - location /AgentHub { - proxy_pass http://localhost:5000; - proxy_http_version 1.1; - proxy_set_header Upgrade \$http_upgrade; - proxy_set_header Connection \"upgrade\"; - proxy_set_header Host \$host; - proxy_cache_bypass \$http_upgrade; - proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto \$scheme; - } - location /ViewerHub { - proxy_pass http://localhost:5000; - proxy_http_version 1.1; - proxy_set_header Upgrade \$http_upgrade; - proxy_set_header Connection \"upgrade\"; - proxy_set_header Host \$host; - proxy_cache_bypass \$http_upgrade; - proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto \$scheme; - } - location /CasterHub { - proxy_pass http://localhost:5000; - proxy_http_version 1.1; - proxy_set_header Upgrade \$http_upgrade; - proxy_set_header Connection \"upgrade\"; - proxy_set_header Host \$host; - proxy_cache_bypass \$http_upgrade; - proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto \$scheme; - } -}" - -echo "$nginxConfig" > /etc/nginx/sites-available/remotely - -ln -s /etc/nginx/sites-available/remotely /etc/nginx/sites-enabled/remotely - -# Test config. -nginx -t - -# Reload. -nginx -s reload - - - - -# Create service. - -serviceConfig="[Unit] -Description=Remotely Server - -[Service] -WorkingDirectory=$AppRoot -ExecStart=/usr/bin/dotnet $AppRoot/Remotely_Server.dll -Restart=always -# Restart service after 10 seconds if the dotnet service crashes: -RestartSec=10 -SyslogIdentifier=remotely -User=www-data -Environment=ASPNETCORE_ENVIRONMENT=Production -Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false - -[Install] -WantedBy=multi-user.target" - -echo "$serviceConfig" > /etc/systemd/system/remotely.service - - -# Enable service. -systemctl enable remotely.service -# Start service. -systemctl restart remotely.service - - -# Install Certbot and get SSL cert. -apt-get -y install certbot python3-certbot-nginx - -certbot --nginx \ No newline at end of file diff --git a/Server.Installer/Server.Installer.csproj b/Server.Installer/Server.Installer.csproj deleted file mode 100644 index 107a54c52..000000000 --- a/Server.Installer/Server.Installer.csproj +++ /dev/null @@ -1,42 +0,0 @@ - - - - Exe - net6.0 - Remotely_Server_Installer - Remotely_Icon.ico - Remotely.Server.Installer - app.manifest - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Server.Installer/Services/GitHubApi.cs b/Server.Installer/Services/GitHubApi.cs deleted file mode 100644 index 24b470afd..000000000 --- a/Server.Installer/Services/GitHubApi.cs +++ /dev/null @@ -1,179 +0,0 @@ -using Remotely.Server.Installer.Models; -using Remotely.Shared.Utilities; -using Server.Installer.Models; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net.Http; -using System.Net.Http.Headers; -using System.Net.Http.Json; -using System.Text; -using System.Text.Json; -using System.Threading.Tasks; - -namespace Server.Installer.Services -{ - public interface IGitHubApi : IDisposable - { - Task DownloadArtifact(CliParams cliParams, string artifactDownloadUrl, string downloadToPath); - - Task GetLatestBuildArtifact(CliParams cliParams); - - Task TriggerDispatch(CliParams cliParams); - Task GetLatestReleaseTag(); - } - public class GitHubApi : IGitHubApi - { - private readonly string _apiHost = "https://api.github.com"; - private readonly HttpClient _httpClient; - public GitHubApi() - { - _httpClient = new HttpClient(); - _httpClient.Timeout = TimeSpan.FromHours(8); - _httpClient.DefaultRequestHeaders.Add("User-Agent", "Remotely Server Installer"); - } - - public void Dispose() - { - _httpClient?.Dispose(); - GC.SuppressFinalize(this); - } - - public async Task DownloadArtifact(CliParams cliParams, string artifactDownloadUrl, string downloadToPath) - { - try - { - ConsoleHelper.WriteLine("Downloading build artifact."); - - var message = GetHttpRequestMessage(HttpMethod.Get, artifactDownloadUrl, cliParams); - - var response = await _httpClient.SendAsync(message); - - ConsoleHelper.WriteLine($"Download artifact response status code: {response.StatusCode}"); - - if (!response.IsSuccessStatusCode) - { - ConsoleHelper.WriteError("GitHub API call to download build artifact failed. Please check your input parameters."); - Environment.Exit(1); - } - - using var responseStream = await response.Content.ReadAsStreamAsync(); - using var fileStream = new FileStream(downloadToPath, FileMode.Create); - - await responseStream.CopyToAsync(fileStream); - return true; - } - catch (Exception ex) - { - ConsoleHelper.WriteError($"Error while downloading artifact. Message: {ex.Message}"); - return false; - } - } - - public async Task GetLatestBuildArtifact(CliParams cliParams) - { - try - { - var message = GetHttpRequestMessage(HttpMethod.Get, - $"{_apiHost}/repos/{cliParams.GitHubUsername}/Remotely/actions/artifacts", - cliParams); - - var response = await _httpClient.SendAsync(message); - - ConsoleHelper.WriteLine($"Get artifacts response status code: {response.StatusCode}"); - - if (!response.IsSuccessStatusCode) - { - ConsoleHelper.WriteError("GitHub API call to get build artifacts failed. Please check your input parameters."); - Environment.Exit(1); - } - - var payload = await response.Content.ReadFromJsonAsync(); - if (payload?.artifacts?.Any() != true) - { - return null; - } - - return payload.artifacts - .OrderByDescending(x => x.created_at) - .FirstOrDefault(x=>x.name.Equals("Server", StringComparison.OrdinalIgnoreCase)); - } - catch (Exception ex) - { - ConsoleHelper.WriteError("Error while trying to retrieve build artifacts." + - $"Error: {ex.Message}"); - Environment.Exit(1); - } - - return null; - } - - public async Task GetLatestReleaseTag() - { - try - { - var response = await _httpClient.GetFromJsonAsync("https://api.github.com/repos/immense/Remotely/releases/latest"); - return response.tag_name; - } - catch (Exception ex) - { - ConsoleHelper.WriteError("Error while trying to retrieve release info." + - $"Error: {ex.Message}"); - Environment.Exit(1); - } - return string.Empty; - } - - public async Task TriggerDispatch(CliParams cliParams) - { - try - { - ConsoleHelper.WriteLine("Trigger GitHub Actions build."); - - - var message = GetHttpRequestMessage( - HttpMethod.Post, - $"{_apiHost}/repos/{cliParams.GitHubUsername}/Remotely/actions/workflows/build.yml/dispatches", - cliParams); - - var rid = EnvironmentHelper.IsLinux ? - "linux-x64" : - "win-x64"; - - var body = new - { - @ref = cliParams.Reference, - inputs = new - { - serverUrl = cliParams.ServerUrl.ToString(), - rid = rid - } - }; - message.Content = new StringContent(JsonSerializer.Serialize(body)); - - var response = await _httpClient.SendAsync(message); - - ConsoleHelper.WriteLine($"Dispatch response status code: {response.StatusCode}"); - - return response.IsSuccessStatusCode; - } - catch (Exception ex) - { - ConsoleHelper.WriteError($"Error: {ex.Message}"); - } - return false; - } - - private HttpRequestMessage GetHttpRequestMessage(HttpMethod method, string url, CliParams cliParams) - { - var message = new HttpRequestMessage(method, url); - - var base64Auth = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{cliParams.GitHubUsername}:{cliParams.GitHubPat}")); - message.Headers.Authorization = new AuthenticationHeaderValue("Basic", base64Auth); - message.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/vnd.github.v3+json")); - - return message; - } - } -} diff --git a/Server.Installer/Services/ServerInstaller.cs b/Server.Installer/Services/ServerInstaller.cs deleted file mode 100644 index 607939e15..000000000 --- a/Server.Installer/Services/ServerInstaller.cs +++ /dev/null @@ -1,200 +0,0 @@ -using Remotely.Shared.Utilities; -using Server.Installer.Models; -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.IO.Compression; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Reflection; -using System.Text; -using System.Threading.Tasks; -using Remotely.Shared.Extensions; -using Immense.RemoteControl.Shared.Helpers; - -namespace Server.Installer.Services -{ - public interface IServerInstaller - { - Task PerformInstall(CliParams cliParams); - } - - public class ServerInstaller : IServerInstaller - { - private readonly IGitHubApi _githubApi; - - public ServerInstaller(IGitHubApi githubApi) - { - _githubApi = githubApi; - } - - public async Task PerformInstall(CliParams cliParams) - { - var zipPath = Path.Combine(Path.GetTempPath(), "Remotely_Server.zip"); - - if (cliParams.UsePrebuiltPackage == true) - { - ConsoleHelper.WriteLine("Downloading pre-built server package."); - - var releaseFile = cliParams.WebServer == WebServerType.IisWindows ? - "https://github.com/immense/Remotely/releases/latest/download/Remotely_Server_Win-x64.zip" : - "https://github.com/immense/Remotely/releases/latest/download/Remotely_Server_Linux-x64.zip"; - - using var httpClient = new HttpClient(); - var response = await httpClient.GetAsync(releaseFile); - var contentLength = response.Content.Headers.ContentLength; - - using var webStream = await response.Content.ReadAsStreamAsync(); - using var fileStream = new FileStream(zipPath, FileMode.Create); - - var progress = 0; - - await webStream.CopyToAsync(fileStream, (int bytesRead) => - { - if (contentLength is null || - contentLength <= 0) - { - return; - - } - - var newProgress = (double)bytesRead / contentLength * 100; - - if (newProgress == 100 || - newProgress - progress > 5) - { - progress = (int)newProgress; - ConsoleHelper.WriteLine($"Progress: {progress}%"); - } - }); - - } - else - { - var latestBuild = await _githubApi.GetLatestBuildArtifact(cliParams); - var latestBuildId = latestBuild?.id; - - if (cliParams.CreateNew == true) - { - var dispatchResult = await _githubApi.TriggerDispatch(cliParams); - - if (!dispatchResult) - { - ConsoleHelper.WriteError("GitHub API call to trigger build action failed. Do you have " + - "Actions enabled on your forked Remotely repo on the Actions tab? If not, enable them and try again. " + - "Otherwise, please check your input parameters."); - return; - } - - ConsoleHelper.WriteLine("Build action triggered successfully. Waiting for build completion."); - - while (latestBuild?.id == latestBuildId) - { - await Task.Delay(TimeSpan.FromMinutes(1)); - ConsoleHelper.WriteLine("Waiting for GitHub build completion."); - latestBuild = await _githubApi.GetLatestBuildArtifact(cliParams); - } - } - else if (latestBuild is null) - { - ConsoleHelper.WriteError("There are no existing build artifacts, and --create-new was not specified. Exiting."); - return; - } - - var downloadResult = await _githubApi.DownloadArtifact(cliParams, latestBuild.archive_download_url, zipPath); - - if (!downloadResult) - { - ConsoleHelper.WriteError("Downloading the build artifact was not successful."); - return; - } - - } - - // Files in use can't be overwritten in Windows. Stop the - // website process first. - if (cliParams.WebServer == WebServerType.IisWindows) - { - var w3wpProcs = Process.GetProcessesByName("w3wp"); - if (w3wpProcs.Length > 0) - { - Process.Start("powershell.exe", "-Command & \"{ Stop-WebAppPool -Name Remotely -ErrorAction SilentlyContinue }\"").WaitForExit(); - Process.Start("powershell.exe", "-Command & \"{ Stop-Website -Name Remotely -ErrorAction SilentlyContinue }\"").WaitForExit(); - - ConsoleHelper.WriteLine("Waiting for w3wp processes to close..."); - foreach (var proc in w3wpProcs) - { - try { proc.Kill(); } - catch { } - } - WaitHelper.WaitFor(() => Process.GetProcessesByName("w3wp").Length < w3wpProcs.Length, TimeSpan.FromMinutes(5), 100); - } - } - - ConsoleHelper.WriteLine("Extracting files."); - Directory.CreateDirectory(cliParams.InstallDirectory); - ZipFile.ExtractToDirectory(zipPath, cliParams.InstallDirectory, true); - - await LaunchExternalInstaller(cliParams); - } - - - private async Task LaunchExternalInstaller(CliParams cliParams) - { - ConsoleHelper.WriteLine("Launching install script for selected reverse proxy type."); - var resourcesPath = "Remotely.Server.Installer.Resources."; - - var fileName = cliParams.WebServer.Value switch - { - WebServerType.UbuntuCaddy => "Ubuntu_Caddy_Install.sh", - WebServerType.UbuntuNginx => "Ubuntu_Nginx_Install.sh", - WebServerType.CentOsCaddy => "CentOS_Caddy_Install.sh", - WebServerType.CentOsNginx => "CentOS_Nginx_Install.sh", - WebServerType.IisWindows => "IIS_Windows_Install.ps1", - _ => throw new Exception("Unrecognized reverse proxy type."), - }; - - var resourcesFile = resourcesPath + fileName; - var filePath = Path.Combine(Path.GetTempPath(), fileName); - - using (var mrs = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourcesFile)) - using (var fileStream = new FileStream(filePath, FileMode.Create)) - { - await mrs.CopyToAsync(fileStream); - } - - var scriptResult = false; - ProcessStartInfo psi; - - if (cliParams.WebServer.Value == WebServerType.IisWindows) - { - psi = new ProcessStartInfo("powershell.exe") - { - Arguments = $"-f \"{filePath}\" -AppPoolName Remotely -SiteName Remotely " + - $"-SitePath \"{cliParams.InstallDirectory}\" -HostName {cliParams.ServerUrl.Authority} -Quiet", - WorkingDirectory = cliParams.InstallDirectory - }; - } - else - { - Process.Start("sudo", $"chmod +x {filePath}").WaitForExit(); - psi = new ProcessStartInfo("sudo") - { - Arguments = $"\"{filePath}\" --host {cliParams.ServerUrl.Authority} --approot {cliParams.InstallDirectory}", - WorkingDirectory = cliParams.InstallDirectory - }; - } - - var proc = Process.Start(psi); - - scriptResult = await Task.Run(() => proc.WaitForExit((int)TimeSpan.FromMinutes(30).TotalMilliseconds)); - - if (!scriptResult) - { - ConsoleHelper.WriteError("Installer script is taking longer than expected."); - } - } - } -} diff --git a/Server.Installer/app.manifest b/Server.Installer/app.manifest deleted file mode 100644 index d72e75011..000000000 --- a/Server.Installer/app.manifest +++ /dev/null @@ -1,76 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Server/API/ClientDownloadsController.cs b/Server/API/ClientDownloadsController.cs index 86e51f6fa..8b3442653 100644 --- a/Server/API/ClientDownloadsController.cs +++ b/Server/API/ClientDownloadsController.cs @@ -1,12 +1,19 @@ -using Microsoft.AspNetCore.Hosting; +using MailKit.Search; +using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Extensions; using Microsoft.AspNetCore.Mvc; using Remotely.Server.Auth; using Remotely.Server.Services; +using Remotely.Shared; +using Remotely.Shared.Models; +using Remotely.Shared.Services; +using Remotely.Shared.Utilities; using System; using System.Collections.Generic; using System.IO; using System.Text; +using System.Text.Json; using System.Threading; using System.Threading.Tasks; @@ -18,16 +25,19 @@ public class ClientDownloadsController : ControllerBase { private readonly IApplicationConfig _appConfig; private readonly IDataService _dataService; + private readonly IEmbeddedServerDataSearcher _embeddedDataSearcher; private readonly SemaphoreSlim _fileLock = new(1,1); private readonly IWebHostEnvironment _hostEnv; public ClientDownloadsController( IWebHostEnvironment hostEnv, IDataService dataService, - IApplicationConfig appConfig) + IApplicationConfig appConfig, + IEmbeddedServerDataSearcher embeddedDataSearcher) { _hostEnv = hostEnv; _appConfig = appConfig; _dataService = dataService; + _embeddedDataSearcher = embeddedDataSearcher; } [HttpGet("desktop/{platformID}")] @@ -133,31 +143,16 @@ private async Task GetBashInstaller(string fileName, string organ private async Task GetDesktopFile(string filePath, string organizationId = null) { - string relayCode; + var serverUrl = $"{Request.Scheme}://{Request.Host}"; + var embeddedData = new EmbeddedServerData(new Uri(serverUrl), organizationId); + var result = await _embeddedDataSearcher.GetRewrittenStream(filePath, embeddedData); - if (!string.IsNullOrWhiteSpace(organizationId)) + if (!result.IsSuccess) { - var currentOrg = _dataService.GetOrganizationById(organizationId); - relayCode = currentOrg.RelayCode; + throw result.Exception; } - else - { - relayCode = await _dataService.GetDefaultRelayCode(); - } - - var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read); - var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(filePath); - - if (!string.IsNullOrWhiteSpace(relayCode)) - { - var downloadFileName = fileNameWithoutExtension + $"-[{relayCode}]" + Path.GetExtension(filePath); - return File(fs, "application/octet-stream", downloadFileName); - } - else - { - return File(fs, "application/octet-stream", Path.GetFileName(filePath)); - } + return File(result.Value, "application/octet-stream", Path.GetFileName(filePath)); } private async Task GetInstallFile(string organizationId, string platformID) @@ -171,21 +166,23 @@ private async Task GetInstallFile(string organizationId, string p case "WindowsInstaller": { var filePath = Path.Combine(_hostEnv.WebRootPath, "Content", "Remotely_Installer.exe"); - var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read); - var organization = _dataService.GetOrganizationById(organizationId); - var relayCode = organization.RelayCode; - return File(fs, "application/octet-stream", $"Remotely_Install-[{relayCode}].exe"); + var serverUrl = $"{Request.Scheme}://{Request.Host}"; + var embeddedData = new EmbeddedServerData(new Uri(serverUrl), organizationId); + var result = await _embeddedDataSearcher.GetRewrittenStream(filePath, embeddedData); + + if (!result.IsSuccess) + { + throw result.Exception; + } + + return File(result.Value, "application/octet-stream", "Remotely_Installer.exe"); } - // TODO: Remove after a few releases. - case "Manjaro-x64": case "ManjaroInstaller-x64": { var fileName = "Install-Manjaro-x64.sh"; return await GetBashInstaller(fileName, organizationId); } - // TODO: Remove after a few releases. - case "Ubuntu-x64": case "UbuntuInstaller-x64": { var fileName = "Install-Ubuntu-x64.sh"; diff --git a/Server/API/RelayController.cs b/Server/API/RelayController.cs deleted file mode 100644 index d9833f57f..000000000 --- a/Server/API/RelayController.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; -using Remotely.Server.Services; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace Remotely.Server.API -{ - [Route("api/[controller]")] - [ApiController] - public class RelayController : ControllerBase - { - private readonly IDataService _dataService; - - public RelayController(IDataService dataService) - { - _dataService = dataService; - } - - [HttpGet("{relayCode}")] - public async Task Get(string relayCode) - { - var organization = await _dataService.GetOrganizationByRelayCode(relayCode); - return organization?.ID; - } - } -} diff --git a/Server/Areas/Identity/Pages/Account/Register.cshtml.cs b/Server/Areas/Identity/Pages/Account/Register.cshtml.cs index d8301993b..8148d37d4 100644 --- a/Server/Areas/Identity/Pages/Account/Register.cshtml.cs +++ b/Server/Areas/Identity/Pages/Account/Register.cshtml.cs @@ -99,12 +99,6 @@ public async Task OnPostAsync(string returnUrl = null) IsAdministrator = true }; - do - { - user.Organization.RelayCode = new string(Guid.NewGuid().ToString().Take(4).ToArray()); - } - while (await _dataService.GetOrganizationByRelayCode(user.Organization.RelayCode) != null); - var result = await _userManager.CreateAsync(user, Input.Password); if (result.Succeeded) { diff --git a/Server/Components/Devices/DeviceCard.razor.cs b/Server/Components/Devices/DeviceCard.razor.cs index 7d25826c0..df99339a8 100644 --- a/Server/Components/Devices/DeviceCard.razor.cs +++ b/Server/Components/Devices/DeviceCard.razor.cs @@ -27,6 +27,7 @@ public partial class DeviceCard : AuthComponentBase, IDisposable private readonly ConcurrentDictionary _fileUploadProgressLookup = new(); private ElementReference _card; private Theme _theme; + private Version _currentVersion = new(); [Parameter] public Device Device { get; set; } @@ -44,6 +45,9 @@ public partial class DeviceCard : AuthComponentBase, IDisposable [Inject] private IServiceHubSessionCache ServiceSessionCache { get; init; } + [Inject] + private IUpgradeService UpgradeService { get; init; } + [Inject] private IDataService DataService { get; set; } @@ -51,7 +55,7 @@ public partial class DeviceCard : AuthComponentBase, IDisposable private bool IsOutdated => Version.TryParse(Device.AgentVersion, out var result) && - result < ParentFrame.HighestVersion; + result < _currentVersion; private bool IsSelected => AppState.DevicesFrameSelectedDevices.Contains(Device.ID); @@ -74,6 +78,7 @@ protected override async Task OnInitializedAsync() { await base.OnInitializedAsync(); _theme = await AppState.GetEffectiveTheme(); + _currentVersion = UpgradeService.GetCurrentVersion(); AppState.PropertyChanged += AppState_PropertyChanged; CircuitConnection.MessageReceived += CircuitConnection_MessageReceived; } diff --git a/Server/Components/Devices/DevicesFrame.razor.cs b/Server/Components/Devices/DevicesFrame.razor.cs index 86adc3fda..f9415df68 100644 --- a/Server/Components/Devices/DevicesFrame.razor.cs +++ b/Server/Components/Devices/DevicesFrame.razor.cs @@ -40,8 +40,6 @@ public partial class DevicesFrame : AuthComponentBase, IDisposable private string _selectedSortProperty = "DeviceName"; private ListSortDirection _sortDirection; - public Version HighestVersion { get; private set; } - [Inject] private IClientAppState AppState { get; set; } @@ -296,8 +294,6 @@ private void LoadDevices() .ToList(); _allDevices.AddRange(devices); - - HighestVersion = _allDevices.Max(x => Version.TryParse(x.AgentVersion, out var result) ? result : default); } FilterDevices(); diff --git a/Server/DockerMain.sh b/Server/DockerMain.sh index ea81e05d7..ccb0da0d1 100644 --- a/Server/DockerMain.sh +++ b/Server/DockerMain.sh @@ -2,22 +2,21 @@ echo "Entered main script." -ServerDir=/var/www/remotely RemotelyData=/remotely-data AppSettingsVolume=/remotely-data/appsettings.json -AppSettingsWww=/var/www/remotely/appsettings.json +AppSettingsSrc=/app/appsettings.json if [ ! -f "$AppSettingsVolume" ]; then echo "Copying appsettings.json to volume." - cp "$AppSettingsWww" "$AppSettingsVolume" + cp "$AppSettingsSrc" "$AppSettingsVolume" fi -if [ -f "$AppSettingsWww" ]; then - rm "$AppSettingsWww" +if [ -f "$AppSettingsSrc" ]; then + rm "$AppSettingsSrc" fi -ln -s "$AppSettingsVolume" "$AppSettingsWww" +ln -s "$AppSettingsVolume" "$AppSettingsSrc" echo "Starting Remotely server." -exec /usr/bin/dotnet /var/www/remotely/Remotely_Server.dll \ No newline at end of file +exec /usr/bin/dotnet /app/Remotely_Server.dll \ No newline at end of file diff --git a/Server/Dockerfile b/Server/Dockerfile index 0cc272a8a..647cd27ef 100644 --- a/Server/Dockerfile +++ b/Server/Dockerfile @@ -1,40 +1,31 @@ -FROM ubuntu:jammy +FROM mcr.microsoft.com/dotnet/aspnet:6.0-jammy + +SHELL ["/bin/bash", "-c"] EXPOSE 5000 ENV ASPNETCORE_ENVIRONMENT="Production" ENV ASPNETCORE_URLS="http://*:5000" -RUN \ - apt-get -y update && \ - apt-get -y install \ - apt-utils \ - wget \ - apt-transport-https \ - unzip \ - acl \ - libssl1.0 +WORKDIR /src -RUN \ - apt-get -y install aspnetcore-runtime-6.0 +COPY /_immense.Remotely/Server/DockerMain.sh . +COPY /_immense.Remotely/Server/linux-x64/Server.zip . RUN \ - mkdir -p /var/www/remotely && \ - mkdir /config && \ - wget -q https://github.com/immense/Remotely/releases/latest/download/Remotely_Server_Linux-x64.zip && \ - unzip -o Remotely_Server_Linux-x64.zip -d /var/www/remotely && \ - rm Remotely_Server_Linux-x64.zip + apt-get -y update && \ + apt-get -y install unzip && \ + unzip -o ./Server.zip -d /app && \ + rm ./Server.zip + +WORKDIR /app RUN \ mkdir -p /remotely-data && \ - sed -i 's/DataSource=Remotely.db/DataSource=\/remotely-data\/Remotely.db/' /var/www/remotely/appsettings.json + sed -i 's/DataSource=Remotely.db/DataSource=\/remotely-data\/Remotely.db/' ./appsettings.json VOLUME "/remotely-data" -WORKDIR /var/www/remotely - -COPY DockerMain.sh / - -RUN chmod 755 /DockerMain.sh +RUN chmod +x "/src/DockerMain.sh" -ENTRYPOINT ["/DockerMain.sh"] \ No newline at end of file +ENTRYPOINT ["/src/DockerMain.sh"] \ No newline at end of file diff --git a/Server/Dockerfile-old b/Server/Dockerfile-old new file mode 100644 index 000000000..ccba22ee7 --- /dev/null +++ b/Server/Dockerfile-old @@ -0,0 +1,40 @@ +FROM ubuntu:jammy + +EXPOSE 5000 + +ENV ASPNETCORE_ENVIRONMENT="Production" +ENV ASPNETCORE_URLS="http://*:5000" + +RUN \ + apt-get -y update && \ + apt-get -y install \ + apt-utils \ + wget \ + apt-transport-https \ + unzip \ + acl \ + libssl1.0 + +RUN \ + apt-get -y install aspnetcore-runtime-6.0 + +RUN \ + mkdir -p /app && \ + mkdir /config && \ + wget -q https://github.com/immense/Remotely/releases/latest/download/Remotely_Server_Linux-x64.zip && \ + unzip -o Remotely_Server_Linux-x64.zip -d /app && \ + rm Remotely_Server_Linux-x64.zip + +RUN \ + mkdir -p /remotely-data && \ + sed -i 's/DataSource=Remotely.db/DataSource=\/remotely-data\/Remotely.db/' /app/appsettings.json + +VOLUME "/remotely-data" + +WORKDIR /app + +COPY DockerMain.sh / + +RUN chmod 755 /DockerMain.sh + +ENTRYPOINT ["/DockerMain.sh"] \ No newline at end of file diff --git a/Server/Migrations/PostgreSql/20221231192625_Remove RelayCode.Designer.cs b/Server/Migrations/PostgreSql/20221231192625_Remove RelayCode.Designer.cs new file mode 100644 index 000000000..e468c66b4 --- /dev/null +++ b/Server/Migrations/PostgreSql/20221231192625_Remove RelayCode.Designer.cs @@ -0,0 +1,1216 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Remotely.Server.Data; + +#nullable disable + +namespace Remotely.Server.Migrations.PostgreSql +{ + [DbContext(typeof(PostgreSqlDbContext))] + [Migration("20221231192625_Remove RelayCode")] + partial class RemoveRelayCode + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.9") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("DeviceGroupRemotelyUser", b => + { + b.Property("DeviceGroupsID") + .HasColumnType("text"); + + b.Property("UsersId") + .HasColumnType("text"); + + b.HasKey("DeviceGroupsID", "UsersId"); + + b.HasIndex("UsersId"); + + b.ToTable("DeviceGroupRemotelyUser"); + }); + + modelBuilder.Entity("DeviceGroupScriptSchedule", b => + { + b.Property("DeviceGroupsID") + .HasColumnType("text"); + + b.Property("ScriptSchedulesId") + .HasColumnType("integer"); + + b.HasKey("DeviceGroupsID", "ScriptSchedulesId"); + + b.HasIndex("ScriptSchedulesId"); + + b.ToTable("DeviceGroupScriptSchedule"); + }); + + modelBuilder.Entity("DeviceScriptRun", b => + { + b.Property("DevicesID") + .HasColumnType("text"); + + b.Property("ScriptRunsId") + .HasColumnType("integer"); + + b.HasKey("DevicesID", "ScriptRunsId"); + + b.HasIndex("ScriptRunsId"); + + b.ToTable("DeviceScriptRun"); + }); + + modelBuilder.Entity("DeviceScriptRun1", b => + { + b.Property("DevicesCompletedID") + .HasColumnType("text"); + + b.Property("ScriptRunsCompletedId") + .HasColumnType("integer"); + + b.HasKey("DevicesCompletedID", "ScriptRunsCompletedId"); + + b.HasIndex("ScriptRunsCompletedId"); + + b.ToTable("DeviceScriptRun1"); + }); + + modelBuilder.Entity("DeviceScriptSchedule", b => + { + b.Property("DevicesID") + .HasColumnType("text"); + + b.Property("ScriptSchedulesId") + .HasColumnType("integer"); + + b.HasKey("DevicesID", "ScriptSchedulesId"); + + b.HasIndex("ScriptSchedulesId"); + + b.ToTable("DeviceScriptSchedule"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Discriminator") + .IsRequired() + .HasColumnType("text"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("RemotelyUsers", (string)null); + + b.HasDiscriminator("Discriminator").HasValue("IdentityUser"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("ProviderKey") + .HasColumnType("text"); + + b.Property("ProviderDisplayName") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("text"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("Remotely.Shared.Models.Alert", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("Details") + .HasColumnType("text"); + + b.Property("DeviceID") + .HasColumnType("text"); + + b.Property("Message") + .HasColumnType("text"); + + b.Property("OrganizationID") + .HasColumnType("text"); + + b.Property("UserID") + .HasColumnType("text"); + + b.HasKey("ID"); + + b.HasIndex("DeviceID"); + + b.HasIndex("OrganizationID"); + + b.HasIndex("UserID"); + + b.ToTable("Alerts"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.ApiToken", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("LastUsed") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("OrganizationID") + .HasColumnType("text"); + + b.Property("Secret") + .HasColumnType("text"); + + b.HasKey("ID"); + + b.HasIndex("OrganizationID"); + + b.ToTable("ApiTokens"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.BrandingInfo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("ButtonForegroundBlue") + .HasColumnType("smallint"); + + b.Property("ButtonForegroundGreen") + .HasColumnType("smallint"); + + b.Property("ButtonForegroundRed") + .HasColumnType("smallint"); + + b.Property("Icon") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("OrganizationId") + .HasColumnType("text"); + + b.Property("Product") + .IsRequired() + .HasMaxLength(25) + .HasColumnType("character varying(25)"); + + b.Property("TitleBackgroundBlue") + .HasColumnType("smallint"); + + b.Property("TitleBackgroundGreen") + .HasColumnType("smallint"); + + b.Property("TitleBackgroundRed") + .HasColumnType("smallint"); + + b.Property("TitleForegroundBlue") + .HasColumnType("smallint"); + + b.Property("TitleForegroundGreen") + .HasColumnType("smallint"); + + b.Property("TitleForegroundRed") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .IsUnique(); + + b.ToTable("BrandingInfos"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.Device", b => + { + b.Property("ID") + .HasColumnType("text"); + + b.Property("AgentVersion") + .HasColumnType("text"); + + b.Property("Alias") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CpuUtilization") + .HasColumnType("double precision"); + + b.Property("CurrentUser") + .HasColumnType("text"); + + b.Property("DeviceGroupID") + .HasColumnType("text"); + + b.Property("DeviceName") + .HasColumnType("text"); + + b.Property("Drives") + .HasColumnType("text"); + + b.Property("Is64Bit") + .HasColumnType("boolean"); + + b.Property("IsOnline") + .HasColumnType("boolean"); + + b.Property("LastOnline") + .HasColumnType("timestamp with time zone"); + + b.Property("Notes") + .HasMaxLength(5000) + .HasColumnType("character varying(5000)"); + + b.Property("OSArchitecture") + .HasColumnType("integer"); + + b.Property("OSDescription") + .HasColumnType("text"); + + b.Property("OrganizationID") + .HasColumnType("text"); + + b.Property("Platform") + .HasColumnType("text"); + + b.Property("ProcessorCount") + .HasColumnType("integer"); + + b.Property("PublicIP") + .HasColumnType("text"); + + b.Property("ServerVerificationToken") + .HasColumnType("text"); + + b.Property("Tags") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TotalMemory") + .HasColumnType("double precision"); + + b.Property("TotalStorage") + .HasColumnType("double precision"); + + b.Property("UsedMemory") + .HasColumnType("double precision"); + + b.Property("UsedStorage") + .HasColumnType("double precision"); + + b.HasKey("ID"); + + b.HasIndex("DeviceGroupID"); + + b.HasIndex("DeviceName"); + + b.HasIndex("OrganizationID"); + + b.ToTable("Devices"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.DeviceGroup", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("OrganizationID") + .HasColumnType("text"); + + b.HasKey("ID"); + + b.HasIndex("OrganizationID"); + + b.ToTable("DeviceGroups"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.EventLog", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("EventType") + .HasColumnType("integer"); + + b.Property("Message") + .HasColumnType("text"); + + b.Property("OrganizationID") + .HasColumnType("text"); + + b.Property("Source") + .HasColumnType("text"); + + b.Property("StackTrace") + .HasColumnType("text"); + + b.Property("TimeStamp") + .HasColumnType("timestamp with time zone"); + + b.HasKey("ID"); + + b.HasIndex("OrganizationID"); + + b.ToTable("EventLogs"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.InviteLink", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("DateSent") + .HasColumnType("timestamp with time zone"); + + b.Property("InvitedUser") + .HasColumnType("text"); + + b.Property("IsAdmin") + .HasColumnType("boolean"); + + b.Property("OrganizationID") + .HasColumnType("text"); + + b.Property("ResetUrl") + .HasColumnType("text"); + + b.HasKey("ID"); + + b.HasIndex("OrganizationID"); + + b.ToTable("InviteLinks"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.Organization", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("IsDefaultOrganization") + .HasColumnType("boolean"); + + b.Property("OrganizationName") + .HasMaxLength(25) + .HasColumnType("character varying(25)"); + + b.HasKey("ID"); + + b.ToTable("Organizations"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.SavedScript", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Content") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatorId") + .HasColumnType("text"); + + b.Property("FolderPath") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("GenerateAlertOnError") + .HasColumnType("boolean"); + + b.Property("IsPublic") + .HasColumnType("boolean"); + + b.Property("IsQuickScript") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("OrganizationID") + .HasColumnType("text"); + + b.Property("SendEmailOnError") + .HasColumnType("boolean"); + + b.Property("SendErrorEmailTo") + .HasColumnType("text"); + + b.Property("Shell") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CreatorId"); + + b.HasIndex("OrganizationID"); + + b.ToTable("SavedScripts"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.ScriptResult", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("DeviceID") + .HasColumnType("text"); + + b.Property("ErrorOutput") + .HasColumnType("text"); + + b.Property("HadErrors") + .HasColumnType("boolean"); + + b.Property("InputType") + .HasColumnType("integer"); + + b.Property("OrganizationID") + .HasColumnType("text"); + + b.Property("RunTime") + .HasColumnType("interval"); + + b.Property("SavedScriptId") + .HasColumnType("uuid"); + + b.Property("ScheduleId") + .HasColumnType("integer"); + + b.Property("ScriptInput") + .HasColumnType("text"); + + b.Property("ScriptRunId") + .HasColumnType("integer"); + + b.Property("SenderConnectionID") + .HasColumnType("text"); + + b.Property("SenderUserName") + .HasColumnType("text"); + + b.Property("Shell") + .HasColumnType("integer"); + + b.Property("StandardOutput") + .HasColumnType("text"); + + b.Property("TimeStamp") + .HasColumnType("timestamp with time zone"); + + b.HasKey("ID"); + + b.HasIndex("DeviceID"); + + b.HasIndex("OrganizationID"); + + b.HasIndex("ScheduleId"); + + b.HasIndex("ScriptRunId"); + + b.ToTable("ScriptResults"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.ScriptRun", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Initiator") + .HasColumnType("text"); + + b.Property("InputType") + .HasColumnType("integer"); + + b.Property("OrganizationID") + .HasColumnType("text"); + + b.Property("RunAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RunOnNextConnect") + .HasColumnType("boolean"); + + b.Property("SavedScriptId") + .HasColumnType("uuid"); + + b.Property("ScheduleId") + .HasColumnType("integer"); + + b.Property("ScriptScheduleId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationID"); + + b.HasIndex("ScriptScheduleId"); + + b.ToTable("ScriptRuns"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.ScriptSchedule", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatorId") + .HasColumnType("text"); + + b.Property("Interval") + .HasColumnType("integer"); + + b.Property("LastRun") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("NextRun") + .HasColumnType("timestamp with time zone"); + + b.Property("OrganizationID") + .HasColumnType("text"); + + b.Property("RunOnNextConnect") + .HasColumnType("boolean"); + + b.Property("SavedScriptId") + .HasColumnType("uuid"); + + b.Property("StartAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("CreatorId"); + + b.HasIndex("OrganizationID"); + + b.ToTable("ScriptSchedules"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.SharedFile", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("ContentType") + .HasColumnType("text"); + + b.Property("FileContents") + .HasColumnType("bytea"); + + b.Property("FileName") + .HasColumnType("text"); + + b.Property("OrganizationID") + .HasColumnType("text"); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone"); + + b.HasKey("ID"); + + b.HasIndex("OrganizationID"); + + b.ToTable("SharedFiles"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.RemotelyUser", b => + { + b.HasBaseType("Microsoft.AspNetCore.Identity.IdentityUser"); + + b.Property("IsAdministrator") + .HasColumnType("boolean"); + + b.Property("IsServerAdmin") + .HasColumnType("boolean"); + + b.Property("OrganizationID") + .HasColumnType("text"); + + b.Property("TempPassword") + .HasColumnType("text"); + + b.Property("UserOptions") + .HasColumnType("text"); + + b.HasIndex("OrganizationID"); + + b.HasIndex("UserName"); + + b.HasDiscriminator().HasValue("RemotelyUser"); + }); + + modelBuilder.Entity("DeviceGroupRemotelyUser", b => + { + b.HasOne("Remotely.Shared.Models.DeviceGroup", null) + .WithMany() + .HasForeignKey("DeviceGroupsID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Remotely.Shared.Models.RemotelyUser", null) + .WithMany() + .HasForeignKey("UsersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("DeviceGroupScriptSchedule", b => + { + b.HasOne("Remotely.Shared.Models.DeviceGroup", null) + .WithMany() + .HasForeignKey("DeviceGroupsID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Remotely.Shared.Models.ScriptSchedule", null) + .WithMany() + .HasForeignKey("ScriptSchedulesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("DeviceScriptRun", b => + { + b.HasOne("Remotely.Shared.Models.Device", null) + .WithMany() + .HasForeignKey("DevicesID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Remotely.Shared.Models.ScriptRun", null) + .WithMany() + .HasForeignKey("ScriptRunsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("DeviceScriptRun1", b => + { + b.HasOne("Remotely.Shared.Models.Device", null) + .WithMany() + .HasForeignKey("DevicesCompletedID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Remotely.Shared.Models.ScriptRun", null) + .WithMany() + .HasForeignKey("ScriptRunsCompletedId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("DeviceScriptSchedule", b => + { + b.HasOne("Remotely.Shared.Models.Device", null) + .WithMany() + .HasForeignKey("DevicesID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Remotely.Shared.Models.ScriptSchedule", null) + .WithMany() + .HasForeignKey("ScriptSchedulesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Remotely.Shared.Models.Alert", b => + { + b.HasOne("Remotely.Shared.Models.Device", "Device") + .WithMany("Alerts") + .HasForeignKey("DeviceID"); + + b.HasOne("Remotely.Shared.Models.Organization", "Organization") + .WithMany("Alerts") + .HasForeignKey("OrganizationID"); + + b.HasOne("Remotely.Shared.Models.RemotelyUser", "User") + .WithMany("Alerts") + .HasForeignKey("UserID"); + + b.Navigation("Device"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.ApiToken", b => + { + b.HasOne("Remotely.Shared.Models.Organization", "Organization") + .WithMany("ApiTokens") + .HasForeignKey("OrganizationID"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.BrandingInfo", b => + { + b.HasOne("Remotely.Shared.Models.Organization", "Organization") + .WithOne("BrandingInfo") + .HasForeignKey("Remotely.Shared.Models.BrandingInfo", "OrganizationId"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.Device", b => + { + b.HasOne("Remotely.Shared.Models.DeviceGroup", "DeviceGroup") + .WithMany("Devices") + .HasForeignKey("DeviceGroupID"); + + b.HasOne("Remotely.Shared.Models.Organization", "Organization") + .WithMany("Devices") + .HasForeignKey("OrganizationID"); + + b.Navigation("DeviceGroup"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.DeviceGroup", b => + { + b.HasOne("Remotely.Shared.Models.Organization", "Organization") + .WithMany("DeviceGroups") + .HasForeignKey("OrganizationID"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.EventLog", b => + { + b.HasOne("Remotely.Shared.Models.Organization", "Organization") + .WithMany("EventLogs") + .HasForeignKey("OrganizationID"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.InviteLink", b => + { + b.HasOne("Remotely.Shared.Models.Organization", "Organization") + .WithMany("InviteLinks") + .HasForeignKey("OrganizationID"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.SavedScript", b => + { + b.HasOne("Remotely.Shared.Models.RemotelyUser", "Creator") + .WithMany("SavedScripts") + .HasForeignKey("CreatorId"); + + b.HasOne("Remotely.Shared.Models.Organization", "Organization") + .WithMany("SavedScripts") + .HasForeignKey("OrganizationID"); + + b.Navigation("Creator"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.ScriptResult", b => + { + b.HasOne("Remotely.Shared.Models.Device", "Device") + .WithMany("ScriptResults") + .HasForeignKey("DeviceID"); + + b.HasOne("Remotely.Shared.Models.Organization", "Organization") + .WithMany("ScriptResults") + .HasForeignKey("OrganizationID"); + + b.HasOne("Remotely.Shared.Models.ScriptSchedule", "Schedule") + .WithMany() + .HasForeignKey("ScheduleId"); + + b.HasOne("Remotely.Shared.Models.ScriptRun", null) + .WithMany("Results") + .HasForeignKey("ScriptRunId"); + + b.Navigation("Device"); + + b.Navigation("Organization"); + + b.Navigation("Schedule"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.ScriptRun", b => + { + b.HasOne("Remotely.Shared.Models.Organization", "Organization") + .WithMany("ScriptRuns") + .HasForeignKey("OrganizationID"); + + b.HasOne("Remotely.Shared.Models.ScriptSchedule", null) + .WithMany("ScriptRuns") + .HasForeignKey("ScriptScheduleId"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.ScriptSchedule", b => + { + b.HasOne("Remotely.Shared.Models.RemotelyUser", "Creator") + .WithMany("ScriptSchedules") + .HasForeignKey("CreatorId"); + + b.HasOne("Remotely.Shared.Models.Organization", "Organization") + .WithMany("ScriptSchedules") + .HasForeignKey("OrganizationID"); + + b.Navigation("Creator"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.SharedFile", b => + { + b.HasOne("Remotely.Shared.Models.Organization", "Organization") + .WithMany("SharedFiles") + .HasForeignKey("OrganizationID"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.RemotelyUser", b => + { + b.HasOne("Remotely.Shared.Models.Organization", "Organization") + .WithMany("RemotelyUsers") + .HasForeignKey("OrganizationID"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.Device", b => + { + b.Navigation("Alerts"); + + b.Navigation("ScriptResults"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.DeviceGroup", b => + { + b.Navigation("Devices"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.Organization", b => + { + b.Navigation("Alerts"); + + b.Navigation("ApiTokens"); + + b.Navigation("BrandingInfo"); + + b.Navigation("DeviceGroups"); + + b.Navigation("Devices"); + + b.Navigation("EventLogs"); + + b.Navigation("InviteLinks"); + + b.Navigation("RemotelyUsers"); + + b.Navigation("SavedScripts"); + + b.Navigation("ScriptResults"); + + b.Navigation("ScriptRuns"); + + b.Navigation("ScriptSchedules"); + + b.Navigation("SharedFiles"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.ScriptRun", b => + { + b.Navigation("Results"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.ScriptSchedule", b => + { + b.Navigation("ScriptRuns"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.RemotelyUser", b => + { + b.Navigation("Alerts"); + + b.Navigation("SavedScripts"); + + b.Navigation("ScriptSchedules"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Server/Migrations/PostgreSql/20221231192625_Remove RelayCode.cs b/Server/Migrations/PostgreSql/20221231192625_Remove RelayCode.cs new file mode 100644 index 000000000..39e492896 --- /dev/null +++ b/Server/Migrations/PostgreSql/20221231192625_Remove RelayCode.cs @@ -0,0 +1,25 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Remotely.Server.Migrations.PostgreSql +{ + public partial class RemoveRelayCode : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "RelayCode", + table: "Organizations"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "RelayCode", + table: "Organizations", + type: "text", + nullable: true); + } + } +} diff --git a/Server/Migrations/PostgreSql/PostgreSqlDbContextModelSnapshot.cs b/Server/Migrations/PostgreSql/PostgreSqlDbContextModelSnapshot.cs index 2890fb458..e37535574 100644 --- a/Server/Migrations/PostgreSql/PostgreSqlDbContextModelSnapshot.cs +++ b/Server/Migrations/PostgreSql/PostgreSqlDbContextModelSnapshot.cs @@ -593,9 +593,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasMaxLength(25) .HasColumnType("character varying(25)"); - b.Property("RelayCode") - .HasColumnType("text"); - b.HasKey("ID"); b.ToTable("Organizations"); diff --git a/Server/Migrations/SqlServer/20221231192616_Remove RelayCode.Designer.cs b/Server/Migrations/SqlServer/20221231192616_Remove RelayCode.Designer.cs new file mode 100644 index 000000000..5b1518b65 --- /dev/null +++ b/Server/Migrations/SqlServer/20221231192616_Remove RelayCode.Designer.cs @@ -0,0 +1,1219 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Remotely.Server.Data; + +#nullable disable + +namespace Remotely.Server.Migrations.SqlServer +{ + [DbContext(typeof(SqlServerDbContext))] + [Migration("20221231192616_Remove RelayCode")] + partial class RemoveRelayCode + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.9") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder, 1L, 1); + + modelBuilder.Entity("DeviceGroupRemotelyUser", b => + { + b.Property("DeviceGroupsID") + .HasColumnType("nvarchar(450)"); + + b.Property("UsersId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("DeviceGroupsID", "UsersId"); + + b.HasIndex("UsersId"); + + b.ToTable("DeviceGroupRemotelyUser"); + }); + + modelBuilder.Entity("DeviceGroupScriptSchedule", b => + { + b.Property("DeviceGroupsID") + .HasColumnType("nvarchar(450)"); + + b.Property("ScriptSchedulesId") + .HasColumnType("int"); + + b.HasKey("DeviceGroupsID", "ScriptSchedulesId"); + + b.HasIndex("ScriptSchedulesId"); + + b.ToTable("DeviceGroupScriptSchedule"); + }); + + modelBuilder.Entity("DeviceScriptRun", b => + { + b.Property("DevicesID") + .HasColumnType("nvarchar(450)"); + + b.Property("ScriptRunsId") + .HasColumnType("int"); + + b.HasKey("DevicesID", "ScriptRunsId"); + + b.HasIndex("ScriptRunsId"); + + b.ToTable("DeviceScriptRun"); + }); + + modelBuilder.Entity("DeviceScriptRun1", b => + { + b.Property("DevicesCompletedID") + .HasColumnType("nvarchar(450)"); + + b.Property("ScriptRunsCompletedId") + .HasColumnType("int"); + + b.HasKey("DevicesCompletedID", "ScriptRunsCompletedId"); + + b.HasIndex("ScriptRunsCompletedId"); + + b.ToTable("DeviceScriptRun1"); + }); + + modelBuilder.Entity("DeviceScriptSchedule", b => + { + b.Property("DevicesID") + .HasColumnType("nvarchar(450)"); + + b.Property("ScriptSchedulesId") + .HasColumnType("int"); + + b.HasKey("DevicesID", "ScriptSchedulesId"); + + b.HasIndex("ScriptSchedulesId"); + + b.ToTable("DeviceScriptSchedule"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Discriminator") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("RemotelyUsers", (string)null); + + b.HasDiscriminator("Discriminator").HasValue("IdentityUser"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("Remotely.Shared.Models.Alert", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("nvarchar(450)"); + + b.Property("CreatedOn") + .HasColumnType("datetimeoffset"); + + b.Property("Details") + .HasColumnType("nvarchar(max)"); + + b.Property("DeviceID") + .HasColumnType("nvarchar(450)"); + + b.Property("Message") + .HasColumnType("nvarchar(max)"); + + b.Property("OrganizationID") + .HasColumnType("nvarchar(450)"); + + b.Property("UserID") + .HasColumnType("nvarchar(450)"); + + b.HasKey("ID"); + + b.HasIndex("DeviceID"); + + b.HasIndex("OrganizationID"); + + b.HasIndex("UserID"); + + b.ToTable("Alerts"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.ApiToken", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("nvarchar(450)"); + + b.Property("LastUsed") + .HasColumnType("datetimeoffset"); + + b.Property("Name") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("OrganizationID") + .HasColumnType("nvarchar(450)"); + + b.Property("Secret") + .HasColumnType("nvarchar(max)"); + + b.HasKey("ID"); + + b.HasIndex("OrganizationID"); + + b.ToTable("ApiTokens"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.BrandingInfo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("nvarchar(450)"); + + b.Property("ButtonForegroundBlue") + .HasColumnType("tinyint"); + + b.Property("ButtonForegroundGreen") + .HasColumnType("tinyint"); + + b.Property("ButtonForegroundRed") + .HasColumnType("tinyint"); + + b.Property("Icon") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("OrganizationId") + .HasColumnType("nvarchar(450)"); + + b.Property("Product") + .IsRequired() + .HasMaxLength(25) + .HasColumnType("nvarchar(25)"); + + b.Property("TitleBackgroundBlue") + .HasColumnType("tinyint"); + + b.Property("TitleBackgroundGreen") + .HasColumnType("tinyint"); + + b.Property("TitleBackgroundRed") + .HasColumnType("tinyint"); + + b.Property("TitleForegroundBlue") + .HasColumnType("tinyint"); + + b.Property("TitleForegroundGreen") + .HasColumnType("tinyint"); + + b.Property("TitleForegroundRed") + .HasColumnType("tinyint"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .IsUnique() + .HasFilter("[OrganizationId] IS NOT NULL"); + + b.ToTable("BrandingInfos"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.Device", b => + { + b.Property("ID") + .HasColumnType("nvarchar(450)"); + + b.Property("AgentVersion") + .HasColumnType("nvarchar(max)"); + + b.Property("Alias") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CpuUtilization") + .HasColumnType("float"); + + b.Property("CurrentUser") + .HasColumnType("nvarchar(max)"); + + b.Property("DeviceGroupID") + .HasColumnType("nvarchar(450)"); + + b.Property("DeviceName") + .HasColumnType("nvarchar(450)"); + + b.Property("Drives") + .HasColumnType("nvarchar(max)"); + + b.Property("Is64Bit") + .HasColumnType("bit"); + + b.Property("IsOnline") + .HasColumnType("bit"); + + b.Property("LastOnline") + .HasColumnType("datetimeoffset"); + + b.Property("Notes") + .HasMaxLength(5000) + .HasColumnType("nvarchar(max)"); + + b.Property("OSArchitecture") + .HasColumnType("int"); + + b.Property("OSDescription") + .HasColumnType("nvarchar(max)"); + + b.Property("OrganizationID") + .HasColumnType("nvarchar(450)"); + + b.Property("Platform") + .HasColumnType("nvarchar(max)"); + + b.Property("ProcessorCount") + .HasColumnType("int"); + + b.Property("PublicIP") + .HasColumnType("nvarchar(max)"); + + b.Property("ServerVerificationToken") + .HasColumnType("nvarchar(max)"); + + b.Property("Tags") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("TotalMemory") + .HasColumnType("float"); + + b.Property("TotalStorage") + .HasColumnType("float"); + + b.Property("UsedMemory") + .HasColumnType("float"); + + b.Property("UsedStorage") + .HasColumnType("float"); + + b.HasKey("ID"); + + b.HasIndex("DeviceGroupID"); + + b.HasIndex("DeviceName"); + + b.HasIndex("OrganizationID"); + + b.ToTable("Devices"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.DeviceGroup", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("OrganizationID") + .HasColumnType("nvarchar(450)"); + + b.HasKey("ID"); + + b.HasIndex("OrganizationID"); + + b.ToTable("DeviceGroups"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.EventLog", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("nvarchar(450)"); + + b.Property("EventType") + .HasColumnType("int"); + + b.Property("Message") + .HasColumnType("nvarchar(max)"); + + b.Property("OrganizationID") + .HasColumnType("nvarchar(450)"); + + b.Property("Source") + .HasColumnType("nvarchar(max)"); + + b.Property("StackTrace") + .HasColumnType("nvarchar(max)"); + + b.Property("TimeStamp") + .HasColumnType("datetimeoffset"); + + b.HasKey("ID"); + + b.HasIndex("OrganizationID"); + + b.ToTable("EventLogs"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.InviteLink", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("nvarchar(450)"); + + b.Property("DateSent") + .HasColumnType("datetimeoffset"); + + b.Property("InvitedUser") + .HasColumnType("nvarchar(max)"); + + b.Property("IsAdmin") + .HasColumnType("bit"); + + b.Property("OrganizationID") + .HasColumnType("nvarchar(450)"); + + b.Property("ResetUrl") + .HasColumnType("nvarchar(max)"); + + b.HasKey("ID"); + + b.HasIndex("OrganizationID"); + + b.ToTable("InviteLinks"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.Organization", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("nvarchar(450)"); + + b.Property("IsDefaultOrganization") + .HasColumnType("bit"); + + b.Property("OrganizationName") + .HasMaxLength(25) + .HasColumnType("nvarchar(25)"); + + b.HasKey("ID"); + + b.ToTable("Organizations"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.SavedScript", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Content") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CreatorId") + .HasColumnType("nvarchar(450)"); + + b.Property("FolderPath") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("GenerateAlertOnError") + .HasColumnType("bit"); + + b.Property("IsPublic") + .HasColumnType("bit"); + + b.Property("IsQuickScript") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("OrganizationID") + .HasColumnType("nvarchar(450)"); + + b.Property("SendEmailOnError") + .HasColumnType("bit"); + + b.Property("SendErrorEmailTo") + .HasColumnType("nvarchar(max)"); + + b.Property("Shell") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CreatorId"); + + b.HasIndex("OrganizationID"); + + b.ToTable("SavedScripts"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.ScriptResult", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("nvarchar(450)"); + + b.Property("DeviceID") + .HasColumnType("nvarchar(450)"); + + b.Property("ErrorOutput") + .HasColumnType("nvarchar(max)"); + + b.Property("HadErrors") + .HasColumnType("bit"); + + b.Property("InputType") + .HasColumnType("int"); + + b.Property("OrganizationID") + .HasColumnType("nvarchar(450)"); + + b.Property("RunTime") + .HasColumnType("time"); + + b.Property("SavedScriptId") + .HasColumnType("uniqueidentifier"); + + b.Property("ScheduleId") + .HasColumnType("int"); + + b.Property("ScriptInput") + .HasColumnType("nvarchar(max)"); + + b.Property("ScriptRunId") + .HasColumnType("int"); + + b.Property("SenderConnectionID") + .HasColumnType("nvarchar(max)"); + + b.Property("SenderUserName") + .HasColumnType("nvarchar(max)"); + + b.Property("Shell") + .HasColumnType("int"); + + b.Property("StandardOutput") + .HasColumnType("nvarchar(max)"); + + b.Property("TimeStamp") + .HasColumnType("datetimeoffset"); + + b.HasKey("ID"); + + b.HasIndex("DeviceID"); + + b.HasIndex("OrganizationID"); + + b.HasIndex("ScheduleId"); + + b.HasIndex("ScriptRunId"); + + b.ToTable("ScriptResults"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.ScriptRun", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("Initiator") + .HasColumnType("nvarchar(max)"); + + b.Property("InputType") + .HasColumnType("int"); + + b.Property("OrganizationID") + .HasColumnType("nvarchar(450)"); + + b.Property("RunAt") + .HasColumnType("datetimeoffset"); + + b.Property("RunOnNextConnect") + .HasColumnType("bit"); + + b.Property("SavedScriptId") + .HasColumnType("uniqueidentifier"); + + b.Property("ScheduleId") + .HasColumnType("int"); + + b.Property("ScriptScheduleId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationID"); + + b.HasIndex("ScriptScheduleId"); + + b.ToTable("ScriptRuns"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.ScriptSchedule", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("CreatedAt") + .HasColumnType("datetimeoffset"); + + b.Property("CreatorId") + .HasColumnType("nvarchar(450)"); + + b.Property("Interval") + .HasColumnType("int"); + + b.Property("LastRun") + .HasColumnType("datetimeoffset"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.Property("NextRun") + .HasColumnType("datetimeoffset"); + + b.Property("OrganizationID") + .HasColumnType("nvarchar(450)"); + + b.Property("RunOnNextConnect") + .HasColumnType("bit"); + + b.Property("SavedScriptId") + .HasColumnType("uniqueidentifier"); + + b.Property("StartAt") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.HasIndex("CreatorId"); + + b.HasIndex("OrganizationID"); + + b.ToTable("ScriptSchedules"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.SharedFile", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("nvarchar(450)"); + + b.Property("ContentType") + .HasColumnType("nvarchar(max)"); + + b.Property("FileContents") + .HasColumnType("varbinary(max)"); + + b.Property("FileName") + .HasColumnType("nvarchar(max)"); + + b.Property("OrganizationID") + .HasColumnType("nvarchar(450)"); + + b.Property("Timestamp") + .HasColumnType("datetimeoffset"); + + b.HasKey("ID"); + + b.HasIndex("OrganizationID"); + + b.ToTable("SharedFiles"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.RemotelyUser", b => + { + b.HasBaseType("Microsoft.AspNetCore.Identity.IdentityUser"); + + b.Property("IsAdministrator") + .HasColumnType("bit"); + + b.Property("IsServerAdmin") + .HasColumnType("bit"); + + b.Property("OrganizationID") + .HasColumnType("nvarchar(450)"); + + b.Property("TempPassword") + .HasColumnType("nvarchar(max)"); + + b.Property("UserOptions") + .HasColumnType("nvarchar(max)"); + + b.HasIndex("OrganizationID"); + + b.HasIndex("UserName"); + + b.HasDiscriminator().HasValue("RemotelyUser"); + }); + + modelBuilder.Entity("DeviceGroupRemotelyUser", b => + { + b.HasOne("Remotely.Shared.Models.DeviceGroup", null) + .WithMany() + .HasForeignKey("DeviceGroupsID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Remotely.Shared.Models.RemotelyUser", null) + .WithMany() + .HasForeignKey("UsersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("DeviceGroupScriptSchedule", b => + { + b.HasOne("Remotely.Shared.Models.DeviceGroup", null) + .WithMany() + .HasForeignKey("DeviceGroupsID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Remotely.Shared.Models.ScriptSchedule", null) + .WithMany() + .HasForeignKey("ScriptSchedulesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("DeviceScriptRun", b => + { + b.HasOne("Remotely.Shared.Models.Device", null) + .WithMany() + .HasForeignKey("DevicesID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Remotely.Shared.Models.ScriptRun", null) + .WithMany() + .HasForeignKey("ScriptRunsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("DeviceScriptRun1", b => + { + b.HasOne("Remotely.Shared.Models.Device", null) + .WithMany() + .HasForeignKey("DevicesCompletedID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Remotely.Shared.Models.ScriptRun", null) + .WithMany() + .HasForeignKey("ScriptRunsCompletedId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("DeviceScriptSchedule", b => + { + b.HasOne("Remotely.Shared.Models.Device", null) + .WithMany() + .HasForeignKey("DevicesID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Remotely.Shared.Models.ScriptSchedule", null) + .WithMany() + .HasForeignKey("ScriptSchedulesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Remotely.Shared.Models.Alert", b => + { + b.HasOne("Remotely.Shared.Models.Device", "Device") + .WithMany("Alerts") + .HasForeignKey("DeviceID"); + + b.HasOne("Remotely.Shared.Models.Organization", "Organization") + .WithMany("Alerts") + .HasForeignKey("OrganizationID"); + + b.HasOne("Remotely.Shared.Models.RemotelyUser", "User") + .WithMany("Alerts") + .HasForeignKey("UserID"); + + b.Navigation("Device"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.ApiToken", b => + { + b.HasOne("Remotely.Shared.Models.Organization", "Organization") + .WithMany("ApiTokens") + .HasForeignKey("OrganizationID"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.BrandingInfo", b => + { + b.HasOne("Remotely.Shared.Models.Organization", "Organization") + .WithOne("BrandingInfo") + .HasForeignKey("Remotely.Shared.Models.BrandingInfo", "OrganizationId"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.Device", b => + { + b.HasOne("Remotely.Shared.Models.DeviceGroup", "DeviceGroup") + .WithMany("Devices") + .HasForeignKey("DeviceGroupID"); + + b.HasOne("Remotely.Shared.Models.Organization", "Organization") + .WithMany("Devices") + .HasForeignKey("OrganizationID"); + + b.Navigation("DeviceGroup"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.DeviceGroup", b => + { + b.HasOne("Remotely.Shared.Models.Organization", "Organization") + .WithMany("DeviceGroups") + .HasForeignKey("OrganizationID"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.EventLog", b => + { + b.HasOne("Remotely.Shared.Models.Organization", "Organization") + .WithMany("EventLogs") + .HasForeignKey("OrganizationID"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.InviteLink", b => + { + b.HasOne("Remotely.Shared.Models.Organization", "Organization") + .WithMany("InviteLinks") + .HasForeignKey("OrganizationID"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.SavedScript", b => + { + b.HasOne("Remotely.Shared.Models.RemotelyUser", "Creator") + .WithMany("SavedScripts") + .HasForeignKey("CreatorId"); + + b.HasOne("Remotely.Shared.Models.Organization", "Organization") + .WithMany("SavedScripts") + .HasForeignKey("OrganizationID"); + + b.Navigation("Creator"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.ScriptResult", b => + { + b.HasOne("Remotely.Shared.Models.Device", "Device") + .WithMany("ScriptResults") + .HasForeignKey("DeviceID"); + + b.HasOne("Remotely.Shared.Models.Organization", "Organization") + .WithMany("ScriptResults") + .HasForeignKey("OrganizationID"); + + b.HasOne("Remotely.Shared.Models.ScriptSchedule", "Schedule") + .WithMany() + .HasForeignKey("ScheduleId"); + + b.HasOne("Remotely.Shared.Models.ScriptRun", null) + .WithMany("Results") + .HasForeignKey("ScriptRunId"); + + b.Navigation("Device"); + + b.Navigation("Organization"); + + b.Navigation("Schedule"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.ScriptRun", b => + { + b.HasOne("Remotely.Shared.Models.Organization", "Organization") + .WithMany("ScriptRuns") + .HasForeignKey("OrganizationID"); + + b.HasOne("Remotely.Shared.Models.ScriptSchedule", null) + .WithMany("ScriptRuns") + .HasForeignKey("ScriptScheduleId"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.ScriptSchedule", b => + { + b.HasOne("Remotely.Shared.Models.RemotelyUser", "Creator") + .WithMany("ScriptSchedules") + .HasForeignKey("CreatorId"); + + b.HasOne("Remotely.Shared.Models.Organization", "Organization") + .WithMany("ScriptSchedules") + .HasForeignKey("OrganizationID"); + + b.Navigation("Creator"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.SharedFile", b => + { + b.HasOne("Remotely.Shared.Models.Organization", "Organization") + .WithMany("SharedFiles") + .HasForeignKey("OrganizationID"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.RemotelyUser", b => + { + b.HasOne("Remotely.Shared.Models.Organization", "Organization") + .WithMany("RemotelyUsers") + .HasForeignKey("OrganizationID"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.Device", b => + { + b.Navigation("Alerts"); + + b.Navigation("ScriptResults"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.DeviceGroup", b => + { + b.Navigation("Devices"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.Organization", b => + { + b.Navigation("Alerts"); + + b.Navigation("ApiTokens"); + + b.Navigation("BrandingInfo"); + + b.Navigation("DeviceGroups"); + + b.Navigation("Devices"); + + b.Navigation("EventLogs"); + + b.Navigation("InviteLinks"); + + b.Navigation("RemotelyUsers"); + + b.Navigation("SavedScripts"); + + b.Navigation("ScriptResults"); + + b.Navigation("ScriptRuns"); + + b.Navigation("ScriptSchedules"); + + b.Navigation("SharedFiles"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.ScriptRun", b => + { + b.Navigation("Results"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.ScriptSchedule", b => + { + b.Navigation("ScriptRuns"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.RemotelyUser", b => + { + b.Navigation("Alerts"); + + b.Navigation("SavedScripts"); + + b.Navigation("ScriptSchedules"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Server/Migrations/SqlServer/20221231192616_Remove RelayCode.cs b/Server/Migrations/SqlServer/20221231192616_Remove RelayCode.cs new file mode 100644 index 000000000..1d1852217 --- /dev/null +++ b/Server/Migrations/SqlServer/20221231192616_Remove RelayCode.cs @@ -0,0 +1,25 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Remotely.Server.Migrations.SqlServer +{ + public partial class RemoveRelayCode : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "RelayCode", + table: "Organizations"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "RelayCode", + table: "Organizations", + type: "nvarchar(max)", + nullable: true); + } + } +} diff --git a/Server/Migrations/SqlServer/SqlServerDbContextModelSnapshot.cs b/Server/Migrations/SqlServer/SqlServerDbContextModelSnapshot.cs index 2da6f69ab..30c4ac4af 100644 --- a/Server/Migrations/SqlServer/SqlServerDbContextModelSnapshot.cs +++ b/Server/Migrations/SqlServer/SqlServerDbContextModelSnapshot.cs @@ -596,9 +596,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasMaxLength(25) .HasColumnType("nvarchar(25)"); - b.Property("RelayCode") - .HasColumnType("nvarchar(max)"); - b.HasKey("ID"); b.ToTable("Organizations"); diff --git a/Server/Migrations/Sqlite/20221231192606_Remove RelayCode.Designer.cs b/Server/Migrations/Sqlite/20221231192606_Remove RelayCode.Designer.cs new file mode 100644 index 000000000..9e6e35595 --- /dev/null +++ b/Server/Migrations/Sqlite/20221231192606_Remove RelayCode.Designer.cs @@ -0,0 +1,1213 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Remotely.Server.Data; + +#nullable disable + +namespace Remotely.Server.Migrations.Sqlite +{ + [DbContext(typeof(SqliteDbContext))] + [Migration("20221231192606_Remove RelayCode")] + partial class RemoveRelayCode + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "6.0.9"); + + modelBuilder.Entity("DeviceGroupRemotelyUser", b => + { + b.Property("DeviceGroupsID") + .HasColumnType("TEXT"); + + b.Property("UsersId") + .HasColumnType("TEXT"); + + b.HasKey("DeviceGroupsID", "UsersId"); + + b.HasIndex("UsersId"); + + b.ToTable("DeviceGroupRemotelyUser"); + }); + + modelBuilder.Entity("DeviceGroupScriptSchedule", b => + { + b.Property("DeviceGroupsID") + .HasColumnType("TEXT"); + + b.Property("ScriptSchedulesId") + .HasColumnType("INTEGER"); + + b.HasKey("DeviceGroupsID", "ScriptSchedulesId"); + + b.HasIndex("ScriptSchedulesId"); + + b.ToTable("DeviceGroupScriptSchedule"); + }); + + modelBuilder.Entity("DeviceScriptRun", b => + { + b.Property("DevicesID") + .HasColumnType("TEXT"); + + b.Property("ScriptRunsId") + .HasColumnType("INTEGER"); + + b.HasKey("DevicesID", "ScriptRunsId"); + + b.HasIndex("ScriptRunsId"); + + b.ToTable("DeviceScriptRun"); + }); + + modelBuilder.Entity("DeviceScriptRun1", b => + { + b.Property("DevicesCompletedID") + .HasColumnType("TEXT"); + + b.Property("ScriptRunsCompletedId") + .HasColumnType("INTEGER"); + + b.HasKey("DevicesCompletedID", "ScriptRunsCompletedId"); + + b.HasIndex("ScriptRunsCompletedId"); + + b.ToTable("DeviceScriptRun1"); + }); + + modelBuilder.Entity("DeviceScriptSchedule", b => + { + b.Property("DevicesID") + .HasColumnType("TEXT"); + + b.Property("ScriptSchedulesId") + .HasColumnType("INTEGER"); + + b.HasKey("DevicesID", "ScriptSchedulesId"); + + b.HasIndex("ScriptSchedulesId"); + + b.ToTable("DeviceScriptSchedule"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Discriminator") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("RemotelyUsers", (string)null); + + b.HasDiscriminator("Discriminator").HasValue("IdentityUser"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("ProviderKey") + .HasColumnType("TEXT"); + + b.Property("ProviderDisplayName") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("Remotely.Shared.Models.Alert", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedOn") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Details") + .HasColumnType("TEXT"); + + b.Property("DeviceID") + .HasColumnType("TEXT"); + + b.Property("Message") + .HasColumnType("TEXT"); + + b.Property("OrganizationID") + .HasColumnType("TEXT"); + + b.Property("UserID") + .HasColumnType("TEXT"); + + b.HasKey("ID"); + + b.HasIndex("DeviceID"); + + b.HasIndex("OrganizationID"); + + b.HasIndex("UserID"); + + b.ToTable("Alerts"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.ApiToken", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("LastUsed") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("OrganizationID") + .HasColumnType("TEXT"); + + b.Property("Secret") + .HasColumnType("TEXT"); + + b.HasKey("ID"); + + b.HasIndex("OrganizationID"); + + b.ToTable("ApiTokens"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.BrandingInfo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ButtonForegroundBlue") + .HasColumnType("INTEGER"); + + b.Property("ButtonForegroundGreen") + .HasColumnType("INTEGER"); + + b.Property("ButtonForegroundRed") + .HasColumnType("INTEGER"); + + b.Property("Icon") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Product") + .IsRequired() + .HasMaxLength(25) + .HasColumnType("TEXT"); + + b.Property("TitleBackgroundBlue") + .HasColumnType("INTEGER"); + + b.Property("TitleBackgroundGreen") + .HasColumnType("INTEGER"); + + b.Property("TitleBackgroundRed") + .HasColumnType("INTEGER"); + + b.Property("TitleForegroundBlue") + .HasColumnType("INTEGER"); + + b.Property("TitleForegroundGreen") + .HasColumnType("INTEGER"); + + b.Property("TitleForegroundRed") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .IsUnique(); + + b.ToTable("BrandingInfos"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.Device", b => + { + b.Property("ID") + .HasColumnType("TEXT"); + + b.Property("AgentVersion") + .HasColumnType("TEXT"); + + b.Property("Alias") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CpuUtilization") + .HasColumnType("REAL"); + + b.Property("CurrentUser") + .HasColumnType("TEXT"); + + b.Property("DeviceGroupID") + .HasColumnType("TEXT"); + + b.Property("DeviceName") + .HasColumnType("TEXT"); + + b.Property("Drives") + .HasColumnType("TEXT"); + + b.Property("Is64Bit") + .HasColumnType("INTEGER"); + + b.Property("IsOnline") + .HasColumnType("INTEGER"); + + b.Property("LastOnline") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasMaxLength(5000) + .HasColumnType("TEXT"); + + b.Property("OSArchitecture") + .HasColumnType("INTEGER"); + + b.Property("OSDescription") + .HasColumnType("TEXT"); + + b.Property("OrganizationID") + .HasColumnType("TEXT"); + + b.Property("Platform") + .HasColumnType("TEXT"); + + b.Property("ProcessorCount") + .HasColumnType("INTEGER"); + + b.Property("PublicIP") + .HasColumnType("TEXT"); + + b.Property("ServerVerificationToken") + .HasColumnType("TEXT"); + + b.Property("Tags") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("TotalMemory") + .HasColumnType("REAL"); + + b.Property("TotalStorage") + .HasColumnType("REAL"); + + b.Property("UsedMemory") + .HasColumnType("REAL"); + + b.Property("UsedStorage") + .HasColumnType("REAL"); + + b.HasKey("ID"); + + b.HasIndex("DeviceGroupID"); + + b.HasIndex("DeviceName"); + + b.HasIndex("OrganizationID"); + + b.ToTable("Devices"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.DeviceGroup", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("OrganizationID") + .HasColumnType("TEXT"); + + b.HasKey("ID"); + + b.HasIndex("OrganizationID"); + + b.ToTable("DeviceGroups"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.EventLog", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("EventType") + .HasColumnType("INTEGER"); + + b.Property("Message") + .HasColumnType("TEXT"); + + b.Property("OrganizationID") + .HasColumnType("TEXT"); + + b.Property("Source") + .HasColumnType("TEXT"); + + b.Property("StackTrace") + .HasColumnType("TEXT"); + + b.Property("TimeStamp") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("ID"); + + b.HasIndex("OrganizationID"); + + b.ToTable("EventLogs"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.InviteLink", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("DateSent") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("InvitedUser") + .HasColumnType("TEXT"); + + b.Property("IsAdmin") + .HasColumnType("INTEGER"); + + b.Property("OrganizationID") + .HasColumnType("TEXT"); + + b.Property("ResetUrl") + .HasColumnType("TEXT"); + + b.HasKey("ID"); + + b.HasIndex("OrganizationID"); + + b.ToTable("InviteLinks"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.Organization", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("IsDefaultOrganization") + .HasColumnType("INTEGER"); + + b.Property("OrganizationName") + .HasMaxLength(25) + .HasColumnType("TEXT"); + + b.HasKey("ID"); + + b.ToTable("Organizations"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.SavedScript", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Content") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatorId") + .HasColumnType("TEXT"); + + b.Property("FolderPath") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("GenerateAlertOnError") + .HasColumnType("INTEGER"); + + b.Property("IsPublic") + .HasColumnType("INTEGER"); + + b.Property("IsQuickScript") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("OrganizationID") + .HasColumnType("TEXT"); + + b.Property("SendEmailOnError") + .HasColumnType("INTEGER"); + + b.Property("SendErrorEmailTo") + .HasColumnType("TEXT"); + + b.Property("Shell") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CreatorId"); + + b.HasIndex("OrganizationID"); + + b.ToTable("SavedScripts"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.ScriptResult", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("DeviceID") + .HasColumnType("TEXT"); + + b.Property("ErrorOutput") + .HasColumnType("TEXT"); + + b.Property("HadErrors") + .HasColumnType("INTEGER"); + + b.Property("InputType") + .HasColumnType("INTEGER"); + + b.Property("OrganizationID") + .HasColumnType("TEXT"); + + b.Property("RunTime") + .HasColumnType("TEXT"); + + b.Property("SavedScriptId") + .HasColumnType("TEXT"); + + b.Property("ScheduleId") + .HasColumnType("INTEGER"); + + b.Property("ScriptInput") + .HasColumnType("TEXT"); + + b.Property("ScriptRunId") + .HasColumnType("INTEGER"); + + b.Property("SenderConnectionID") + .HasColumnType("TEXT"); + + b.Property("SenderUserName") + .HasColumnType("TEXT"); + + b.Property("Shell") + .HasColumnType("INTEGER"); + + b.Property("StandardOutput") + .HasColumnType("TEXT"); + + b.Property("TimeStamp") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("ID"); + + b.HasIndex("DeviceID"); + + b.HasIndex("OrganizationID"); + + b.HasIndex("ScheduleId"); + + b.HasIndex("ScriptRunId"); + + b.ToTable("ScriptResults"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.ScriptRun", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Initiator") + .HasColumnType("TEXT"); + + b.Property("InputType") + .HasColumnType("INTEGER"); + + b.Property("OrganizationID") + .HasColumnType("TEXT"); + + b.Property("RunAt") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("RunOnNextConnect") + .HasColumnType("INTEGER"); + + b.Property("SavedScriptId") + .HasColumnType("TEXT"); + + b.Property("ScheduleId") + .HasColumnType("INTEGER"); + + b.Property("ScriptScheduleId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationID"); + + b.HasIndex("ScriptScheduleId"); + + b.ToTable("ScriptRuns"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.ScriptSchedule", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatorId") + .HasColumnType("TEXT"); + + b.Property("Interval") + .HasColumnType("INTEGER"); + + b.Property("LastRun") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("NextRun") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("OrganizationID") + .HasColumnType("TEXT"); + + b.Property("RunOnNextConnect") + .HasColumnType("INTEGER"); + + b.Property("SavedScriptId") + .HasColumnType("TEXT"); + + b.Property("StartAt") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CreatorId"); + + b.HasIndex("OrganizationID"); + + b.ToTable("ScriptSchedules"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.SharedFile", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ContentType") + .HasColumnType("TEXT"); + + b.Property("FileContents") + .HasColumnType("BLOB"); + + b.Property("FileName") + .HasColumnType("TEXT"); + + b.Property("OrganizationID") + .HasColumnType("TEXT"); + + b.Property("Timestamp") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("ID"); + + b.HasIndex("OrganizationID"); + + b.ToTable("SharedFiles"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.RemotelyUser", b => + { + b.HasBaseType("Microsoft.AspNetCore.Identity.IdentityUser"); + + b.Property("IsAdministrator") + .HasColumnType("INTEGER"); + + b.Property("IsServerAdmin") + .HasColumnType("INTEGER"); + + b.Property("OrganizationID") + .HasColumnType("TEXT"); + + b.Property("TempPassword") + .HasColumnType("TEXT"); + + b.Property("UserOptions") + .HasColumnType("TEXT"); + + b.HasIndex("OrganizationID"); + + b.HasIndex("UserName"); + + b.HasDiscriminator().HasValue("RemotelyUser"); + }); + + modelBuilder.Entity("DeviceGroupRemotelyUser", b => + { + b.HasOne("Remotely.Shared.Models.DeviceGroup", null) + .WithMany() + .HasForeignKey("DeviceGroupsID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Remotely.Shared.Models.RemotelyUser", null) + .WithMany() + .HasForeignKey("UsersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("DeviceGroupScriptSchedule", b => + { + b.HasOne("Remotely.Shared.Models.DeviceGroup", null) + .WithMany() + .HasForeignKey("DeviceGroupsID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Remotely.Shared.Models.ScriptSchedule", null) + .WithMany() + .HasForeignKey("ScriptSchedulesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("DeviceScriptRun", b => + { + b.HasOne("Remotely.Shared.Models.Device", null) + .WithMany() + .HasForeignKey("DevicesID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Remotely.Shared.Models.ScriptRun", null) + .WithMany() + .HasForeignKey("ScriptRunsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("DeviceScriptRun1", b => + { + b.HasOne("Remotely.Shared.Models.Device", null) + .WithMany() + .HasForeignKey("DevicesCompletedID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Remotely.Shared.Models.ScriptRun", null) + .WithMany() + .HasForeignKey("ScriptRunsCompletedId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("DeviceScriptSchedule", b => + { + b.HasOne("Remotely.Shared.Models.Device", null) + .WithMany() + .HasForeignKey("DevicesID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Remotely.Shared.Models.ScriptSchedule", null) + .WithMany() + .HasForeignKey("ScriptSchedulesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Remotely.Shared.Models.Alert", b => + { + b.HasOne("Remotely.Shared.Models.Device", "Device") + .WithMany("Alerts") + .HasForeignKey("DeviceID"); + + b.HasOne("Remotely.Shared.Models.Organization", "Organization") + .WithMany("Alerts") + .HasForeignKey("OrganizationID"); + + b.HasOne("Remotely.Shared.Models.RemotelyUser", "User") + .WithMany("Alerts") + .HasForeignKey("UserID"); + + b.Navigation("Device"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.ApiToken", b => + { + b.HasOne("Remotely.Shared.Models.Organization", "Organization") + .WithMany("ApiTokens") + .HasForeignKey("OrganizationID"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.BrandingInfo", b => + { + b.HasOne("Remotely.Shared.Models.Organization", "Organization") + .WithOne("BrandingInfo") + .HasForeignKey("Remotely.Shared.Models.BrandingInfo", "OrganizationId"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.Device", b => + { + b.HasOne("Remotely.Shared.Models.DeviceGroup", "DeviceGroup") + .WithMany("Devices") + .HasForeignKey("DeviceGroupID"); + + b.HasOne("Remotely.Shared.Models.Organization", "Organization") + .WithMany("Devices") + .HasForeignKey("OrganizationID"); + + b.Navigation("DeviceGroup"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.DeviceGroup", b => + { + b.HasOne("Remotely.Shared.Models.Organization", "Organization") + .WithMany("DeviceGroups") + .HasForeignKey("OrganizationID"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.EventLog", b => + { + b.HasOne("Remotely.Shared.Models.Organization", "Organization") + .WithMany("EventLogs") + .HasForeignKey("OrganizationID"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.InviteLink", b => + { + b.HasOne("Remotely.Shared.Models.Organization", "Organization") + .WithMany("InviteLinks") + .HasForeignKey("OrganizationID"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.SavedScript", b => + { + b.HasOne("Remotely.Shared.Models.RemotelyUser", "Creator") + .WithMany("SavedScripts") + .HasForeignKey("CreatorId"); + + b.HasOne("Remotely.Shared.Models.Organization", "Organization") + .WithMany("SavedScripts") + .HasForeignKey("OrganizationID"); + + b.Navigation("Creator"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.ScriptResult", b => + { + b.HasOne("Remotely.Shared.Models.Device", "Device") + .WithMany("ScriptResults") + .HasForeignKey("DeviceID"); + + b.HasOne("Remotely.Shared.Models.Organization", "Organization") + .WithMany("ScriptResults") + .HasForeignKey("OrganizationID"); + + b.HasOne("Remotely.Shared.Models.ScriptSchedule", "Schedule") + .WithMany() + .HasForeignKey("ScheduleId"); + + b.HasOne("Remotely.Shared.Models.ScriptRun", null) + .WithMany("Results") + .HasForeignKey("ScriptRunId"); + + b.Navigation("Device"); + + b.Navigation("Organization"); + + b.Navigation("Schedule"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.ScriptRun", b => + { + b.HasOne("Remotely.Shared.Models.Organization", "Organization") + .WithMany("ScriptRuns") + .HasForeignKey("OrganizationID"); + + b.HasOne("Remotely.Shared.Models.ScriptSchedule", null) + .WithMany("ScriptRuns") + .HasForeignKey("ScriptScheduleId"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.ScriptSchedule", b => + { + b.HasOne("Remotely.Shared.Models.RemotelyUser", "Creator") + .WithMany("ScriptSchedules") + .HasForeignKey("CreatorId"); + + b.HasOne("Remotely.Shared.Models.Organization", "Organization") + .WithMany("ScriptSchedules") + .HasForeignKey("OrganizationID"); + + b.Navigation("Creator"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.SharedFile", b => + { + b.HasOne("Remotely.Shared.Models.Organization", "Organization") + .WithMany("SharedFiles") + .HasForeignKey("OrganizationID"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.RemotelyUser", b => + { + b.HasOne("Remotely.Shared.Models.Organization", "Organization") + .WithMany("RemotelyUsers") + .HasForeignKey("OrganizationID"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.Device", b => + { + b.Navigation("Alerts"); + + b.Navigation("ScriptResults"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.DeviceGroup", b => + { + b.Navigation("Devices"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.Organization", b => + { + b.Navigation("Alerts"); + + b.Navigation("ApiTokens"); + + b.Navigation("BrandingInfo"); + + b.Navigation("DeviceGroups"); + + b.Navigation("Devices"); + + b.Navigation("EventLogs"); + + b.Navigation("InviteLinks"); + + b.Navigation("RemotelyUsers"); + + b.Navigation("SavedScripts"); + + b.Navigation("ScriptResults"); + + b.Navigation("ScriptRuns"); + + b.Navigation("ScriptSchedules"); + + b.Navigation("SharedFiles"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.ScriptRun", b => + { + b.Navigation("Results"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.ScriptSchedule", b => + { + b.Navigation("ScriptRuns"); + }); + + modelBuilder.Entity("Remotely.Shared.Models.RemotelyUser", b => + { + b.Navigation("Alerts"); + + b.Navigation("SavedScripts"); + + b.Navigation("ScriptSchedules"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Server/Migrations/Sqlite/20221231192606_Remove RelayCode.cs b/Server/Migrations/Sqlite/20221231192606_Remove RelayCode.cs new file mode 100644 index 000000000..c604c593a --- /dev/null +++ b/Server/Migrations/Sqlite/20221231192606_Remove RelayCode.cs @@ -0,0 +1,25 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Remotely.Server.Migrations.Sqlite +{ + public partial class RemoveRelayCode : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "RelayCode", + table: "Organizations"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "RelayCode", + table: "Organizations", + type: "TEXT", + nullable: true); + } + } +} diff --git a/Server/Migrations/Sqlite/SqliteDbContextModelSnapshot.cs b/Server/Migrations/Sqlite/SqliteDbContextModelSnapshot.cs index 889b2d6a5..4bb870507 100644 --- a/Server/Migrations/Sqlite/SqliteDbContextModelSnapshot.cs +++ b/Server/Migrations/Sqlite/SqliteDbContextModelSnapshot.cs @@ -588,9 +588,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasMaxLength(25) .HasColumnType("TEXT"); - b.Property("RelayCode") - .HasColumnType("TEXT"); - b.HasKey("ID"); b.ToTable("Organizations"); diff --git a/Server/Pages/About.razor b/Server/Pages/About.razor index 7df1b604b..d243d1a3d 100644 --- a/Server/Pages/About.razor +++ b/Server/Pages/About.razor @@ -14,7 +14,7 @@

Version: @if (System.IO.File.Exists("Remotely_Server.dll")) { - @System.Diagnostics.FileVersionInfo.GetVersionInfo("Remotely_Server.dll").FileVersion.ToString() + @System.Diagnostics.FileVersionInfo.GetVersionInfo("Remotely_Server.dll").FileVersion } else { diff --git a/Server/Pages/ManageOrganization.razor b/Server/Pages/ManageOrganization.razor index 95a37f3db..0342b5133 100644 --- a/Server/Pages/ManageOrganization.razor +++ b/Server/Pages/ManageOrganization.razor @@ -14,14 +14,6 @@ -

- - -
- -
@@ -49,7 +41,7 @@

@@ -64,7 +56,7 @@