From ccfab6323016a522dcb16a4ed26431916a660d8f Mon Sep 17 00:00:00 2001 From: Spike Curtis Date: Thu, 13 Mar 2025 17:08:09 +0400 Subject: [PATCH 1/4] feat: adds mutagen daemon controller --- App/App.csproj | 3 + App/App.xaml.cs | 22 +- App/Services/MutagenController.cs | 439 ++++++++++++++++++ App/packages.lock.json | 355 +++++++++++++- Installer/Program.cs | 12 +- Tests.App/Services/MutagenControllerTest.cs | 138 ++++++ Tests.Vpn.Service/packages.lock.json | 1 + Tests.Vpn/packages.lock.json | 22 +- Vpn.DebugClient/packages.lock.json | 22 +- Vpn.Service/packages.lock.json | 1 + .../RegistryConfigurationSource.cs | 2 +- Vpn/Vpn.csproj | 1 + Vpn/packages.lock.json | 22 +- scripts/Get-Mutagen.ps1 | 44 ++ scripts/Publish.ps1 | 42 +- 15 files changed, 1076 insertions(+), 50 deletions(-) create mode 100644 App/Services/MutagenController.cs create mode 100644 Tests.App/Services/MutagenControllerTest.cs rename {Vpn.Service => Vpn}/RegistryConfigurationSource.cs (96%) create mode 100644 scripts/Get-Mutagen.ps1 diff --git a/App/App.csproj b/App/App.csproj index d4f2bed..8b7e810 100644 --- a/App/App.csproj +++ b/App/App.csproj @@ -62,11 +62,14 @@ + + + diff --git a/App/App.xaml.cs b/App/App.xaml.cs index 9895fc8..9d4cf20 100644 --- a/App/App.xaml.cs +++ b/App/App.xaml.cs @@ -6,8 +6,12 @@ using Coder.Desktop.App.ViewModels; using Coder.Desktop.App.Views; using Coder.Desktop.App.Views.Pages; +using Coder.Desktop.Vpn; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; using Microsoft.UI.Xaml; +using Microsoft.Win32; namespace Coder.Desktop.App; @@ -17,12 +21,28 @@ public partial class App : Application private bool _handleWindowClosed = true; +#if !DEBUG + private const string MutagenControllerConfigSection = "MutagenController"; +#else + private const string MutagenControllerConfigSection = "DebugMutagenController"; +#endif + public App() { - var services = new ServiceCollection(); + var builder = Host.CreateApplicationBuilder(); + + (builder.Configuration as IConfigurationBuilder).Add( + new RegistryConfigurationSource(Registry.LocalMachine, @"SOFTWARE\Coder Desktop")); + + var services = builder.Services; + services.AddSingleton(); services.AddSingleton(); + services.AddOptions() + .Bind(builder.Configuration.GetSection(MutagenControllerConfigSection)); + services.AddSingleton(); + // SignInWindow views and view models services.AddTransient(); services.AddTransient(); diff --git a/App/Services/MutagenController.cs b/App/Services/MutagenController.cs new file mode 100644 index 0000000..234be45 --- /dev/null +++ b/App/Services/MutagenController.cs @@ -0,0 +1,439 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Diagnostics; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Coder.Desktop.MutagenSdk; +using Coder.Desktop.MutagenSdk.Proto.Selection; +using Coder.Desktop.MutagenSdk.Proto.Service.Daemon; +using Coder.Desktop.MutagenSdk.Proto.Service.Synchronization; +using Coder.Desktop.Vpn.Utilities; +using Microsoft.Extensions.Options; +using TerminateRequest = Coder.Desktop.MutagenSdk.Proto.Service.Daemon.TerminateRequest; + +namespace Coder.Desktop.App.Services; + +// +// A file synchronization session to a Coder workspace agent. +// +// +// This implementation is a placeholder while implementing the daemon lifecycle. It's implementation +// will be backed by the MutagenSDK eventually. +// +public class SyncSession +{ + public string name { get; init; } = ""; + public string localPath { get; init; } = ""; + public string workspace { get; init; } = ""; + public string agent { get; init; } = ""; + public string remotePath { get; init; } = ""; +} + +public interface ISyncSessionController +{ + Task> ListSyncSessions(CancellationToken ct); + Task CreateSyncSession(SyncSession session, CancellationToken ct); + + Task TerminateSyncSession(SyncSession session, CancellationToken ct); + + // + // Initializes the controller; running the daemon if there are any saved sessions. Must be called and + // complete before other methods are allowed. + // + Task Initialize(CancellationToken ct); +} + +// These values are the config option names used in the registry. Any option +// here can be configured with `(Debug)?Mutagen:OptionName` in the registry. +// +// They should not be changed without backwards compatibility considerations. +// If changed here, they should also be changed in the installer. +public class MutagenControllerConfig +{ + [Required] public string MutagenExecutablePath { get; set; } = @"c:\mutagen.exe"; +} + +// +// A file synchronization controller based on the Mutagen Daemon. +// +public sealed class MutagenController : ISyncSessionController, IAsyncDisposable +{ + // Lock to protect all non-readonly class members. + private readonly RaiiSemaphoreSlim _lock = new(1, 1); + + // daemonProcess is non-null while the daemon is running, starting, or + // in the process of stopping. + private Process? _daemonProcess; + + private LogWriter? _logWriter; + + // holds an in-progress task starting or stopping the daemon. If task is null, + // then we are not starting or stopping, and the _daemonProcess will be null if + // the daemon is currently stopped. If the task is not null, the daemon is + // starting or stopping. If stopping, the result is null. + private Task? _inProgressTransition; + + // holds a client connected to the running mutagen daemon, if the daemon is running. + private MutagenClient? _mutagenClient; + + // holds a local count of SyncSessions, primarily so we can tell when to shut down + // the daemon because it is unneeded. + private int _sessionCount = -1; + + // set to true if we are disposing the controller. Prevents the daemon from being + // restarted. + private bool _disposing; + + private readonly string _mutagenExecutablePath; + + + private readonly string _mutagenDataDirectory = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), + "CoderDesktop", + "mutagen"); + + public MutagenController(IOptions config) + { + _mutagenExecutablePath = config.Value.MutagenExecutablePath; + } + + public MutagenController(string executablePath, string dataDirectory) + { + _mutagenExecutablePath = executablePath; + _mutagenDataDirectory = dataDirectory; + } + + public async ValueTask DisposeAsync() + { + Task? transition = null; + using (_ = await _lock.LockAsync(CancellationToken.None)) + { + _disposing = true; + if (_inProgressTransition == null && _daemonProcess == null && _mutagenClient == null) return; + transition = _inProgressTransition; + } + + if (transition != null) await transition; + await StopDaemon(new CancellationTokenSource(TimeSpan.FromSeconds(5)).Token); + GC.SuppressFinalize(this); + } + + + public async Task CreateSyncSession(SyncSession session, CancellationToken ct) + { + // reads of _sessionCount are atomic, so don't bother locking for this quick check. + if (_sessionCount == -1) throw new InvalidOperationException("Controller must be Initialized first"); + var client = await EnsureDaemon(ct); + // TODO: implement + using (_ = await _lock.LockAsync(ct)) + { + _sessionCount += 1; + } + + return session; + } + + + public async Task> ListSyncSessions(CancellationToken ct) + { + // reads of _sessionCount are atomic, so don't bother locking for this quick check. + switch (_sessionCount) + { + case -1: + throw new InvalidOperationException("Controller must be Initialized first"); + case 0: + // If we already know there are no sessions, don't start up the daemon + // again. + return new List(); + } + + var client = await EnsureDaemon(ct); + // TODO: implement + return new List(); + } + + public async Task Initialize(CancellationToken ct) + { + using (_ = await _lock.LockAsync(ct)) + { + if (_sessionCount != -1) throw new InvalidOperationException("Initialized more than once"); + _sessionCount = -2; // in progress + } + + const int maxAttempts = 5; + ListResponse? sessions = null; + for (var attempts = 1; attempts <= maxAttempts; attempts++) + { + ct.ThrowIfCancellationRequested(); + try + { + var client = await EnsureDaemon(ct); + sessions = await client.Synchronization.ListAsync(new ListRequest + { + Selection = new Selection + { + All = true, + }, + }, cancellationToken: ct); + } + catch (Exception e) when (e is not OperationCanceledException) + { + if (attempts == maxAttempts) + throw; + // back off a little and try again. + await Task.Delay(100, ct); + continue; + } + + break; + } + + using (_ = await _lock.LockAsync(ct)) + { + _sessionCount = sessions == null ? 0 : sessions.SessionStates.Count; + // check first that no other transition is happening + if (_sessionCount != 0 || _inProgressTransition != null) + return; + + // don't pass the CancellationToken; we're not going to wait for + // this Task anyway. + var transition = StopDaemon(new CancellationTokenSource(TimeSpan.FromSeconds(5)).Token); + _inProgressTransition = transition; + _ = transition.ContinueWith(RemoveTransition, CancellationToken.None); + // here we don't need to wait for the transition to complete + // before returning from Initialize(), since other operations + // will wait for the _inProgressTransition to complete before + // doing anything. + } + } + + public async Task TerminateSyncSession(SyncSession session, CancellationToken ct) + { + if (_sessionCount == -1) throw new InvalidOperationException("Controller must be Initialized first"); + var client = await EnsureDaemon(ct); + // TODO: implement + + // here we don't use the Cancellation Token, since we want to decrement and possibly + // stop the daemon even if we were cancelled, since we already successfully terminated + // the session. + using (_ = await _lock.LockAsync(CancellationToken.None)) + { + _sessionCount -= 1; + if (_sessionCount == 0) + // check first that no other transition is happening + if (_inProgressTransition == null) + { + var transition = StopDaemon(CancellationToken.None); + _inProgressTransition = transition; + _ = transition.ContinueWith(RemoveTransition, CancellationToken.None); + // here we don't need to wait for the transition to complete + // before returning, since other operations + // will wait for the _inProgressTransition to complete before + // doing anything. + } + } + } + + + private async Task EnsureDaemon(CancellationToken ct) + { + while (true) + { + ct.ThrowIfCancellationRequested(); + Task transition; + using (_ = await _lock.LockAsync(ct)) + { + if (_disposing) throw new ObjectDisposedException(ToString(), "async disposal underway"); + if (_mutagenClient != null && _inProgressTransition == null) return _mutagenClient; + if (_inProgressTransition != null) + { + transition = _inProgressTransition; + } + else + { + // no transition in progress, this implies the _mutagenClient + // must be null, and we are stopped. + _inProgressTransition = StartDaemon(ct); + transition = _inProgressTransition; + _ = transition.ContinueWith(RemoveTransition, ct); + } + } + + // wait for the transition without holding the lock. + var result = await transition; + if (result != null) return result; + } + } + + // + // Remove the completed transition from _inProgressTransition + // + private void RemoveTransition(Task transition) + { + using (_ = _lock.Lock()) + { + ; + } + + if (_inProgressTransition == transition) _inProgressTransition = null; + } + + private async Task StartDaemon(CancellationToken ct) + { + // stop any orphaned daemon + try + { + var client = new MutagenClient(_mutagenDataDirectory); + await client.Daemon.TerminateAsync(new TerminateRequest(), cancellationToken: ct); + } + catch (FileNotFoundException) + { + // Mainline; no daemon running. + } + + using (_ = await _lock.LockAsync(ct)) + { + StartDaemonProcessLocked(); + } + + return await WaitForDaemon(ct); + } + + private async Task WaitForDaemon(CancellationToken ct) + { + while (true) + { + ct.ThrowIfCancellationRequested(); + try + { + MutagenClient? client; + using (_ = await _lock.LockAsync(ct)) + { + client = _mutagenClient ?? new MutagenClient(_mutagenDataDirectory); + } + + _ = await client.Daemon.VersionAsync(new VersionRequest(), cancellationToken: ct); + + using (_ = await _lock.LockAsync(ct)) + { + if (_mutagenClient != null) + // Some concurrent process already wrote a client; unexpected + // since we should be ensuring only one transition is happening + // at a time. Start over with the new client. + continue; + _mutagenClient = client; + return _mutagenClient; + } + } + catch (Exception e) when + (e is not OperationCanceledException) // TODO: Are there other permanent errors we can detect? + { + // just wait a little longer for the daemon to come up + await Task.Delay(100, ct); + } + } + } + + private void StartDaemonProcessLocked() + { + if (_daemonProcess != null) + throw new InvalidOperationException("startDaemonLock called when daemonProcess already present"); + + // create the log file first, so ensure we have permissions + var logPath = Path.Combine(_mutagenDataDirectory, "daemon.log"); + var logStream = new StreamWriter(logPath, true); + + _daemonProcess = new Process(); + _daemonProcess.StartInfo.FileName = _mutagenExecutablePath; + _daemonProcess.StartInfo.Arguments = "daemon run"; + _daemonProcess.StartInfo.Environment.Add("MUTAGEN_DATA_DIRECTORY", _mutagenDataDirectory); + // shell needs to be disabled since we set the environment + // https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.processstartinfo.environment?view=net-8.0 + _daemonProcess.StartInfo.UseShellExecute = false; + _daemonProcess.StartInfo.RedirectStandardError = true; + _daemonProcess.Start(); + + var writer = new LogWriter(_daemonProcess.StandardError, logStream); + Task.Run(() => { _ = writer.Run(); }); + _logWriter = writer; + } + + private async Task StopDaemon(CancellationToken ct) + { + Process? process; + MutagenClient? client; + LogWriter? writer; + using (_ = await _lock.LockAsync(ct)) + { + process = _daemonProcess; + client = _mutagenClient; + writer = _logWriter; + _daemonProcess = null; + _mutagenClient = null; + _logWriter = null; + } + + try + { + if (client == null) + { + if (process == null) return null; + process.Kill(true); + } + else + { + try + { + await client.Daemon.TerminateAsync(new TerminateRequest(), cancellationToken: ct); + } + catch + { + if (process == null) return null; + process.Kill(true); + } + } + + if (process == null) return null; + var cts = CancellationTokenSource.CreateLinkedTokenSource(ct); + cts.CancelAfter(TimeSpan.FromSeconds(5)); + await process.WaitForExitAsync(cts.Token); + } + finally + { + client?.Dispose(); + process?.Dispose(); + writer?.Dispose(); + } + + return null; + } +} + +public class LogWriter(StreamReader reader, StreamWriter writer) : IDisposable +{ + public void Dispose() + { + reader.Dispose(); + writer.Dispose(); + GC.SuppressFinalize(this); + } + + public async Task Run() + { + try + { + string? line; + while ((line = await reader.ReadLineAsync()) != null) await writer.WriteLineAsync(line); + } + catch + { + // TODO: Log? + } + finally + { + Dispose(); + } + } +} diff --git a/App/packages.lock.json b/App/packages.lock.json index 264df38..8988638 100644 --- a/App/packages.lock.json +++ b/App/packages.lock.json @@ -35,6 +35,46 @@ "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1" } }, + "Microsoft.Extensions.Hosting": { + "type": "Direct", + "requested": "[9.0.1, )", + "resolved": "9.0.1", + "contentHash": "3wZNcVvC8RW44HDqqmIq+BqF5pgmTQdbNdR9NyYw33JSMnJuclwoJ2PEkrJ/KvD1U/hmqHVL3l5If+Hn3D1fWA==", + "dependencies": { + "Microsoft.Extensions.Configuration": "9.0.1", + "Microsoft.Extensions.Configuration.Abstractions": "9.0.1", + "Microsoft.Extensions.Configuration.Binder": "9.0.1", + "Microsoft.Extensions.Configuration.CommandLine": "9.0.1", + "Microsoft.Extensions.Configuration.EnvironmentVariables": "9.0.1", + "Microsoft.Extensions.Configuration.FileExtensions": "9.0.1", + "Microsoft.Extensions.Configuration.Json": "9.0.1", + "Microsoft.Extensions.Configuration.UserSecrets": "9.0.1", + "Microsoft.Extensions.DependencyInjection": "9.0.1", + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", + "Microsoft.Extensions.Diagnostics": "9.0.1", + "Microsoft.Extensions.FileProviders.Abstractions": "9.0.1", + "Microsoft.Extensions.FileProviders.Physical": "9.0.1", + "Microsoft.Extensions.Hosting.Abstractions": "9.0.1", + "Microsoft.Extensions.Logging": "9.0.1", + "Microsoft.Extensions.Logging.Abstractions": "9.0.1", + "Microsoft.Extensions.Logging.Configuration": "9.0.1", + "Microsoft.Extensions.Logging.Console": "9.0.1", + "Microsoft.Extensions.Logging.Debug": "9.0.1", + "Microsoft.Extensions.Logging.EventLog": "9.0.1", + "Microsoft.Extensions.Logging.EventSource": "9.0.1", + "Microsoft.Extensions.Options": "9.0.1" + } + }, + "Microsoft.Extensions.Options": { + "type": "Direct", + "requested": "[9.0.1, )", + "resolved": "9.0.1", + "contentHash": "nggoNKnWcsBIAaOWHA+53XZWrslC7aGeok+aR+epDPRy7HI7GwMnGZE8yEsL2Onw7kMOHVHwKcsDls1INkNUJQ==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", + "Microsoft.Extensions.Primitives": "9.0.1" + } + }, "Microsoft.WindowsAppSDK": { "type": "Direct", "requested": "[1.6.250108002, )", @@ -50,6 +90,28 @@ "resolved": "3.29.3", "contentHash": "t7nZFFUFwigCwZ+nIXHDLweXvwIpsOXi+P7J7smPT/QjI3EKxnCzTQOhBqyEh6XEzc/pNH+bCFOOSjatrPt6Tw==" }, + "Grpc.Core.Api": { + "type": "Transitive", + "resolved": "2.67.0", + "contentHash": "cL1/2f8kc8lsAGNdfCU25deedXVehhLA6GXKLLN4hAWx16XN7BmjYn3gFU+FBpir5yJynvDTHEypr3Tl0j7x/Q==" + }, + "Grpc.Net.Client": { + "type": "Transitive", + "resolved": "2.67.0", + "contentHash": "ofTjJQfegWkVlk5R4k/LlwpcucpsBzntygd4iAeuKd/eLMkmBWoXN+xcjYJ5IibAahRpIJU461jABZvT6E9dwA==", + "dependencies": { + "Grpc.Net.Common": "2.67.0", + "Microsoft.Extensions.Logging.Abstractions": "6.0.0" + } + }, + "Grpc.Net.Common": { + "type": "Transitive", + "resolved": "2.67.0", + "contentHash": "gazn1cD2Eol0/W5ZJRV4PYbNrxJ9oMs8pGYux5S9E4MymClvl7aqYSmpqgmWAUWvziRqK9K+yt3cjCMfQ3x/5A==", + "dependencies": { + "Grpc.Core.Api": "2.67.0" + } + }, "H.GeneratedIcons.System.Drawing": { "type": "Transitive", "resolved": "2.2.0", @@ -66,15 +128,242 @@ "H.GeneratedIcons.System.Drawing": "2.2.0" } }, + "Microsoft.Extensions.Configuration": { + "type": "Transitive", + "resolved": "9.0.1", + "contentHash": "VuthqFS+ju6vT8W4wevdhEFiRi1trvQtkzWLonApfF5USVzzDcTBoY3F24WvN/tffLSrycArVfX1bThm/9xY2A==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "9.0.1", + "Microsoft.Extensions.Primitives": "9.0.1" + } + }, + "Microsoft.Extensions.Configuration.Abstractions": { + "type": "Transitive", + "resolved": "9.0.1", + "contentHash": "+4hfFIY1UjBCXFTTOd+ojlDPq6mep3h5Vq5SYE3Pjucr7dNXmq4S/6P/LoVnZFz2e/5gWp/om4svUFgznfULcA==", + "dependencies": { + "Microsoft.Extensions.Primitives": "9.0.1" + } + }, + "Microsoft.Extensions.Configuration.Binder": { + "type": "Transitive", + "resolved": "9.0.1", + "contentHash": "w7kAyu1Mm7eParRV6WvGNNwA8flPTub16fwH49h7b/yqJZFTgYxnOVCuiah3G2bgseJMEq4DLjjsyQRvsdzRgA==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "9.0.1" + } + }, + "Microsoft.Extensions.Configuration.CommandLine": { + "type": "Transitive", + "resolved": "9.0.1", + "contentHash": "5WC1OsXfljC1KHEyL0yefpAyt1UZjrZ0/xyOqFowc5VntbE79JpCYOTSYFlxEuXm3Oq5xsgU2YXeZLTgAAX+DA==", + "dependencies": { + "Microsoft.Extensions.Configuration": "9.0.1", + "Microsoft.Extensions.Configuration.Abstractions": "9.0.1" + } + }, + "Microsoft.Extensions.Configuration.EnvironmentVariables": { + "type": "Transitive", + "resolved": "9.0.1", + "contentHash": "5HShUdF8KFAUSzoEu0DOFbX09FlcFtHxEalowyjM7Kji0EjdF0DLjHajb2IBvoqsExAYox+Z2GfbfGF7dH7lKQ==", + "dependencies": { + "Microsoft.Extensions.Configuration": "9.0.1", + "Microsoft.Extensions.Configuration.Abstractions": "9.0.1" + } + }, + "Microsoft.Extensions.Configuration.FileExtensions": { + "type": "Transitive", + "resolved": "9.0.1", + "contentHash": "QBOI8YVAyKqeshYOyxSe6co22oag431vxMu5xQe1EjXMkYE4xK4J71xLCW3/bWKmr9Aoy1VqGUARSLFnotk4Bg==", + "dependencies": { + "Microsoft.Extensions.Configuration": "9.0.1", + "Microsoft.Extensions.Configuration.Abstractions": "9.0.1", + "Microsoft.Extensions.FileProviders.Abstractions": "9.0.1", + "Microsoft.Extensions.FileProviders.Physical": "9.0.1", + "Microsoft.Extensions.Primitives": "9.0.1" + } + }, + "Microsoft.Extensions.Configuration.Json": { + "type": "Transitive", + "resolved": "9.0.1", + "contentHash": "z+g+lgPET1JRDjsOkFe51rkkNcnJgvOK5UIpeTfF1iAi0GkBJz5/yUuTa8a9V8HUh4gj4xFT5WGoMoXoSDKfGg==", + "dependencies": { + "Microsoft.Extensions.Configuration": "9.0.1", + "Microsoft.Extensions.Configuration.Abstractions": "9.0.1", + "Microsoft.Extensions.Configuration.FileExtensions": "9.0.1", + "Microsoft.Extensions.FileProviders.Abstractions": "9.0.1", + "System.Text.Json": "9.0.1" + } + }, + "Microsoft.Extensions.Configuration.UserSecrets": { + "type": "Transitive", + "resolved": "9.0.1", + "contentHash": "esGPOgLZ1tZddEomexhrU+LJ5YIsuJdkh0tU7r4WVpNZ15dLuMPqPW4Xe4txf3T2PDUX2ILe3nYQEDjZjfSEJg==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "9.0.1", + "Microsoft.Extensions.Configuration.Json": "9.0.1", + "Microsoft.Extensions.FileProviders.Abstractions": "9.0.1", + "Microsoft.Extensions.FileProviders.Physical": "9.0.1" + } + }, "Microsoft.Extensions.DependencyInjection.Abstractions": { "type": "Transitive", "resolved": "9.0.1", "contentHash": "Tr74eP0oQ3AyC24ch17N8PuEkrPbD0JqIfENCYqmgKYNOmL8wQKzLJu3ObxTUDrjnn4rHoR1qKa37/eQyHmCDA==" }, + "Microsoft.Extensions.Diagnostics": { + "type": "Transitive", + "resolved": "9.0.1", + "contentHash": "4ZmP6turxMFsNwK/MCko2fuIITaYYN/eXyyIRq1FjLDKnptdbn6xMb7u0zfSMzCGpzkx4RxH/g1jKN2IchG7uA==", + "dependencies": { + "Microsoft.Extensions.Configuration": "9.0.1", + "Microsoft.Extensions.Diagnostics.Abstractions": "9.0.1", + "Microsoft.Extensions.Options.ConfigurationExtensions": "9.0.1" + } + }, + "Microsoft.Extensions.Diagnostics.Abstractions": { + "type": "Transitive", + "resolved": "9.0.1", + "contentHash": "pfAPuVtHvG6dvZtAa0OQbXdDqq6epnr8z0/IIUjdmV0tMeI8Aj9KxDXvdDvqr+qNHTkmA7pZpChNxwNZt4GXVg==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", + "Microsoft.Extensions.Options": "9.0.1", + "System.Diagnostics.DiagnosticSource": "9.0.1" + } + }, + "Microsoft.Extensions.FileProviders.Abstractions": { + "type": "Transitive", + "resolved": "9.0.1", + "contentHash": "DguZYt1DWL05+8QKWL3b6bW7A2pC5kYFMY5iXM6W2M23jhvcNa8v6AU8PvVJBcysxHwr9/jax0agnwoBumsSwg==", + "dependencies": { + "Microsoft.Extensions.Primitives": "9.0.1" + } + }, + "Microsoft.Extensions.FileProviders.Physical": { + "type": "Transitive", + "resolved": "9.0.1", + "contentHash": "TKDMNRS66UTMEVT38/tU9hA63UTMvzI3DyNm5mx8+JCf3BaOtxgrvWLCI1y3J52PzT5yNl/T2KN5Z0KbApLZcg==", + "dependencies": { + "Microsoft.Extensions.FileProviders.Abstractions": "9.0.1", + "Microsoft.Extensions.FileSystemGlobbing": "9.0.1", + "Microsoft.Extensions.Primitives": "9.0.1" + } + }, + "Microsoft.Extensions.FileSystemGlobbing": { + "type": "Transitive", + "resolved": "9.0.1", + "contentHash": "Mxcp9NXuQMvAnudRZcgIb5SqlWrlullQzntBLTwuv0MPIJ5LqiGwbRqiyxgdk+vtCoUkplb0oXy5kAw1t469Ug==" + }, + "Microsoft.Extensions.Hosting.Abstractions": { + "type": "Transitive", + "resolved": "9.0.1", + "contentHash": "CwSMhLNe8HLkfbFzdz0CHWJhtWH3TtfZSicLBd/itFD+NqQtfGHmvqXHQbaFFl3mQB5PBb2gxwzWQyW2pIj7PA==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "9.0.1", + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", + "Microsoft.Extensions.Diagnostics.Abstractions": "9.0.1", + "Microsoft.Extensions.FileProviders.Abstractions": "9.0.1", + "Microsoft.Extensions.Logging.Abstractions": "9.0.1" + } + }, + "Microsoft.Extensions.Logging": { + "type": "Transitive", + "resolved": "9.0.1", + "contentHash": "E/k5r7S44DOW+08xQPnNbO8DKAQHhkspDboTThNJ6Z3/QBb4LC6gStNWzVmy3IvW7sUD+iJKf4fj0xEkqE7vnQ==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection": "9.0.1", + "Microsoft.Extensions.Logging.Abstractions": "9.0.1", + "Microsoft.Extensions.Options": "9.0.1" + } + }, + "Microsoft.Extensions.Logging.Abstractions": { + "type": "Transitive", + "resolved": "9.0.1", + "contentHash": "w2gUqXN/jNIuvqYwX3lbXagsizVNXYyt6LlF57+tMve4JYCEgCMMAjRce6uKcDASJgpMbErRT1PfHy2OhbkqEA==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", + "System.Diagnostics.DiagnosticSource": "9.0.1" + } + }, + "Microsoft.Extensions.Logging.Configuration": { + "type": "Transitive", + "resolved": "9.0.1", + "contentHash": "MeZePlyu3/74Wk4FHYSzXijADJUhWa7gxtaphLxhS8zEPWdJuBCrPo0sezdCSZaKCL+cZLSLobrb7xt2zHOxZQ==", + "dependencies": { + "Microsoft.Extensions.Configuration": "9.0.1", + "Microsoft.Extensions.Configuration.Abstractions": "9.0.1", + "Microsoft.Extensions.Configuration.Binder": "9.0.1", + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", + "Microsoft.Extensions.Logging": "9.0.1", + "Microsoft.Extensions.Logging.Abstractions": "9.0.1", + "Microsoft.Extensions.Options": "9.0.1", + "Microsoft.Extensions.Options.ConfigurationExtensions": "9.0.1" + } + }, + "Microsoft.Extensions.Logging.Console": { + "type": "Transitive", + "resolved": "9.0.1", + "contentHash": "YUzguHYlWfp4upfYlpVe3dnY59P25wc+/YLJ9/NQcblT3EvAB1CObQulClll7NtnFbbx4Js0a0UfyS8SbRsWXQ==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", + "Microsoft.Extensions.Logging": "9.0.1", + "Microsoft.Extensions.Logging.Abstractions": "9.0.1", + "Microsoft.Extensions.Logging.Configuration": "9.0.1", + "Microsoft.Extensions.Options": "9.0.1", + "System.Text.Json": "9.0.1" + } + }, + "Microsoft.Extensions.Logging.Debug": { + "type": "Transitive", + "resolved": "9.0.1", + "contentHash": "pzdyibIV8k4sym0Sszcp2MJCuXrpOGs9qfOvY+hCRu8k4HbdVoeKOLnacxHK6vEPITX5o5FjjsZW2zScLXTjYA==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", + "Microsoft.Extensions.Logging": "9.0.1", + "Microsoft.Extensions.Logging.Abstractions": "9.0.1" + } + }, + "Microsoft.Extensions.Logging.EventLog": { + "type": "Transitive", + "resolved": "9.0.1", + "contentHash": "+a4RlbwFWjsMujNNhf1Jy9Nm5CpMT+nxXxfgrkRSloPo0OAWhPSPsrFo6VWpvgIPPS41qmfAVWr3DqAmOoVZgQ==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", + "Microsoft.Extensions.Logging": "9.0.1", + "Microsoft.Extensions.Logging.Abstractions": "9.0.1", + "Microsoft.Extensions.Options": "9.0.1", + "System.Diagnostics.EventLog": "9.0.1" + } + }, + "Microsoft.Extensions.Logging.EventSource": { + "type": "Transitive", + "resolved": "9.0.1", + "contentHash": "d47ZRZUOg1dGOX+yisWScQ7w4+92OlR9beS2UXaiadUCA3RFoZzobzVgrzBX7Oo/qefx9LxdRcaeFpWKb3BNBw==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", + "Microsoft.Extensions.Logging": "9.0.1", + "Microsoft.Extensions.Logging.Abstractions": "9.0.1", + "Microsoft.Extensions.Options": "9.0.1", + "Microsoft.Extensions.Primitives": "9.0.1", + "System.Text.Json": "9.0.1" + } + }, + "Microsoft.Extensions.Options.ConfigurationExtensions": { + "type": "Transitive", + "resolved": "9.0.1", + "contentHash": "8RRKWtuU4fR+8MQLR/8CqZwZ9yc2xCpllw/WPRY7kskIqEq0hMcEI4AfUJO72yGiK2QJkrsDcUvgB5Yc+3+lyg==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "9.0.1", + "Microsoft.Extensions.Configuration.Binder": "9.0.1", + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", + "Microsoft.Extensions.Options": "9.0.1", + "Microsoft.Extensions.Primitives": "9.0.1" + } + }, "Microsoft.Extensions.Primitives": { "type": "Transitive", - "resolved": "5.0.1", - "contentHash": "5WPSmL4YeP7eW+Vc8XZ4DwjYWBAiSwDV9Hm63JJWcz1Ie3Xjv4KuJXzgCstj48LkLfVCYa7mLcx7y+q6yqVvtw==" + "resolved": "9.0.1", + "contentHash": "bHtTesA4lrSGD1ZUaMIx6frU3wyy0vYtTa/hM6gGQu5QNrydObv8T5COiGUWsisflAfmsaFOe9Xvw5NSO99z0g==" }, "Microsoft.Web.WebView2": { "type": "Transitive", @@ -104,6 +393,16 @@ "resolved": "9.0.0", "contentHash": "QhkXUl2gNrQtvPmtBTQHb0YsUrDiDQ2QS09YbtTTiSjGcf7NBqtYbrG/BE06zcBPCKEwQGzIv13IVdXNOSub2w==" }, + "System.Diagnostics.DiagnosticSource": { + "type": "Transitive", + "resolved": "9.0.1", + "contentHash": "yOcDWx4P/s1I83+7gQlgQLmhny2eNcU0cfo1NBWi+en4EAI38Jau+/neT85gUW6w1s7+FUJc2qNOmmwGLIREqA==" + }, + "System.Diagnostics.EventLog": { + "type": "Transitive", + "resolved": "9.0.1", + "contentHash": "iVnDpgYJsRaRFnk77kcLA3+913WfWDtnAKrQl9tQ5ahqKANTaJKmQdsuPWWiAPWE9pk1Kj4Pg9JGXWfFYYyakQ==" + }, "System.Drawing.Common": { "type": "Transitive", "resolved": "9.0.0", @@ -125,13 +424,35 @@ "System.Collections.Immutable": "9.0.0" } }, + "System.Text.Encodings.Web": { + "type": "Transitive", + "resolved": "9.0.1", + "contentHash": "XkspqduP2t1e1x2vBUAD/xZ5ZDvmywuUwsmB93MvyQLospJfqtX0GsR/kU0vUL2h4kmvf777z3txV2W4NrQ9Qg==" + }, + "System.Text.Json": { + "type": "Transitive", + "resolved": "9.0.1", + "contentHash": "eqWHDZqYPv1PvuvoIIx5pF74plL3iEOZOl/0kQP+Y0TEbtgNnM2W6k8h8EPYs+LTJZsXuWa92n5W5sHTWvE3VA==", + "dependencies": { + "System.IO.Pipelines": "9.0.1", + "System.Text.Encodings.Web": "9.0.1" + } + }, "Coder.Desktop.CoderSdk": { "type": "Project" }, + "Coder.Desktop.MutagenSdk": { + "type": "Project", + "dependencies": { + "Google.Protobuf": "[3.29.3, )", + "Grpc.Net.Client": "[2.67.0, )" + } + }, "Coder.Desktop.Vpn": { "type": "Project", "dependencies": { "Coder.Desktop.Vpn.Proto": "[1.0.0, )", + "Microsoft.Extensions.Configuration": "[9.0.1, )", "Semver": "[3.0.0, )", "System.IO.Pipelines": "[9.0.1, )" } @@ -163,6 +484,16 @@ "type": "Transitive", "resolved": "9.0.0", "contentHash": "z8FfGIaoeALdD+KF44A2uP8PZIQQtDGiXsOLuN8nohbKhkyKt7zGaZb+fKiCxTuBqG22Q7myIAioSWaIcOOrOw==" + }, + "System.Diagnostics.EventLog": { + "type": "Transitive", + "resolved": "9.0.1", + "contentHash": "iVnDpgYJsRaRFnk77kcLA3+913WfWDtnAKrQl9tQ5ahqKANTaJKmQdsuPWWiAPWE9pk1Kj4Pg9JGXWfFYYyakQ==" + }, + "System.Text.Encodings.Web": { + "type": "Transitive", + "resolved": "9.0.1", + "contentHash": "XkspqduP2t1e1x2vBUAD/xZ5ZDvmywuUwsmB93MvyQLospJfqtX0GsR/kU0vUL2h4kmvf777z3txV2W4NrQ9Qg==" } }, "net8.0-windows10.0.19041/win-x64": { @@ -185,6 +516,16 @@ "type": "Transitive", "resolved": "9.0.0", "contentHash": "z8FfGIaoeALdD+KF44A2uP8PZIQQtDGiXsOLuN8nohbKhkyKt7zGaZb+fKiCxTuBqG22Q7myIAioSWaIcOOrOw==" + }, + "System.Diagnostics.EventLog": { + "type": "Transitive", + "resolved": "9.0.1", + "contentHash": "iVnDpgYJsRaRFnk77kcLA3+913WfWDtnAKrQl9tQ5ahqKANTaJKmQdsuPWWiAPWE9pk1Kj4Pg9JGXWfFYYyakQ==" + }, + "System.Text.Encodings.Web": { + "type": "Transitive", + "resolved": "9.0.1", + "contentHash": "XkspqduP2t1e1x2vBUAD/xZ5ZDvmywuUwsmB93MvyQLospJfqtX0GsR/kU0vUL2h4kmvf777z3txV2W4NrQ9Qg==" } }, "net8.0-windows10.0.19041/win-x86": { @@ -207,6 +548,16 @@ "type": "Transitive", "resolved": "9.0.0", "contentHash": "z8FfGIaoeALdD+KF44A2uP8PZIQQtDGiXsOLuN8nohbKhkyKt7zGaZb+fKiCxTuBqG22Q7myIAioSWaIcOOrOw==" + }, + "System.Diagnostics.EventLog": { + "type": "Transitive", + "resolved": "9.0.1", + "contentHash": "iVnDpgYJsRaRFnk77kcLA3+913WfWDtnAKrQl9tQ5ahqKANTaJKmQdsuPWWiAPWE9pk1Kj4Pg9JGXWfFYYyakQ==" + }, + "System.Text.Encodings.Web": { + "type": "Transitive", + "resolved": "9.0.1", + "contentHash": "XkspqduP2t1e1x2vBUAD/xZ5ZDvmywuUwsmB93MvyQLospJfqtX0GsR/kU0vUL2h4kmvf777z3txV2W4NrQ9Qg==" } } } diff --git a/Installer/Program.cs b/Installer/Program.cs index 0bec102..775a723 100644 --- a/Installer/Program.cs +++ b/Installer/Program.cs @@ -250,17 +250,21 @@ private static int BuildMsiPackage(MsiOptions opts) programFiles64Folder.AddDir(installDir); project.AddDir(programFiles64Folder); - // Add registry values that are consumed by the manager. Note that these - // should not be changed. See Vpn.Service/Program.cs and - // Vpn.Service/ManagerConfig.cs for more details. + project.AddRegValues( + // Add registry values that are consumed by the manager. Note that these + // should not be changed. See Vpn.Service/Program.cs and + // Vpn.Service/ManagerConfig.cs for more details. new RegValue(RegistryHive, RegistryKey, "Manager:ServiceRpcPipeName", "Coder.Desktop.Vpn"), new RegValue(RegistryHive, RegistryKey, "Manager:TunnelBinaryPath", $"[INSTALLFOLDER]{opts.VpnDir}\\coder-vpn.exe"), new RegValue(RegistryHive, RegistryKey, "Manager:LogFileLocation", @"[INSTALLFOLDER]coder-desktop-service.log"), new RegValue(RegistryHive, RegistryKey, "Manager:TunnelBinarySignatureSigner", "Coder Technologies Inc."), - new RegValue(RegistryHive, RegistryKey, "Manager:TunnelBinaryAllowVersionMismatch", "false")); + new RegValue(RegistryHive, RegistryKey, "Manager:TunnelBinaryAllowVersionMismatch", "false"), + // Add registry values that are consumed by the MutagenController. + new RegValue(RegistryHive, RegistryKey, "MutagenController:MutagenExecutablePath", @"[INSTALLFOLDER]mutagen.exe") + ); // Note: most of this control panel info will not be visible as this // package is usually hidden in favor of the bootstrapper showing diff --git a/Tests.App/Services/MutagenControllerTest.cs b/Tests.App/Services/MutagenControllerTest.cs new file mode 100644 index 0000000..40d6a48 --- /dev/null +++ b/Tests.App/Services/MutagenControllerTest.cs @@ -0,0 +1,138 @@ +using System.Diagnostics; +using System.Runtime.InteropServices; +using Coder.Desktop.App.Services; + +namespace Coder.Desktop.Tests.App.Services; + +[TestFixture] +public class MutagenControllerTest +{ + [OneTimeSetUp] + public async Task DownloadMutagen() + { + var ct = new CancellationTokenSource(TimeSpan.FromSeconds(60)).Token; + var scriptDirectory = Path.GetFullPath(Path.Combine(TestContext.CurrentContext.TestDirectory, + "..", "..", "..", "..", "scripts")); + var process = new Process(); + process.StartInfo.FileName = "powershell.exe"; + process.StartInfo.UseShellExecute = false; + process.StartInfo.Arguments = $"-ExecutionPolicy Bypass -File Get-Mutagen.ps1 -arch {_arch}"; + process.StartInfo.RedirectStandardError = true; + process.StartInfo.RedirectStandardOutput = true; + process.StartInfo.WorkingDirectory = scriptDirectory; + process.Start(); + var output = await process.StandardOutput.ReadToEndAsync(ct); + TestContext.Out.Write(output); + var error = await process.StandardError.ReadToEndAsync(ct); + TestContext.Error.Write(error); + Assert.That(process.ExitCode, Is.EqualTo(0)); + _mutagenBinaryPath = Path.Combine(scriptDirectory, "files", $"mutagen-windows-{_arch}.exe"); + Assert.That(File.Exists(_mutagenBinaryPath)); + } + + [SetUp] + public void CreateTempDir() + { + _tempDirectory = Directory.CreateTempSubdirectory(GetType().Name); + TestContext.Out.WriteLine($"temp directory: {_tempDirectory}"); + } + + private readonly string _arch = RuntimeInformation.ProcessArchitecture switch + { + Architecture.X64 => "x64", + Architecture.Arm64 => "arm64", + // We only support amd64 and arm64 on Windows currently. + _ => throw new PlatformNotSupportedException( + $"Unsupported architecture '{RuntimeInformation.ProcessArchitecture}'. Coder only supports x64 and arm64."), + }; + + private string _mutagenBinaryPath; + private DirectoryInfo _tempDirectory; + + [Test(Description = "Shut down daemon when no sessions")] + [CancelAfter(30_000)] + public async Task ShutdownNoSessions(CancellationToken ct) + { + // NUnit runs each test in a temporary directory + var dataDirectory = _tempDirectory.FullName; + await using var controller = new MutagenController(_mutagenBinaryPath, dataDirectory); + await controller.Initialize(ct); + + // log file tells us the daemon was started. + var logPath = Path.Combine(dataDirectory, "daemon.log"); + Assert.That(File.Exists(logPath)); + + var lockPath = Path.Combine(dataDirectory, "daemon", "daemon.lock"); + // If we can lock the daemon.lock file, it means the daemon has stopped. + while (true) + { + ct.ThrowIfCancellationRequested(); + try + { + await using var lockFile = new FileStream(lockPath, FileMode.Open, FileAccess.Write, FileShare.None); + } + catch (IOException e) + { + TestContext.Out.WriteLine($"Didn't get lock (will retry): {e.Message}"); + await Task.Delay(100, ct); + } + + break; + } + } + + [Test(Description = "Daemon is restarted when we create a session")] + [CancelAfter(30_000)] + public async Task CreateRestartsDaemon(CancellationToken ct) + { + // NUnit runs each test in a temporary directory + var dataDirectory = _tempDirectory.FullName; + await using (var controller = new MutagenController(_mutagenBinaryPath, dataDirectory)) + { + await controller.Initialize(ct); + await controller.CreateSyncSession(new SyncSession(), ct); + } + + var logPath = Path.Combine(dataDirectory, "daemon.log"); + Assert.That(File.Exists(logPath)); + var logLines = File.ReadAllLines(logPath); + + // Here we're going to use the log to verify the daemon was started 2 times. + // slightly brittle, but unlikely this log line will change. + Assert.That(logLines.Count(s => s.Contains("[sync] Session manager initialized")), Is.EqualTo(2)); + } + + [Test(Description = "Controller kills orphaned daemon")] + [CancelAfter(30_000)] + public async Task Orphaned(CancellationToken ct) + { + // NUnit runs each test in a temporary directory + var dataDirectory = _tempDirectory.FullName; + MutagenController? controller1 = null; + MutagenController? controller2 = null; + try + { + controller1 = new MutagenController(_mutagenBinaryPath, dataDirectory); + await controller1.Initialize(ct); + await controller1.CreateSyncSession(new SyncSession(), ct); + + controller2 = new MutagenController(_mutagenBinaryPath, dataDirectory); + await controller2.Initialize(ct); + } + finally + { + if (controller1 != null) await controller1.DisposeAsync(); + if (controller2 != null) await controller2.DisposeAsync(); + } + + var logPath = Path.Combine(dataDirectory, "daemon.log"); + Assert.That(File.Exists(logPath)); + var logLines = File.ReadAllLines(logPath); + + // Here we're going to use the log to verify the daemon was started 3 times. + // slightly brittle, but unlikely this log line will change. + Assert.That(logLines.Count(s => s.Contains("[sync] Session manager initialized")), Is.EqualTo(3)); + } + + // TODO: Add more tests once we actually implement creating sessions on the daemon +} diff --git a/Tests.Vpn.Service/packages.lock.json b/Tests.Vpn.Service/packages.lock.json index 45e0457..7ba4c03 100644 --- a/Tests.Vpn.Service/packages.lock.json +++ b/Tests.Vpn.Service/packages.lock.json @@ -474,6 +474,7 @@ "type": "Project", "dependencies": { "Coder.Desktop.Vpn.Proto": "[1.0.0, )", + "Microsoft.Extensions.Configuration": "[9.0.1, )", "Semver": "[3.0.0, )", "System.IO.Pipelines": "[9.0.1, )" } diff --git a/Tests.Vpn/packages.lock.json b/Tests.Vpn/packages.lock.json index 10f6f62..07d90a8 100644 --- a/Tests.Vpn/packages.lock.json +++ b/Tests.Vpn/packages.lock.json @@ -46,10 +46,27 @@ "resolved": "17.12.0", "contentHash": "4svMznBd5JM21JIG2xZKGNanAHNXplxf/kQDFfLHXQ3OnpJkayRK/TjacFjA+EYmoyuNXHo/sOETEfcYtAzIrA==" }, + "Microsoft.Extensions.Configuration": { + "type": "Transitive", + "resolved": "9.0.1", + "contentHash": "VuthqFS+ju6vT8W4wevdhEFiRi1trvQtkzWLonApfF5USVzzDcTBoY3F24WvN/tffLSrycArVfX1bThm/9xY2A==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "9.0.1", + "Microsoft.Extensions.Primitives": "9.0.1" + } + }, + "Microsoft.Extensions.Configuration.Abstractions": { + "type": "Transitive", + "resolved": "9.0.1", + "contentHash": "+4hfFIY1UjBCXFTTOd+ojlDPq6mep3h5Vq5SYE3Pjucr7dNXmq4S/6P/LoVnZFz2e/5gWp/om4svUFgznfULcA==", + "dependencies": { + "Microsoft.Extensions.Primitives": "9.0.1" + } + }, "Microsoft.Extensions.Primitives": { "type": "Transitive", - "resolved": "5.0.1", - "contentHash": "5WPSmL4YeP7eW+Vc8XZ4DwjYWBAiSwDV9Hm63JJWcz1Ie3Xjv4KuJXzgCstj48LkLfVCYa7mLcx7y+q6yqVvtw==" + "resolved": "9.0.1", + "contentHash": "bHtTesA4lrSGD1ZUaMIx6frU3wyy0vYtTa/hM6gGQu5QNrydObv8T5COiGUWsisflAfmsaFOe9Xvw5NSO99z0g==" }, "Microsoft.TestPlatform.ObjectModel": { "type": "Transitive", @@ -95,6 +112,7 @@ "type": "Project", "dependencies": { "Coder.Desktop.Vpn.Proto": "[1.0.0, )", + "Microsoft.Extensions.Configuration": "[9.0.1, )", "Semver": "[3.0.0, )", "System.IO.Pipelines": "[9.0.1, )" } diff --git a/Vpn.DebugClient/packages.lock.json b/Vpn.DebugClient/packages.lock.json index 403a41b..5844675 100644 --- a/Vpn.DebugClient/packages.lock.json +++ b/Vpn.DebugClient/packages.lock.json @@ -7,10 +7,27 @@ "resolved": "3.29.3", "contentHash": "t7nZFFUFwigCwZ+nIXHDLweXvwIpsOXi+P7J7smPT/QjI3EKxnCzTQOhBqyEh6XEzc/pNH+bCFOOSjatrPt6Tw==" }, + "Microsoft.Extensions.Configuration": { + "type": "Transitive", + "resolved": "9.0.1", + "contentHash": "VuthqFS+ju6vT8W4wevdhEFiRi1trvQtkzWLonApfF5USVzzDcTBoY3F24WvN/tffLSrycArVfX1bThm/9xY2A==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "9.0.1", + "Microsoft.Extensions.Primitives": "9.0.1" + } + }, + "Microsoft.Extensions.Configuration.Abstractions": { + "type": "Transitive", + "resolved": "9.0.1", + "contentHash": "+4hfFIY1UjBCXFTTOd+ojlDPq6mep3h5Vq5SYE3Pjucr7dNXmq4S/6P/LoVnZFz2e/5gWp/om4svUFgznfULcA==", + "dependencies": { + "Microsoft.Extensions.Primitives": "9.0.1" + } + }, "Microsoft.Extensions.Primitives": { "type": "Transitive", - "resolved": "5.0.1", - "contentHash": "5WPSmL4YeP7eW+Vc8XZ4DwjYWBAiSwDV9Hm63JJWcz1Ie3Xjv4KuJXzgCstj48LkLfVCYa7mLcx7y+q6yqVvtw==" + "resolved": "9.0.1", + "contentHash": "bHtTesA4lrSGD1ZUaMIx6frU3wyy0vYtTa/hM6gGQu5QNrydObv8T5COiGUWsisflAfmsaFOe9Xvw5NSO99z0g==" }, "Semver": { "type": "Transitive", @@ -29,6 +46,7 @@ "type": "Project", "dependencies": { "Coder.Desktop.Vpn.Proto": "[1.0.0, )", + "Microsoft.Extensions.Configuration": "[9.0.1, )", "Semver": "[3.0.0, )", "System.IO.Pipelines": "[9.0.1, )" } diff --git a/Vpn.Service/packages.lock.json b/Vpn.Service/packages.lock.json index ace2cdb..fb4185a 100644 --- a/Vpn.Service/packages.lock.json +++ b/Vpn.Service/packages.lock.json @@ -416,6 +416,7 @@ "type": "Project", "dependencies": { "Coder.Desktop.Vpn.Proto": "[1.0.0, )", + "Microsoft.Extensions.Configuration": "[9.0.1, )", "Semver": "[3.0.0, )", "System.IO.Pipelines": "[9.0.1, )" } diff --git a/Vpn.Service/RegistryConfigurationSource.cs b/Vpn/RegistryConfigurationSource.cs similarity index 96% rename from Vpn.Service/RegistryConfigurationSource.cs rename to Vpn/RegistryConfigurationSource.cs index 8e2dd0d..2e67b87 100644 --- a/Vpn.Service/RegistryConfigurationSource.cs +++ b/Vpn/RegistryConfigurationSource.cs @@ -1,7 +1,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Win32; -namespace Coder.Desktop.Vpn.Service; +namespace Coder.Desktop.Vpn; public class RegistryConfigurationSource : IConfigurationSource { diff --git a/Vpn/Vpn.csproj b/Vpn/Vpn.csproj index c08b669..a26061e 100644 --- a/Vpn/Vpn.csproj +++ b/Vpn/Vpn.csproj @@ -14,6 +14,7 @@ + diff --git a/Vpn/packages.lock.json b/Vpn/packages.lock.json index 5eca812..8e56ce8 100644 --- a/Vpn/packages.lock.json +++ b/Vpn/packages.lock.json @@ -2,6 +2,16 @@ "version": 1, "dependencies": { "net8.0": { + "Microsoft.Extensions.Configuration": { + "type": "Direct", + "requested": "[9.0.1, )", + "resolved": "9.0.1", + "contentHash": "VuthqFS+ju6vT8W4wevdhEFiRi1trvQtkzWLonApfF5USVzzDcTBoY3F24WvN/tffLSrycArVfX1bThm/9xY2A==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "9.0.1", + "Microsoft.Extensions.Primitives": "9.0.1" + } + }, "Semver": { "type": "Direct", "requested": "[3.0.0, )", @@ -22,10 +32,18 @@ "resolved": "3.29.3", "contentHash": "t7nZFFUFwigCwZ+nIXHDLweXvwIpsOXi+P7J7smPT/QjI3EKxnCzTQOhBqyEh6XEzc/pNH+bCFOOSjatrPt6Tw==" }, + "Microsoft.Extensions.Configuration.Abstractions": { + "type": "Transitive", + "resolved": "9.0.1", + "contentHash": "+4hfFIY1UjBCXFTTOd+ojlDPq6mep3h5Vq5SYE3Pjucr7dNXmq4S/6P/LoVnZFz2e/5gWp/om4svUFgznfULcA==", + "dependencies": { + "Microsoft.Extensions.Primitives": "9.0.1" + } + }, "Microsoft.Extensions.Primitives": { "type": "Transitive", - "resolved": "5.0.1", - "contentHash": "5WPSmL4YeP7eW+Vc8XZ4DwjYWBAiSwDV9Hm63JJWcz1Ie3Xjv4KuJXzgCstj48LkLfVCYa7mLcx7y+q6yqVvtw==" + "resolved": "9.0.1", + "contentHash": "bHtTesA4lrSGD1ZUaMIx6frU3wyy0vYtTa/hM6gGQu5QNrydObv8T5COiGUWsisflAfmsaFOe9Xvw5NSO99z0g==" }, "Coder.Desktop.Vpn.Proto": { "type": "Project", diff --git a/scripts/Get-Mutagen.ps1 b/scripts/Get-Mutagen.ps1 new file mode 100644 index 0000000..c540809 --- /dev/null +++ b/scripts/Get-Mutagen.ps1 @@ -0,0 +1,44 @@ +# Usage: Get-Mutagen.ps1 -arch +param ( + [ValidateSet("x64", "arm64")] + [Parameter(Mandatory = $true)] + [string] $arch +) + +function Download-File([string] $url, [string] $outputPath, [string] $etagFile) { + Write-Host "Downloading '$url' to '$outputPath'" + # We use `curl.exe` here because `Invoke-WebRequest` is notoriously slow. + & curl.exe ` + --progress-bar ` + --show-error ` + --fail ` + --location ` + --etag-compare $etagFile ` + --etag-save $etagFile ` + --output $outputPath ` + $url + if ($LASTEXITCODE -ne 0) { throw "Failed to download $url" } + if (!(Test-Path $outputPath) -or (Get-Item $outputPath).Length -eq 0) { + throw "Failed to download '$url', output file '$outputPath' is missing or empty" + } +} + +$goArch = switch ($arch) { + "x64" { "amd64" } + "arm64" { "arm64" } + default { throw "Unsupported architecture: $arch" } +} + +# Download the mutagen binary from our bucket for this platform if we don't have +# it yet (or it's different). +$mutagenVersion = "v0.18.1" +$mutagenPath = Join-Path $PSScriptRoot "files\mutagen-windows-$($arch).exe" +$mutagenUrl = "https://storage.googleapis.com/coder-desktop/mutagen/$($mutagenVersion)/mutagen-windows-$($goArch).exe" +$mutagenEtagFile = $mutagenPath + ".etag" +Download-File $mutagenUrl $mutagenPath $mutagenEtagFile + +# Download mutagen agents tarball. +$mutagenAgentsPath = Join-Path $PSScriptRoot "files\mutagen-agents.tar.gz" +$mutagenAgentsUrl = "https://storage.googleapis.com/coder-desktop/mutagen/$($mutagenVersion)/mutagen-agents.tar.gz" +$mutagenAgentsEtagFile = $mutagenAgentsPath + ".etag" +Download-File $mutagenAgentsUrl $mutagenAgentsPath $mutagenAgentsEtagFile diff --git a/scripts/Publish.ps1 b/scripts/Publish.ps1 index fa3a571..5f7a25e 100644 --- a/scripts/Publish.ps1 +++ b/scripts/Publish.ps1 @@ -83,30 +83,6 @@ function Add-CoderSignature([string] $path) { } } -function Download-File([string] $url, [string] $outputPath, [string] $etagFile) { - Write-Host "Downloading '$url' to '$outputPath'" - # We use `curl.exe` here because `Invoke-WebRequest` is notoriously slow. - & curl.exe ` - --progress-bar ` - --show-error ` - --fail ` - --location ` - --etag-compare $etagFile ` - --etag-save $etagFile ` - --output $outputPath ` - $url - if ($LASTEXITCODE -ne 0) { throw "Failed to download $url" } - if (!(Test-Path $outputPath) -or (Get-Item $outputPath).Length -eq 0) { - throw "Failed to download '$url', output file '$outputPath' is missing or empty" - } -} - -$goArch = switch ($arch) { - "x64" { "amd64" } - "arm64" { "arm64" } - default { throw "Unsupported architecture: $arch" } -} - # CD to the root of the repo $repoRoot = Resolve-Path (Join-Path $PSScriptRoot "..") Push-Location $repoRoot @@ -169,21 +145,15 @@ if ($null -eq $wintunDllSrc) { $wintunDllDest = Join-Path $vpnFilesPath "wintun.dll" Copy-Item $wintunDllSrc $wintunDllDest -# Download the mutagen binary from our bucket for this platform if we don't have -# it yet (or it's different). -$mutagenVersion = "v0.18.1" -$mutagenSrcPath = Join-Path $repoRoot "scripts\files\mutagen-windows-$($goArch).exe" -$mutagenSrcUrl = "https://storage.googleapis.com/coder-desktop/mutagen/$($mutagenVersion)/mutagen-windows-$($goArch).exe" -$mutagenEtagFile = $mutagenSrcPath + ".etag" -Download-File $mutagenSrcUrl $mutagenSrcPath $mutagenEtagFile +$scriptRoot = Join-Path $repoRoot "scripts" +$getMutagen = Join-Path $scriptRoot "Get-Mutagen.ps1" +& $getMutagen -arch $arch + +$mutagenSrcPath = Join-Path $scriptRoot "files\mutagen-windows-$($arch).exe" $mutagenDestPath = Join-Path $vpnFilesPath "mutagen.exe" Copy-Item $mutagenSrcPath $mutagenDestPath -# Download mutagen agents tarball. -$mutagenAgentsSrcPath = Join-Path $repoRoot "scripts\files\mutagen-agents.tar.gz" -$mutagenAgentsSrcUrl = "https://storage.googleapis.com/coder-desktop/mutagen/$($mutagenVersion)/mutagen-agents.tar.gz" -$mutagenAgentsEtagFile = $mutagenAgentsSrcPath + ".etag" -Download-File $mutagenAgentsSrcUrl $mutagenAgentsSrcPath $mutagenAgentsEtagFile +$mutagenAgentsSrcPath = Join-Path $scriptRoot "files\mutagen-agents.tar.gz" $mutagenAgentsDestPath = Join-Path $vpnFilesPath "mutagen-agents.tar.gz" Copy-Item $mutagenAgentsSrcPath $mutagenAgentsDestPath From 3fc4d6f2cb8a1d4ccc1cd01e914126f9aec1e93c Mon Sep 17 00:00:00 2001 From: Spike Curtis Date: Mon, 17 Mar 2025 16:55:51 +0400 Subject: [PATCH 2/4] Code review cleanup: * refactor retry logic for StartDaemon * fixup checks for _sessionCount < 0 * fix Vpn targeting windows to avoid complaints about the registry API calxOAOA --- App/Services/MutagenController.cs | 69 +++++++++++++++--------------- Installer/Program.cs | 7 +-- Tests.Vpn/packages.lock.json | 54 +---------------------- Vpn.DebugClient/packages.lock.json | 43 +------------------ Vpn/Vpn.csproj | 2 +- Vpn/packages.lock.json | 2 +- 6 files changed, 42 insertions(+), 135 deletions(-) diff --git a/App/Services/MutagenController.cs b/App/Services/MutagenController.cs index 234be45..532f08b 100644 --- a/App/Services/MutagenController.cs +++ b/App/Services/MutagenController.cs @@ -46,7 +46,7 @@ public interface ISyncSessionController } // These values are the config option names used in the registry. Any option -// here can be configured with `(Debug)?Mutagen:OptionName` in the registry. +// here can be configured with `(Debug)?MutagenController:OptionName` in the registry. // // They should not be changed without backwards compatibility considerations. // If changed here, they should also be changed in the installer. @@ -141,7 +141,7 @@ public async Task> ListSyncSessions(CancellationToken ct) // reads of _sessionCount are atomic, so don't bother locking for this quick check. switch (_sessionCount) { - case -1: + case < 0: throw new InvalidOperationException("Controller must be Initialized first"); case 0: // If we already know there are no sessions, don't start up the daemon @@ -162,33 +162,14 @@ public async Task Initialize(CancellationToken ct) _sessionCount = -2; // in progress } - const int maxAttempts = 5; - ListResponse? sessions = null; - for (var attempts = 1; attempts <= maxAttempts; attempts++) + var client = await EnsureDaemon(ct); + var sessions = await client.Synchronization.ListAsync(new ListRequest { - ct.ThrowIfCancellationRequested(); - try + Selection = new Selection { - var client = await EnsureDaemon(ct); - sessions = await client.Synchronization.ListAsync(new ListRequest - { - Selection = new Selection - { - All = true, - }, - }, cancellationToken: ct); - } - catch (Exception e) when (e is not OperationCanceledException) - { - if (attempts == maxAttempts) - throw; - // back off a little and try again. - await Task.Delay(100, ct); - continue; - } - - break; - } + All = true, + }, + }, cancellationToken: ct); using (_ = await _lock.LockAsync(ct)) { @@ -211,7 +192,7 @@ public async Task Initialize(CancellationToken ct) public async Task TerminateSyncSession(SyncSession session, CancellationToken ct) { - if (_sessionCount == -1) throw new InvalidOperationException("Controller must be Initialized first"); + if (_sessionCount < 0) throw new InvalidOperationException("Controller must be Initialized first"); var client = await EnsureDaemon(ct); // TODO: implement @@ -272,11 +253,7 @@ private async Task EnsureDaemon(CancellationToken ct) // private void RemoveTransition(Task transition) { - using (_ = _lock.Lock()) - { - ; - } - + using var _ = _lock.Lock(); if (_inProgressTransition == transition) _inProgressTransition = null; } @@ -293,9 +270,31 @@ private void RemoveTransition(Task transition) // Mainline; no daemon running. } - using (_ = await _lock.LockAsync(ct)) + // If we get some failure while creating the log file or starting the process, we'll retry + // it up to 5 times x 100ms. Those issues should resolve themselves quickly if they are + // going to at all. + const int maxAttempts = 5; + ListResponse? sessions = null; + for (var attempts = 1; attempts <= maxAttempts; attempts++) { - StartDaemonProcessLocked(); + ct.ThrowIfCancellationRequested(); + try + { + using (_ = await _lock.LockAsync(ct)) + { + StartDaemonProcessLocked(); + } + } + catch (Exception e) when (e is not OperationCanceledException) + { + if (attempts == maxAttempts) + throw; + // back off a little and try again. + await Task.Delay(100, ct); + continue; + } + + break; } return await WaitForDaemon(ct); diff --git a/Installer/Program.cs b/Installer/Program.cs index 775a723..02b7dab 100644 --- a/Installer/Program.cs +++ b/Installer/Program.cs @@ -262,9 +262,10 @@ private static int BuildMsiPackage(MsiOptions opts) @"[INSTALLFOLDER]coder-desktop-service.log"), new RegValue(RegistryHive, RegistryKey, "Manager:TunnelBinarySignatureSigner", "Coder Technologies Inc."), new RegValue(RegistryHive, RegistryKey, "Manager:TunnelBinaryAllowVersionMismatch", "false"), - // Add registry values that are consumed by the MutagenController. - new RegValue(RegistryHive, RegistryKey, "MutagenController:MutagenExecutablePath", @"[INSTALLFOLDER]mutagen.exe") - ); + // Add registry values that are consumed by the MutagenController. See App/Services/MutagenController.cs + new RegValue(RegistryHive, RegistryKey, "MutagenController:MutagenExecutablePath", + @"[INSTALLFOLDER]mutagen.exe") + ); // Note: most of this control panel info will not be visible as this // package is usually hidden in favor of the bootstrapper showing diff --git a/Tests.Vpn/packages.lock.json b/Tests.Vpn/packages.lock.json index 07d90a8..26adab4 100644 --- a/Tests.Vpn/packages.lock.json +++ b/Tests.Vpn/packages.lock.json @@ -36,38 +36,11 @@ "resolved": "4.6.0", "contentHash": "R7e1+a4vuV/YS+ItfL7f//rG+JBvVeVLX4mHzFEZo4W1qEKl8Zz27AqvQSAqo+BtIzUCo4aAJMYa56VXS4hudw==" }, - "Google.Protobuf": { - "type": "Transitive", - "resolved": "3.29.3", - "contentHash": "t7nZFFUFwigCwZ+nIXHDLweXvwIpsOXi+P7J7smPT/QjI3EKxnCzTQOhBqyEh6XEzc/pNH+bCFOOSjatrPt6Tw==" - }, "Microsoft.CodeCoverage": { "type": "Transitive", "resolved": "17.12.0", "contentHash": "4svMznBd5JM21JIG2xZKGNanAHNXplxf/kQDFfLHXQ3OnpJkayRK/TjacFjA+EYmoyuNXHo/sOETEfcYtAzIrA==" }, - "Microsoft.Extensions.Configuration": { - "type": "Transitive", - "resolved": "9.0.1", - "contentHash": "VuthqFS+ju6vT8W4wevdhEFiRi1trvQtkzWLonApfF5USVzzDcTBoY3F24WvN/tffLSrycArVfX1bThm/9xY2A==", - "dependencies": { - "Microsoft.Extensions.Configuration.Abstractions": "9.0.1", - "Microsoft.Extensions.Primitives": "9.0.1" - } - }, - "Microsoft.Extensions.Configuration.Abstractions": { - "type": "Transitive", - "resolved": "9.0.1", - "contentHash": "+4hfFIY1UjBCXFTTOd+ojlDPq6mep3h5Vq5SYE3Pjucr7dNXmq4S/6P/LoVnZFz2e/5gWp/om4svUFgznfULcA==", - "dependencies": { - "Microsoft.Extensions.Primitives": "9.0.1" - } - }, - "Microsoft.Extensions.Primitives": { - "type": "Transitive", - "resolved": "9.0.1", - "contentHash": "bHtTesA4lrSGD1ZUaMIx6frU3wyy0vYtTa/hM6gGQu5QNrydObv8T5COiGUWsisflAfmsaFOe9Xvw5NSO99z0g==" - }, "Microsoft.TestPlatform.ObjectModel": { "type": "Transitive", "resolved": "17.12.0", @@ -90,38 +63,13 @@ "resolved": "13.0.1", "contentHash": "ppPFpBcvxdsfUonNcvITKqLl3bqxWbDCZIzDWHzjpdAHRFfZe0Dw9HmA0+za13IdyrgJwpkDTDA9fHaxOrt20A==" }, - "Semver": { - "type": "Transitive", - "resolved": "3.0.0", - "contentHash": "9jZCicsVgTebqkAujRWtC9J1A5EQVlu0TVKHcgoCuv345ve5DYf4D1MjhKEnQjdRZo6x/vdv6QQrYFs7ilGzLA==", - "dependencies": { - "Microsoft.Extensions.Primitives": "5.0.1" - } - }, - "System.IO.Pipelines": { - "type": "Transitive", - "resolved": "9.0.1", - "contentHash": "uXf5o8eV/gtzDQY4lGROLFMWQvcViKcF8o4Q6KpIOjloAQXrnscQSu6gTxYJMHuNJnh7szIF9AzkaEq+zDLoEg==" - }, "System.Reflection.Metadata": { "type": "Transitive", "resolved": "1.6.0", "contentHash": "COC1aiAJjCoA5GBF+QKL2uLqEBew4JsCkQmoHKbN3TlOZKa2fKLz5CpiRQKDz0RsAOEGsVKqOD5bomsXq/4STQ==" }, "Coder.Desktop.Vpn": { - "type": "Project", - "dependencies": { - "Coder.Desktop.Vpn.Proto": "[1.0.0, )", - "Microsoft.Extensions.Configuration": "[9.0.1, )", - "Semver": "[3.0.0, )", - "System.IO.Pipelines": "[9.0.1, )" - } - }, - "Coder.Desktop.Vpn.Proto": { - "type": "Project", - "dependencies": { - "Google.Protobuf": "[3.29.3, )" - } + "type": "Project" } } } diff --git a/Vpn.DebugClient/packages.lock.json b/Vpn.DebugClient/packages.lock.json index 5844675..79788d7 100644 --- a/Vpn.DebugClient/packages.lock.json +++ b/Vpn.DebugClient/packages.lock.json @@ -7,49 +7,8 @@ "resolved": "3.29.3", "contentHash": "t7nZFFUFwigCwZ+nIXHDLweXvwIpsOXi+P7J7smPT/QjI3EKxnCzTQOhBqyEh6XEzc/pNH+bCFOOSjatrPt6Tw==" }, - "Microsoft.Extensions.Configuration": { - "type": "Transitive", - "resolved": "9.0.1", - "contentHash": "VuthqFS+ju6vT8W4wevdhEFiRi1trvQtkzWLonApfF5USVzzDcTBoY3F24WvN/tffLSrycArVfX1bThm/9xY2A==", - "dependencies": { - "Microsoft.Extensions.Configuration.Abstractions": "9.0.1", - "Microsoft.Extensions.Primitives": "9.0.1" - } - }, - "Microsoft.Extensions.Configuration.Abstractions": { - "type": "Transitive", - "resolved": "9.0.1", - "contentHash": "+4hfFIY1UjBCXFTTOd+ojlDPq6mep3h5Vq5SYE3Pjucr7dNXmq4S/6P/LoVnZFz2e/5gWp/om4svUFgznfULcA==", - "dependencies": { - "Microsoft.Extensions.Primitives": "9.0.1" - } - }, - "Microsoft.Extensions.Primitives": { - "type": "Transitive", - "resolved": "9.0.1", - "contentHash": "bHtTesA4lrSGD1ZUaMIx6frU3wyy0vYtTa/hM6gGQu5QNrydObv8T5COiGUWsisflAfmsaFOe9Xvw5NSO99z0g==" - }, - "Semver": { - "type": "Transitive", - "resolved": "3.0.0", - "contentHash": "9jZCicsVgTebqkAujRWtC9J1A5EQVlu0TVKHcgoCuv345ve5DYf4D1MjhKEnQjdRZo6x/vdv6QQrYFs7ilGzLA==", - "dependencies": { - "Microsoft.Extensions.Primitives": "5.0.1" - } - }, - "System.IO.Pipelines": { - "type": "Transitive", - "resolved": "9.0.1", - "contentHash": "uXf5o8eV/gtzDQY4lGROLFMWQvcViKcF8o4Q6KpIOjloAQXrnscQSu6gTxYJMHuNJnh7szIF9AzkaEq+zDLoEg==" - }, "Coder.Desktop.Vpn": { - "type": "Project", - "dependencies": { - "Coder.Desktop.Vpn.Proto": "[1.0.0, )", - "Microsoft.Extensions.Configuration": "[9.0.1, )", - "Semver": "[3.0.0, )", - "System.IO.Pipelines": "[9.0.1, )" - } + "type": "Project" }, "Coder.Desktop.Vpn.Proto": { "type": "Project", diff --git a/Vpn/Vpn.csproj b/Vpn/Vpn.csproj index a26061e..76a72eb 100644 --- a/Vpn/Vpn.csproj +++ b/Vpn/Vpn.csproj @@ -3,7 +3,7 @@ Coder.Desktop.Vpn Coder.Desktop.Vpn - net8.0 + net8.0-windows enable enable true diff --git a/Vpn/packages.lock.json b/Vpn/packages.lock.json index 8e56ce8..8876fe4 100644 --- a/Vpn/packages.lock.json +++ b/Vpn/packages.lock.json @@ -1,7 +1,7 @@ { "version": 1, "dependencies": { - "net8.0": { + "net8.0-windows7.0": { "Microsoft.Extensions.Configuration": { "type": "Direct", "requested": "[9.0.1, )", From 5aa8f8282b862e293a2d5828a02f594088e54c91 Mon Sep 17 00:00:00 2001 From: Spike Curtis Date: Mon, 17 Mar 2025 17:05:11 +0400 Subject: [PATCH 3/4] Fix targets --- Tests.Vpn/Tests.Vpn.csproj | 2 +- Tests.Vpn/packages.lock.json | 56 +++++++++++++++++++++++++- Vpn.DebugClient/Vpn.DebugClient.csproj | 2 +- Vpn.DebugClient/packages.lock.json | 45 ++++++++++++++++++++- 4 files changed, 99 insertions(+), 6 deletions(-) diff --git a/Tests.Vpn/Tests.Vpn.csproj b/Tests.Vpn/Tests.Vpn.csproj index b1ff6c6..2b9e30f 100644 --- a/Tests.Vpn/Tests.Vpn.csproj +++ b/Tests.Vpn/Tests.Vpn.csproj @@ -3,7 +3,7 @@ Coder.Desktop.Tests.Vpn Coder.Desktop.Tests.Vpn - net8.0 + net8.0-windows enable enable true diff --git a/Tests.Vpn/packages.lock.json b/Tests.Vpn/packages.lock.json index 26adab4..725c743 100644 --- a/Tests.Vpn/packages.lock.json +++ b/Tests.Vpn/packages.lock.json @@ -1,7 +1,7 @@ { "version": 1, "dependencies": { - "net8.0": { + "net8.0-windows7.0": { "coverlet.collector": { "type": "Direct", "requested": "[6.0.4, )", @@ -36,11 +36,38 @@ "resolved": "4.6.0", "contentHash": "R7e1+a4vuV/YS+ItfL7f//rG+JBvVeVLX4mHzFEZo4W1qEKl8Zz27AqvQSAqo+BtIzUCo4aAJMYa56VXS4hudw==" }, + "Google.Protobuf": { + "type": "Transitive", + "resolved": "3.29.3", + "contentHash": "t7nZFFUFwigCwZ+nIXHDLweXvwIpsOXi+P7J7smPT/QjI3EKxnCzTQOhBqyEh6XEzc/pNH+bCFOOSjatrPt6Tw==" + }, "Microsoft.CodeCoverage": { "type": "Transitive", "resolved": "17.12.0", "contentHash": "4svMznBd5JM21JIG2xZKGNanAHNXplxf/kQDFfLHXQ3OnpJkayRK/TjacFjA+EYmoyuNXHo/sOETEfcYtAzIrA==" }, + "Microsoft.Extensions.Configuration": { + "type": "Transitive", + "resolved": "9.0.1", + "contentHash": "VuthqFS+ju6vT8W4wevdhEFiRi1trvQtkzWLonApfF5USVzzDcTBoY3F24WvN/tffLSrycArVfX1bThm/9xY2A==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "9.0.1", + "Microsoft.Extensions.Primitives": "9.0.1" + } + }, + "Microsoft.Extensions.Configuration.Abstractions": { + "type": "Transitive", + "resolved": "9.0.1", + "contentHash": "+4hfFIY1UjBCXFTTOd+ojlDPq6mep3h5Vq5SYE3Pjucr7dNXmq4S/6P/LoVnZFz2e/5gWp/om4svUFgznfULcA==", + "dependencies": { + "Microsoft.Extensions.Primitives": "9.0.1" + } + }, + "Microsoft.Extensions.Primitives": { + "type": "Transitive", + "resolved": "9.0.1", + "contentHash": "bHtTesA4lrSGD1ZUaMIx6frU3wyy0vYtTa/hM6gGQu5QNrydObv8T5COiGUWsisflAfmsaFOe9Xvw5NSO99z0g==" + }, "Microsoft.TestPlatform.ObjectModel": { "type": "Transitive", "resolved": "17.12.0", @@ -63,13 +90,38 @@ "resolved": "13.0.1", "contentHash": "ppPFpBcvxdsfUonNcvITKqLl3bqxWbDCZIzDWHzjpdAHRFfZe0Dw9HmA0+za13IdyrgJwpkDTDA9fHaxOrt20A==" }, + "Semver": { + "type": "Transitive", + "resolved": "3.0.0", + "contentHash": "9jZCicsVgTebqkAujRWtC9J1A5EQVlu0TVKHcgoCuv345ve5DYf4D1MjhKEnQjdRZo6x/vdv6QQrYFs7ilGzLA==", + "dependencies": { + "Microsoft.Extensions.Primitives": "5.0.1" + } + }, + "System.IO.Pipelines": { + "type": "Transitive", + "resolved": "9.0.1", + "contentHash": "uXf5o8eV/gtzDQY4lGROLFMWQvcViKcF8o4Q6KpIOjloAQXrnscQSu6gTxYJMHuNJnh7szIF9AzkaEq+zDLoEg==" + }, "System.Reflection.Metadata": { "type": "Transitive", "resolved": "1.6.0", "contentHash": "COC1aiAJjCoA5GBF+QKL2uLqEBew4JsCkQmoHKbN3TlOZKa2fKLz5CpiRQKDz0RsAOEGsVKqOD5bomsXq/4STQ==" }, "Coder.Desktop.Vpn": { - "type": "Project" + "type": "Project", + "dependencies": { + "Coder.Desktop.Vpn.Proto": "[1.0.0, )", + "Microsoft.Extensions.Configuration": "[9.0.1, )", + "Semver": "[3.0.0, )", + "System.IO.Pipelines": "[9.0.1, )" + } + }, + "Coder.Desktop.Vpn.Proto": { + "type": "Project", + "dependencies": { + "Google.Protobuf": "[3.29.3, )" + } } } } diff --git a/Vpn.DebugClient/Vpn.DebugClient.csproj b/Vpn.DebugClient/Vpn.DebugClient.csproj index bc81b6b..0eda43d 100644 --- a/Vpn.DebugClient/Vpn.DebugClient.csproj +++ b/Vpn.DebugClient/Vpn.DebugClient.csproj @@ -4,7 +4,7 @@ Coder.Desktop.Vpn.DebugClient Coder.Desktop.Vpn.DebugClient Exe - net8.0 + net8.0-windows enable enable true diff --git a/Vpn.DebugClient/packages.lock.json b/Vpn.DebugClient/packages.lock.json index 79788d7..473422b 100644 --- a/Vpn.DebugClient/packages.lock.json +++ b/Vpn.DebugClient/packages.lock.json @@ -1,14 +1,55 @@ { "version": 1, "dependencies": { - "net8.0": { + "net8.0-windows7.0": { "Google.Protobuf": { "type": "Transitive", "resolved": "3.29.3", "contentHash": "t7nZFFUFwigCwZ+nIXHDLweXvwIpsOXi+P7J7smPT/QjI3EKxnCzTQOhBqyEh6XEzc/pNH+bCFOOSjatrPt6Tw==" }, + "Microsoft.Extensions.Configuration": { + "type": "Transitive", + "resolved": "9.0.1", + "contentHash": "VuthqFS+ju6vT8W4wevdhEFiRi1trvQtkzWLonApfF5USVzzDcTBoY3F24WvN/tffLSrycArVfX1bThm/9xY2A==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "9.0.1", + "Microsoft.Extensions.Primitives": "9.0.1" + } + }, + "Microsoft.Extensions.Configuration.Abstractions": { + "type": "Transitive", + "resolved": "9.0.1", + "contentHash": "+4hfFIY1UjBCXFTTOd+ojlDPq6mep3h5Vq5SYE3Pjucr7dNXmq4S/6P/LoVnZFz2e/5gWp/om4svUFgznfULcA==", + "dependencies": { + "Microsoft.Extensions.Primitives": "9.0.1" + } + }, + "Microsoft.Extensions.Primitives": { + "type": "Transitive", + "resolved": "9.0.1", + "contentHash": "bHtTesA4lrSGD1ZUaMIx6frU3wyy0vYtTa/hM6gGQu5QNrydObv8T5COiGUWsisflAfmsaFOe9Xvw5NSO99z0g==" + }, + "Semver": { + "type": "Transitive", + "resolved": "3.0.0", + "contentHash": "9jZCicsVgTebqkAujRWtC9J1A5EQVlu0TVKHcgoCuv345ve5DYf4D1MjhKEnQjdRZo6x/vdv6QQrYFs7ilGzLA==", + "dependencies": { + "Microsoft.Extensions.Primitives": "5.0.1" + } + }, + "System.IO.Pipelines": { + "type": "Transitive", + "resolved": "9.0.1", + "contentHash": "uXf5o8eV/gtzDQY4lGROLFMWQvcViKcF8o4Q6KpIOjloAQXrnscQSu6gTxYJMHuNJnh7szIF9AzkaEq+zDLoEg==" + }, "Coder.Desktop.Vpn": { - "type": "Project" + "type": "Project", + "dependencies": { + "Coder.Desktop.Vpn.Proto": "[1.0.0, )", + "Microsoft.Extensions.Configuration": "[9.0.1, )", + "Semver": "[3.0.0, )", + "System.IO.Pipelines": "[9.0.1, )" + } }, "Coder.Desktop.Vpn.Proto": { "type": "Project", From b554bf58b99d792da32bc53f53ba4b207b475645 Mon Sep 17 00:00:00 2001 From: Spike Curtis Date: Mon, 17 Mar 2025 17:08:47 +0400 Subject: [PATCH 4/4] Registry key to AppMutagenController --- App/App.xaml.cs | 4 ++-- App/Services/MutagenController.cs | 2 +- Installer/Program.cs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/App/App.xaml.cs b/App/App.xaml.cs index 9d4cf20..e1c5cb4 100644 --- a/App/App.xaml.cs +++ b/App/App.xaml.cs @@ -22,9 +22,9 @@ public partial class App : Application private bool _handleWindowClosed = true; #if !DEBUG - private const string MutagenControllerConfigSection = "MutagenController"; + private const string MutagenControllerConfigSection = "AppMutagenController"; #else - private const string MutagenControllerConfigSection = "DebugMutagenController"; + private const string MutagenControllerConfigSection = "DebugAppMutagenController"; #endif public App() diff --git a/App/Services/MutagenController.cs b/App/Services/MutagenController.cs index 532f08b..7f48426 100644 --- a/App/Services/MutagenController.cs +++ b/App/Services/MutagenController.cs @@ -46,7 +46,7 @@ public interface ISyncSessionController } // These values are the config option names used in the registry. Any option -// here can be configured with `(Debug)?MutagenController:OptionName` in the registry. +// here can be configured with `(Debug)?AppMutagenController:OptionName` in the registry. // // They should not be changed without backwards compatibility considerations. // If changed here, they should also be changed in the installer. diff --git a/Installer/Program.cs b/Installer/Program.cs index 02b7dab..7945f5b 100644 --- a/Installer/Program.cs +++ b/Installer/Program.cs @@ -262,8 +262,8 @@ private static int BuildMsiPackage(MsiOptions opts) @"[INSTALLFOLDER]coder-desktop-service.log"), new RegValue(RegistryHive, RegistryKey, "Manager:TunnelBinarySignatureSigner", "Coder Technologies Inc."), new RegValue(RegistryHive, RegistryKey, "Manager:TunnelBinaryAllowVersionMismatch", "false"), - // Add registry values that are consumed by the MutagenController. See App/Services/MutagenController.cs - new RegValue(RegistryHive, RegistryKey, "MutagenController:MutagenExecutablePath", + // Add registry values that are consumed by the App MutagenController. See App/Services/MutagenController.cs + new RegValue(RegistryHive, RegistryKey, "AppMutagenController:MutagenExecutablePath", @"[INSTALLFOLDER]mutagen.exe") );