Remotely/Server.Installer/Services/ServerInstaller.cs
2021-07-29 07:58:11 -07:00

184 lines
7.3 KiB
C#

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;
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.");
int progress = 0;
var releaseFile = cliParams.WebServer == WebServerType.IisWindows ?
"https://github.com/lucent-sea/Remotely/releases/latest/download/Remotely_Server_Win-x64.zip" :
"https://github.com/lucent-sea/Remotely/releases/latest/download/Remotely_Server_Linux-x64.zip";
using var webClient = new WebClient();
webClient.DownloadProgressChanged += (sender, args) =>
{
var newProgress = args.ProgressPercentage / 5 * 5;
if (newProgress != progress)
{
progress = newProgress;
ConsoleHelper.WriteLine($"Progress: {progress}%");
}
};
await webClient.DownloadFileTaskAsync(releaseFile, zipPath);
}
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 { }
}
TaskHelper.DelayUntil(() => 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.");
}
}
}
}