Skip to content

Commit 41cf916

Browse files
committed
config defaulting, also include VPN service
1 parent 37ee7e4 commit 41cf916

File tree

7 files changed

+129
-77
lines changed

7 files changed

+129
-77
lines changed

App/App.xaml.cs

+30-20
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
using System;
22
using System.Diagnostics;
33
using System.IO;
4-
using System.Linq;
54
using System.Threading;
65
using System.Threading.Tasks;
76
using Coder.Desktop.App.Models;
@@ -19,6 +18,7 @@
1918
using Windows.ApplicationModel.Activation;
2019
using Microsoft.Extensions.Logging;
2120
using Serilog;
21+
using System.Collections.Generic;
2222

2323
namespace Coder.Desktop.App;
2424

@@ -29,9 +29,6 @@ public partial class App : Application
2929
private bool _handleWindowClosed = true;
3030
private const string MutagenControllerConfigSection = "MutagenController";
3131

32-
private const string logTemplate =
33-
"{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {SourceContext} - {Message:lj}{NewLine}{Exception}";
34-
3532
#if !DEBUG
3633
private const string ConfigSubKey = @"SOFTWARE\Coder Desktop\App";
3734
private const string logFilename = "app.log";
@@ -45,31 +42,22 @@ public partial class App : Application
4542
public App()
4643
{
4744
var builder = Host.CreateApplicationBuilder();
45+
var configBuilder = builder.Configuration as IConfigurationBuilder;
4846

49-
(builder.Configuration as IConfigurationBuilder).Add(
47+
// Add config in increasing order of precedence: first builtin defaults, then HKLM, finally HKCU
48+
// so that the user's settings in the registry take precedence.
49+
AddDefaultConfig(configBuilder);
50+
configBuilder.Add(
5051
new RegistryConfigurationSource(Registry.LocalMachine, ConfigSubKey));
52+
configBuilder.Add(
53+
new RegistryConfigurationSource(Registry.CurrentUser, ConfigSubKey));
5154

5255
var services = builder.Services;
5356

5457
// Logging
5558
builder.Services.AddSerilog((_, loggerConfig) =>
5659
{
5760
loggerConfig.ReadFrom.Configuration(builder.Configuration);
58-
var sinkConfig = builder.Configuration.GetSection("Serilog").GetSection("WriteTo");
59-
if (!sinkConfig.GetChildren().Any())
60-
{
61-
// no log sink defined in the registry, so we'll add one here.
62-
// We can't generally define these in the registry because we don't
63-
// know, a priori, what user will execute Coder Desktop, and therefore
64-
// what directories are writable by them. But, it's nice to be able to
65-
// directly customize Serilog via the registry if you know what you are
66-
// doing.
67-
var logPath = Path.Combine(
68-
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
69-
"CoderDesktop",
70-
logFilename);
71-
loggerConfig.WriteTo.File(logPath, outputTemplate: logTemplate, rollingInterval: RollingInterval.Day);
72-
}
7361
});
7462

7563
services.AddSingleton<ICredentialManager, CredentialManager>();
@@ -108,6 +96,7 @@ public App()
10896

10997
public async Task ExitApplication()
11098
{
99+
_logger.LogDebug("exiting app");
111100
_handleWindowClosed = false;
112101
Exit();
113102
var syncController = _services.GetRequiredService<ISyncSessionController>();
@@ -169,6 +158,7 @@ protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs ar
169158
Debugger.Break();
170159
#endif
171160
}
161+
172162
syncSessionCts.Dispose();
173163
}, CancellationToken.None);
174164

@@ -208,4 +198,24 @@ public void HandleURIActivation(Uri uri)
208198
// don't log the query string as that's where we include some sensitive information like passwords
209199
_logger.LogInformation("handling URI activation for {path}", uri.AbsolutePath);
210200
}
201+
202+
private static void AddDefaultConfig(IConfigurationBuilder builder)
203+
{
204+
var logPath = Path.Combine(
205+
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
206+
"CoderDesktop",
207+
logFilename);
208+
builder.AddInMemoryCollection(new Dictionary<string, string?>
209+
{
210+
[MutagenControllerConfigSection + ":MutagenExecutablePath"] = @"C:\mutagen.exe",
211+
["Serilog:Using:0"] = "Serilog.Sinks.File",
212+
["Serilog:MinimumLevel"] = "Information",
213+
["Serilog:Enrich:0"] = "FromLogContext",
214+
["Serilog:WriteTo:0:Name"] = "File",
215+
["Serilog:WriteTo:0:Args:path"] = logPath,
216+
["Serilog:WriteTo:0:Args:outputTemplate"] =
217+
"{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {SourceContext} - {Message:lj}{NewLine}{Exception}",
218+
["Serilog:WriteTo:0:Args:rollingInterval"] = "Day",
219+
});
220+
}
211221
}

Installer/Program.cs

+15-31
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,8 @@ public class BootstrapperOptions : SharedOptions
130130
if (!SystemFile.Exists(MsiPath))
131131
throw new ArgumentException($"MSI package not found at '{MsiPath}'", nameof(MsiPath));
132132
if (!SystemFile.Exists(WindowsAppSdkPath))
133-
throw new ArgumentException($"Windows App Sdk package not found at '{WindowsAppSdkPath}'", nameof(WindowsAppSdkPath));
133+
throw new ArgumentException($"Windows App Sdk package not found at '{WindowsAppSdkPath}'",
134+
nameof(WindowsAppSdkPath));
134135
}
135136
}
136137

@@ -141,6 +142,7 @@ public class Program
141142
private const string HelpUrl = "https://coder.com/docs";
142143
private const string RegistryKey = @"SOFTWARE\Coder Desktop";
143144
private const string AppConfigRegistryKey = RegistryKey + @"\App";
145+
private const string VpnServiceConfigRegistryKey = RegistryKey + @"\VpnService";
144146

145147
private const string DotNetCheckName = "DOTNET_RUNTIME_CHECK";
146148
private const RollForward DotNetCheckRollForward = RollForward.minor;
@@ -259,42 +261,24 @@ private static int BuildMsiPackage(MsiOptions opts)
259261
project.AddDir(programFiles64Folder);
260262

261263

262-
263264
project.AddRegValues(
264265
// Add registry values that are consumed by the manager. Note that these
265-
// should not be changed. See Vpn.Service/Program.cs and
266+
// should not be changed. See Vpn.Service/Program.cs (AddDefaultConfig) and
266267
// Vpn.Service/ManagerConfig.cs for more details.
267-
new RegValue(RegistryHive, RegistryKey, "Manager:ServiceRpcPipeName", "Coder.Desktop.Vpn"),
268-
new RegValue(RegistryHive, RegistryKey, "Manager:TunnelBinaryPath",
268+
new RegValue(RegistryHive, VpnServiceConfigRegistryKey, "Manager:ServiceRpcPipeName", "Coder.Desktop.Vpn"),
269+
new RegValue(RegistryHive, VpnServiceConfigRegistryKey, "Manager:TunnelBinaryPath",
269270
$"[INSTALLFOLDER]{opts.VpnDir}\\coder-vpn.exe"),
270-
new RegValue(RegistryHive, RegistryKey, "Manager:LogFileLocation",
271+
new RegValue(RegistryHive, VpnServiceConfigRegistryKey, "Manager:TunnelBinarySignatureSigner",
272+
"Coder Technologies Inc."),
273+
new RegValue(RegistryHive, VpnServiceConfigRegistryKey, "Manager:TunnelBinaryAllowVersionMismatch",
274+
"false"),
275+
new RegValue(RegistryHive, VpnServiceConfigRegistryKey, "Serilog:WriteTo:0:Args:path",
271276
@"[INSTALLFOLDER]coder-desktop-service.log"),
272-
new RegValue(RegistryHive, RegistryKey, "Manager:TunnelBinarySignatureSigner", "Coder Technologies Inc."),
273-
new RegValue(RegistryHive, RegistryKey, "Manager:TunnelBinaryAllowVersionMismatch", "false")
274-
);
275277

276-
// rather than adding individual reg values, it's much clearer to write the whole config as JSON then
277-
// programmatically spit out the registry values.
278-
var appConfigJson = """
279-
{
280-
"MutagenController": {
281-
"MutagenExecutablePath": "[INSTALLFOLDER]vpn\mutagen.exe"
282-
},
283-
"Serilog": {
284-
"Using": ["Serilog.Sinks.File"],
285-
"MinimumLevel": "Information",
286-
"Enrich": [ "FromLogContext" ],
287-
}
288-
}
289-
""";
290-
var appConfig =
291-
new ConfigurationBuilder().AddJsonStream(new MemoryStream(Encoding.UTF8.GetBytes(appConfigJson))).Build();
292-
293-
foreach (var kvp in appConfig.AsEnumerable())
294-
{
295-
if (kvp.Value is null) continue;
296-
project.AddRegValue(new RegValue(RegistryHive, AppConfigRegistryKey, kvp.Key, kvp.Value));
297-
}
278+
// Add registry values that are consumed by the App MutagenController. See App/Services/MutagenController.cs
279+
new RegValue(RegistryHive, AppConfigRegistryKey, "MutagenController:MutagenExecutablePath",
280+
@"[INSTALLFOLDER]vpn\mutagen.exe")
281+
);
298282

299283
// Note: most of this control panel info will not be visible as this
300284
// package is usually hidden in favor of the bootstrapper showing

Tests.Vpn.Service/packages.lock.json

+20
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,15 @@
137137
"resolved": "9.0.4",
138138
"contentHash": "UI0TQPVkS78bFdjkTodmkH0Fe8lXv9LnhGFKgKrsgUJ5a5FVdFRcgjIkBVLbGgdRhxWirxH/8IXUtEyYJx6GQg=="
139139
},
140+
"Microsoft.Extensions.DependencyModel": {
141+
"type": "Transitive",
142+
"resolved": "9.0.0",
143+
"contentHash": "saxr2XzwgDU77LaQfYFXmddEDRUKHF4DaGMZkNB3qjdVSZlax3//dGJagJkKrGMIPNZs2jVFXITyCCR6UHJNdA==",
144+
"dependencies": {
145+
"System.Text.Encodings.Web": "9.0.0",
146+
"System.Text.Json": "9.0.0"
147+
}
148+
},
140149
"Microsoft.Extensions.Diagnostics": {
141150
"type": "Transitive",
142151
"resolved": "9.0.4",
@@ -409,6 +418,16 @@
409418
"Serilog": "4.2.0"
410419
}
411420
},
421+
"Serilog.Settings.Configuration": {
422+
"type": "Transitive",
423+
"resolved": "9.0.0",
424+
"contentHash": "4/Et4Cqwa+F88l5SeFeNZ4c4Z6dEAIKbu3MaQb2Zz9F/g27T5a3wvfMcmCOaAiACjfUb4A6wrlTVfyYUZk3RRQ==",
425+
"dependencies": {
426+
"Microsoft.Extensions.Configuration.Binder": "9.0.0",
427+
"Microsoft.Extensions.DependencyModel": "9.0.0",
428+
"Serilog": "4.2.0"
429+
}
430+
},
412431
"Serilog.Sinks.Console": {
413432
"type": "Transitive",
414433
"resolved": "6.0.0",
@@ -496,6 +515,7 @@
496515
"Microsoft.Security.Extensions": "[1.3.0, )",
497516
"Semver": "[3.0.0, )",
498517
"Serilog.Extensions.Hosting": "[9.0.0, )",
518+
"Serilog.Settings.Configuration": "[9.0.0, )",
499519
"Serilog.Sinks.Console": "[6.0.0, )",
500520
"Serilog.Sinks.File": "[6.0.0, )"
501521
}

Vpn.Service/ManagerConfig.cs

-2
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@ public class ManagerConfig
1515

1616
[Required] public string TunnelBinaryPath { get; set; } = @"C:\coder-vpn.exe";
1717

18-
[Required] public string LogFileLocation { get; set; } = @"C:\coder-desktop-service.log";
19-
2018
// If empty, signatures will not be verified.
2119
[Required] public string TunnelBinarySignatureSigner { get; set; } = "Coder Technologies Inc.";
2220

Vpn.Service/Program.cs

+43-24
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using Microsoft.Extensions.Hosting;
44
using Microsoft.Win32;
55
using Serilog;
6+
using ILogger = Serilog.ILogger;
67

78
namespace Coder.Desktop.Vpn.Service;
89

@@ -14,29 +15,22 @@ public static class Program
1415
// installer.
1516
#if !DEBUG
1617
private const string ServiceName = "Coder Desktop";
17-
private const string ManagerConfigSection = "Manager";
18+
private const string ConfigSubKey = @"SOFTWARE\Coder Desktop\VpnService";
1819
#else
1920
// This value matches Create-Service.ps1.
2021
private const string ServiceName = "Coder Desktop (Debug)";
21-
private const string ManagerConfigSection = "DebugManager";
22+
private const string ConfigSubKey = @"SOFTWARE\Coder Desktop\DebugVpnService";
2223
#endif
2324

24-
private const string ConsoleOutputTemplate =
25-
"[{Timestamp:HH:mm:ss} {Level:u3}] {SourceContext} - {Message:lj}{NewLine}{Exception}";
26-
27-
private const string FileOutputTemplate =
28-
"{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {SourceContext} - {Message:lj}{NewLine}{Exception}";
25+
private const string ManagerConfigSection = "Manager";
2926

3027
private static ILogger MainLogger => Log.ForContext("SourceContext", "Coder.Desktop.Vpn.Service.Program");
3128

32-
private static LoggerConfiguration BaseLogConfig => new LoggerConfiguration()
33-
.Enrich.FromLogContext()
34-
.MinimumLevel.Debug()
35-
.WriteTo.Console(outputTemplate: ConsoleOutputTemplate);
36-
3729
public static async Task<int> Main(string[] args)
3830
{
39-
Log.Logger = BaseLogConfig.CreateLogger();
31+
// This logger will only be used until we load our full logging configuration and replace it.
32+
Log.Logger = new LoggerConfiguration().MinimumLevel.Debug().WriteTo.Console()
33+
.CreateLogger();
4034
MainLogger.Information("Application is starting");
4135
try
4236
{
@@ -58,27 +52,26 @@ public static async Task<int> Main(string[] args)
5852
private static async Task BuildAndRun(string[] args)
5953
{
6054
var builder = Host.CreateApplicationBuilder(args);
55+
var configBuilder = builder.Configuration as IConfigurationBuilder;
6156

6257
// Configuration sources
6358
builder.Configuration.Sources.Clear();
64-
(builder.Configuration as IConfigurationBuilder).Add(
65-
new RegistryConfigurationSource(Registry.LocalMachine, @"SOFTWARE\Coder Desktop"));
59+
AddDefaultConfig(configBuilder);
60+
configBuilder.Add(
61+
new RegistryConfigurationSource(Registry.LocalMachine, ConfigSubKey));
6662
builder.Configuration.AddEnvironmentVariables("CODER_MANAGER_");
6763
builder.Configuration.AddCommandLine(args);
6864

6965
// Options types (these get registered as IOptions<T> singletons)
7066
builder.Services.AddOptions<ManagerConfig>()
7167
.Bind(builder.Configuration.GetSection(ManagerConfigSection))
72-
.ValidateDataAnnotations()
73-
.PostConfigure(config =>
74-
{
75-
Log.Logger = BaseLogConfig
76-
.WriteTo.File(config.LogFileLocation, outputTemplate: FileOutputTemplate)
77-
.CreateLogger();
78-
});
68+
.ValidateDataAnnotations();
7969

8070
// Logging
81-
builder.Services.AddSerilog();
71+
builder.Services.AddSerilog((_, loggerConfig) =>
72+
{
73+
loggerConfig.ReadFrom.Configuration(builder.Configuration);
74+
});
8275

8376
// Singletons
8477
builder.Services.AddSingleton<IDownloader, Downloader>();
@@ -101,6 +94,32 @@ private static async Task BuildAndRun(string[] args)
10194
builder.Services.AddHostedService<ManagerService>();
10295
builder.Services.AddHostedService<ManagerRpcService>();
10396

104-
await builder.Build().RunAsync();
97+
var host = builder.Build();
98+
Log.Logger = (ILogger)host.Services.GetService(typeof(ILogger))!;
99+
MainLogger.Information("Application is starting");
100+
101+
await host.RunAsync();
102+
}
103+
104+
private static void AddDefaultConfig(IConfigurationBuilder builder)
105+
{
106+
builder.AddInMemoryCollection(new Dictionary<string, string?>
107+
{
108+
["Serilog:Using:0"] = "Serilog.Sinks.File",
109+
["Serilog:Using:1"] = "Serilog.Sinks.Console",
110+
111+
["Serilog:MinimumLevel"] = "Information",
112+
["Serilog:Enrich:0"] = "FromLogContext",
113+
114+
["Serilog:WriteTo:0:Name"] = "File",
115+
["Serilog:WriteTo:0:Args:path"] = @"C:\coder-desktop-service.log",
116+
["Serilog:WriteTo:0:Args:outputTemplate"] =
117+
"{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {SourceContext} - {Message:lj}{NewLine}{Exception}",
118+
["Serilog:WriteTo:0:Args:rollingInterval"] = "Day",
119+
120+
["Serilog:WriteTo:1:Name"] = "Console",
121+
["Serilog:WriteTo:1:Args:outputTemplate"] =
122+
"[{Timestamp:HH:mm:ss} {Level:u3}] {SourceContext} - {Message:lj}{NewLine}{Exception}",
123+
});
105124
}
106125
}

Vpn.Service/Vpn.Service.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
<PackageReference Include="Microsoft.Security.Extensions" Version="1.3.0" />
3232
<PackageReference Include="Semver" Version="3.0.0" />
3333
<PackageReference Include="Serilog.Extensions.Hosting" Version="9.0.0" />
34+
<PackageReference Include="Serilog.Settings.Configuration" Version="9.0.0" />
3435
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
3536
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
3637
</ItemGroup>

Vpn.Service/packages.lock.json

+20
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,17 @@
8181
"Serilog.Extensions.Logging": "9.0.0"
8282
}
8383
},
84+
"Serilog.Settings.Configuration": {
85+
"type": "Direct",
86+
"requested": "[9.0.0, )",
87+
"resolved": "9.0.0",
88+
"contentHash": "4/Et4Cqwa+F88l5SeFeNZ4c4Z6dEAIKbu3MaQb2Zz9F/g27T5a3wvfMcmCOaAiACjfUb4A6wrlTVfyYUZk3RRQ==",
89+
"dependencies": {
90+
"Microsoft.Extensions.Configuration.Binder": "9.0.0",
91+
"Microsoft.Extensions.DependencyModel": "9.0.0",
92+
"Serilog": "4.2.0"
93+
}
94+
},
8495
"Serilog.Sinks.Console": {
8596
"type": "Direct",
8697
"requested": "[6.0.0, )",
@@ -195,6 +206,15 @@
195206
"resolved": "9.0.4",
196207
"contentHash": "UI0TQPVkS78bFdjkTodmkH0Fe8lXv9LnhGFKgKrsgUJ5a5FVdFRcgjIkBVLbGgdRhxWirxH/8IXUtEyYJx6GQg=="
197208
},
209+
"Microsoft.Extensions.DependencyModel": {
210+
"type": "Transitive",
211+
"resolved": "9.0.0",
212+
"contentHash": "saxr2XzwgDU77LaQfYFXmddEDRUKHF4DaGMZkNB3qjdVSZlax3//dGJagJkKrGMIPNZs2jVFXITyCCR6UHJNdA==",
213+
"dependencies": {
214+
"System.Text.Encodings.Web": "9.0.0",
215+
"System.Text.Json": "9.0.0"
216+
}
217+
},
198218
"Microsoft.Extensions.Diagnostics": {
199219
"type": "Transitive",
200220
"resolved": "9.0.4",

0 commit comments

Comments
 (0)