feature: Bootstrapper secure server discovery

This commit is contained in:
Gary Sharp
2026-01-22 15:26:23 +11:00
parent 71fa53bfb2
commit e1f1973520
40 changed files with 2094 additions and 460 deletions
+178 -189
View File
@@ -1,4 +1,6 @@
using System;
using Disco.Client.Interop;
using Disco.ClientBootstrapper.Interop;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
@@ -6,46 +8,191 @@ using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Disco.ClientBootstrapper
{
class BootstrapperLoop
internal class BootstrapperLoop
{
public Thread LoopThread;
public delegate void LoopCompleteCallback();
private LoopCompleteCallback mLoopCompleteCallback;
private IStatus statusUI;
private readonly Func<CancellationToken, Task> completeCallback;
private readonly CancellationToken cancellationToken;
private readonly IStatus statusUI;
private readonly Uri forcedServerUrl;
private string tempWorkingDirectory;
private StringBuilder errorMessage;
private Process clientProcess;
//#if DEBUG
// public const string DiscoServerName = "WS-GSHARP";
// public const int DiscoServerPort = 57252;
//#else
public const string DiscoServerName = "DISCO";
public const int DiscoServerPort = 9292;
//#endif
public BootstrapperLoop(IStatus StatusUI, LoopCompleteCallback Callback)
public BootstrapperLoop(IStatus statusUI, Uri forcedServerUrl, Func<CancellationToken, Task> callback, CancellationToken cancellationToken)
{
statusUI = StatusUI;
mLoopCompleteCallback = Callback;
errorMessage = new StringBuilder();
this.statusUI = statusUI;
this.forcedServerUrl = forcedServerUrl;
completeCallback = callback;
this.cancellationToken = cancellationToken;
}
public void Start()
{
LoopThread = new Thread(new ThreadStart(loopHost));
LoopThread.Start();
Task.Factory.StartNew(async () =>
{
await Loop(forcedServerUrl, cancellationToken);
}, cancellationToken);
}
private void loopHost()
private async Task Loop(Uri forcedServerUrl, CancellationToken cancellationToken)
{
try
{
loop();
statusUI.UpdateStatus("System Preparation (Bootstrapper)", "Starting", "Please wait...", true, -1);
tempWorkingDirectory = Path.Combine(Path.GetPathRoot(Environment.SystemDirectory), @"Disco\Temp");
if (!Directory.Exists(tempWorkingDirectory))
Directory.CreateDirectory(tempWorkingDirectory);
// Check for Network Connectivity
statusUI.UpdateStatus(null, "Detecting Network", "Checking network connectivity, Please wait...", true, -1);
if (!NetworkInterop.HasNetworkConnectivity())
{
statusUI.UpdateStatus(null, "Detecting Network", "No network connectivity detected, Diagnosing...", true, -1);
statusUI_WriteAdapterInfo();
if (!NetworkInterop.HasNetworkConnectivity())
{
// Check for Wireless
var hasWireless = (NetworkInterop.NetworkAdapters.Count(na => na.IsWireless) > 0);
if (hasWireless)
{
// True: Do wireless loop
statusUI.UpdateStatus(null, "Configuring Wireless Network", "Wireless adapter detected, Configuring...", true, -1);
await NetworkInterop.ConfigureWireless(cancellationToken);
statusUI.UpdateStatus(null, "Waiting for Wireless Network", null, true, 0);
for (int i = 0; i < 30; i++)
{
statusUI_WriteAdapterInfo();
statusUI.UpdateStatus(null, null, null, true, i);
await Program.SleepThread(2000, false, cancellationToken);
if (NetworkInterop.HasNetworkConnectivity())
break;
}
if (!NetworkInterop.HasNetworkConnectivity())
{
statusUI.UpdateStatus(null, "Wireless Network Failed", "Unable to connect to the wireless network, please connect the network cable...", false);
await Program.SleepThread(3000, false, cancellationToken);
}
}
if (!NetworkInterop.HasNetworkConnectivity())
{
// Instruct user to connect network cable
statusUI.UpdateStatus(null, "Please connect the network cable", null);
for (int i = 0; i < 30; i++)
{
statusUI_WriteAdapterInfo();
statusUI.UpdateStatus(null, null, null, true, i);
await Program.SleepThread(2000, false, cancellationToken);
if (NetworkInterop.HasNetworkConnectivity())
break;
}
}
}
if (!NetworkInterop.HasNetworkConnectivity())
{
// Client Failed
if (completeCallback != null)
await completeCallback(cancellationToken);
return;
}
}
Tuple<Uri, string> serverDiscovery;
statusUI.UpdateStatus(null, "Detecting Disco ICT", "Locating Disco ICT Server, Please wait...", true, -1);
try
{
serverDiscovery = EndpointDiscovery.DiscoverServer(forcedServerUrl);
statusUI.UpdateStatus(null, null, $"{serverDiscovery.Item1} ({serverDiscovery.Item2})", true, -1);
}
catch (Exception)
{
statusUI.UpdateStatus(null, null, "Failed to locate Disco ICT Server, exiting...", true, -1);
await Program.SleepThread(2000, false, cancellationToken);
throw;
}
// Download Client
statusUI.UpdateStatus(null, "Downloading", "Retrieving Preparation Client, Please wait...", true, -1);
string clientSourceLocation = Path.Combine(tempWorkingDirectory, "PreparationClient.zip");
using (var webClient = new WebClient())
{
// Don't use a proxy when downloading the Client
webClient.Proxy = new WebProxy();
webClient.Headers.Add("X-DiscoICT-Discovery", serverDiscovery.Item2);
try
{
webClient.DownloadFile(new Uri(serverDiscovery.Item1, "/Services/Client/PreparationClient"), clientSourceLocation);
}
catch (WebException ex)
{
if (ex.Response != null &&
ex.Response is HttpWebResponse response)
{
if (response.StatusCode == HttpStatusCode.BadRequest)
{
statusUI.UpdateStatus(null, "Download failed: Bad Request", response.StatusDescription, true, -1);
await Program.SleepThread(5000, false, cancellationToken);
}
else if (response.StatusCode == HttpStatusCode.InternalServerError)
{
statusUI.UpdateStatus(null, "Download failed: Something went wrong on the server", "Review logs for more information (Configuration > Logging)", true, -1);
await Program.SleepThread(5000, false, cancellationToken);
}
}
throw;
}
}
// Unzip Client
statusUI.UpdateStatus(null, "Extracting", "Retrieving Preparation Client, Please wait...", true, -1);
string clientLocation = Path.Combine(tempWorkingDirectory, "PreparationClient");
if (Directory.Exists(clientLocation))
Directory.Delete(clientLocation, true);
Directory.CreateDirectory(clientLocation);
using (var clientSource = Ionic.Zip.ZipFile.Read(clientSourceLocation))
{
clientSource.ExtractAll(clientLocation, Ionic.Zip.ExtractExistingFileAction.OverwriteSilently);
}
// Launch Client
statusUI.UpdateStatus("System Preparation (Client)", "Running", "Launching Preparation Client, Please wait...", true, -1);
ProcessStartInfo clientProcessStart = new ProcessStartInfo(Path.Combine(clientLocation, "Start.bat"), $"2 {Process.GetCurrentProcess().Id} {serverDiscovery.Item1}")
{
WorkingDirectory = clientLocation,
CreateNoWindow = true,
RedirectStandardOutput = true,
UseShellExecute = false,
};
using (clientProcess = Process.Start(clientProcessStart))
{
// Read StdOutput until End
try
{
clientProcess.OutputDataReceived += new DataReceivedEventHandler(clientProcess_OutputDataReceived);
clientProcess.BeginOutputReadLine();
clientProcess.WaitForExit();
}
catch (Exception) { throw; }
finally
{
try { clientProcess.CloseMainWindow(); }
catch (Exception) { }
}
}
clientProcess = null;
// Cleanup
if (Directory.Exists(tempWorkingDirectory))
Directory.Delete(tempWorkingDirectory, true);
CertificateInterop.RemoveTempCerts();
}
catch (Exception ex)
{
@@ -53,167 +200,20 @@ namespace Disco.ClientBootstrapper
return;
if (ex.GetType() == typeof(ThreadInterruptedException))
return;
Program.WriteAppError(ex);
throw;
}
}
private void loop()
{
#if Debug
statusUI.UpdateStatus("Waiting for Debugger", "Please wait...", true, -1);
try
{
do
{
System.Threading.Thread.Sleep(10);
} while (!System.Diagnostics.Debugger.IsAttached);
}
catch (Exception ex)
{
statusUI.UpdateStatus("Error", ex.Message, true, -1);
return;
}
#else
statusUI.UpdateStatus("System Preparation (Bootstrapper)", "Starting", "Please wait...", true, -1);
#endif
tempWorkingDirectory = Path.Combine(Path.GetPathRoot(Environment.SystemDirectory), "Disco\\Temp");
if (!Directory.Exists(tempWorkingDirectory))
Directory.CreateDirectory(tempWorkingDirectory);
// Check for Network Connectivity
statusUI.UpdateStatus(null, "Detecting Network", "Checking network connectivity, Please wait...", true, -1);
if (!Interop.NetworkInterop.PingDiscoIct(DiscoServerName))
{
statusUI.UpdateStatus(null, "Detecting Network", "No network connectivity detected, Diagnosing...", true, -1);
statusUI_WriteAdapterInfo();
if (!Interop.NetworkInterop.PingDiscoIct(DiscoServerName))
{
// Check for Wireless
var hasWireless = (Interop.NetworkInterop.NetworkAdapters.Count(na => na.IsWireless) > 0);
if (hasWireless)
{
// True: Do wireless loop
statusUI.UpdateStatus(null, "Configuring Wireless Network", "Wireless adapter detected, Configuring...", true, -1);
Interop.NetworkInterop.ConfigureWireless();
statusUI.UpdateStatus(null, "Waiting for Wireless Network", null, true, 0);
for (int i = 0; i < 100; i++)
{
statusUI_WriteAdapterInfo();
statusUI.UpdateStatus(null, null, null, true, i);
Program.SleepThread(500, false);
if (Interop.NetworkInterop.PingDiscoIct(DiscoServerName))
break;
}
if (!Interop.NetworkInterop.PingDiscoIct(DiscoServerName))
{
statusUI.UpdateStatus(null, "Wireless Network Failed", "Unable to connect to the wireless network, please connect the network cable...", false);
Program.SleepThread(3000, false);
}
}
if (!Interop.NetworkInterop.PingDiscoIct(DiscoServerName))
{
// Instruct user to connect network cable
statusUI.UpdateStatus(null, "Please connect the network cable", null);
for (int i = 0; i < 100; i++)
{
statusUI_WriteAdapterInfo();
statusUI.UpdateStatus(null, null, null, true, i);
Program.SleepThread(500, false);
if (Interop.NetworkInterop.PingDiscoIct(DiscoServerName))
break;
}
}
}
if (!Interop.NetworkInterop.PingDiscoIct(DiscoServerName))
{
// Client Failed
if (mLoopCompleteCallback != null)
{
mLoopCompleteCallback.BeginInvoke(null, null);
}
if (ex.GetType() == typeof(OperationCanceledException))
return;
}
Program.WriteAppError(ex);
}
// Download Client
statusUI.UpdateStatus(null, "Downloading", "Retrieving Preparation Client, Please wait...", true, -1);
string clientSourceLocation = Path.Combine(tempWorkingDirectory, "PreparationClient.zip");
using (var webClient = new WebClient())
{
// Don't use a proxy when downloading the Client
webClient.Proxy = new WebProxy();
webClient.DownloadFile($"http://{DiscoServerName}:{DiscoServerPort}/Services/Client/PreparationClient", clientSourceLocation);
}
// Unzip Client
statusUI.UpdateStatus(null, "Extracting", "Retrieving Preparation Client, Please wait...", true, -1);
string clientLocation = Path.Combine(tempWorkingDirectory, "PreparationClient");
if (Directory.Exists(clientLocation))
Directory.Delete(clientLocation, true);
Directory.CreateDirectory(clientLocation);
using (var clientSource = Ionic.Zip.ZipFile.Read(clientSourceLocation))
{
clientSource.ExtractAll(clientLocation, Ionic.Zip.ExtractExistingFileAction.OverwriteSilently);
}
// Launch Client
statusUI.UpdateStatus("System Preparation (Client)", "Running", "Launching Preparation Client, Please wait...", true, -1);
ProcessStartInfo clientProcessStart = new ProcessStartInfo(Path.Combine(clientLocation, "Start.bat"))
{
WorkingDirectory = clientLocation,
CreateNoWindow = true,
RedirectStandardOutput = true,
UseShellExecute = false,
};
using (clientProcess = Process.Start(clientProcessStart))
{
// Read StdOutput until End
try
{
clientProcess.OutputDataReceived += new DataReceivedEventHandler(clientProcess_OutputDataReceived);
clientProcess.BeginOutputReadLine();
clientProcess.WaitForExit();
}
catch (Exception) { throw; }
finally
{
try { clientProcess.CloseMainWindow(); }
catch (Exception) { }
}
}
clientProcess = null;
// Cleanup
if (Directory.Exists(tempWorkingDirectory))
Directory.Delete(tempWorkingDirectory, true);
Interop.CertificateInterop.RemoveTempCerts();
// Pause if Error
if (errorMessage.Length > 0)
{
Program.SleepThread(10000, true);
}
// End Of Loop
if (mLoopCompleteCallback != null)
{
mLoopCompleteCallback.BeginInvoke(null, null);
}
if (completeCallback != null)
await completeCallback(cancellationToken);
}
void statusUI_WriteAdapterInfo()
private void statusUI_WriteAdapterInfo()
{
var info = new StringBuilder();
foreach (var na in Interop.NetworkInterop.NetworkAdapters)
foreach (var na in NetworkInterop.NetworkAdapters)
{
if (na.IsWireless)
{
@@ -228,11 +228,10 @@ namespace Disco.ClientBootstrapper
}
void clientProcess_OutputDataReceived(object sender, DataReceivedEventArgs e)
private void clientProcess_OutputDataReceived(object sender, DataReceivedEventArgs e)
{
if (!string.IsNullOrWhiteSpace(e.Data))
{
Debug.WriteLine($"OUTPUT: {e.Data}");
var data = e.Data.Substring(1).Split(new char[] { ',' });
switch (e.Data[0])
{
@@ -249,15 +248,5 @@ namespace Disco.ClientBootstrapper
}
}
//void clientProcess_ErrorDataReceived(object sender, DataReceivedEventArgs e)
//{
// if (!string.IsNullOrEmpty(e.Data))
// {
// System.Diagnostics.Debug.WriteLine(string.Format("ERROR: {0}", e.Data));
// this.errorMessage.AppendLine(e.Data);
// statusUI.UpdateStatus(null, "An Error Occurred", this.errorMessage.ToString(), false);
// }
//}
}
}