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") ); pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy