From 11c57562e8ebdeaec2b871a296c3d5bfe45cafe7 Mon Sep 17 00:00:00 2001
From: Dean Sheather <dean@deansheather.com>
Date: Tue, 28 Jan 2025 14:33:33 +1100
Subject: [PATCH 1/7] chore: add GitHub actions

---
 .github/workflows/ci.yaml | 59 +++++++++++++++++++++++++++++++++++++++
 .gitignore                |  2 ++
 Vpn.Service/Downloader.cs |  6 ++--
 3 files changed, 65 insertions(+), 2 deletions(-)
 create mode 100644 .github/workflows/ci.yaml

diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
new file mode 100644
index 0000000..c1297b3
--- /dev/null
+++ b/.github/workflows/ci.yaml
@@ -0,0 +1,59 @@
+name: ci
+
+on:
+  push:
+    branches:
+      - main
+  pull_request:
+    branches:
+      - main
+
+jobs:
+  fmt:
+    runs-on: windows-latest
+    steps:
+      - uses: actions/checkout@v4
+      - name: Setup dotnet
+        uses: actions/setup-dotnet@v4
+        with:
+          dotnet-version: 8.0.x
+          cache: true
+      - name: Install dotnet format
+        run: dotnet tool install -g dotnet-format
+      - name: Run dotnet format
+        run: dotnet format --verify-no-changes .\Coder.Desktop.sln
+
+  test:
+    runs-on: windows-latest
+    steps:
+      - uses: actions/checkout@v4
+      - name: Setup dotnet
+        uses: actions/setup-dotnet@v4
+        with:
+          dotnet-version: 8.0.x
+          cache: true
+      - name: Restore dependencies
+        run: dotnet restore .\Coder.Desktop.sln
+      - name: Test
+        run: dotnet test .\Coder.Desktop.sln
+
+  build:
+    runs-on: windows-latest
+    steps:
+      - uses: actions/checkout@v4
+      - name: Setup dotnet
+        uses: actions/setup-dotnet@v4
+        with:
+          dotnet-version: 8.0.x
+          cache: true
+      - name: Restore dependencies
+        run: dotnet restore .\Coder.Desktop.sln
+      - name: Build
+        run: dotnet build .\Coder.Desktop.sln
+      - name: Publish
+        run: dotnet publish .\Coder.Desktop.sln -c Release -o .\publish
+      - name: Upload artifact
+        uses: actions/upload-artifact@v4
+        with:
+          name: publish
+          path: .\publish\
diff --git a/.gitignore b/.gitignore
index 79d0686..e191fc6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -401,3 +401,5 @@ FodyWeavers.xsd
 .idea/**/workspace.xml
 .idea/**/usage.statistics.xml
 .idea/**/shelf
+
+publish
diff --git a/Vpn.Service/Downloader.cs b/Vpn.Service/Downloader.cs
index ea4c587..a0a1359 100644
--- a/Vpn.Service/Downloader.cs
+++ b/Vpn.Service/Downloader.cs
@@ -40,15 +40,16 @@ public Task ValidateAsync(string path, CancellationToken ct = default)
 /// </summary>
 public class AuthenticodeDownloadValidator : IDownloadValidator
 {
+    public static readonly AuthenticodeDownloadValidator Coder = new("Coder Technologies Inc.");
+
     private readonly string _expectedName;
 
+    // ReSharper disable once ConvertToPrimaryConstructor
     public AuthenticodeDownloadValidator(string expectedName)
     {
         _expectedName = expectedName;
     }
 
-    public static AuthenticodeDownloadValidator Coder => new("Coder Technologies Inc.");
-
     public async Task ValidateAsync(string path, CancellationToken ct = default)
     {
         FileSignatureInfo fileSigInfo;
@@ -79,6 +80,7 @@ public class AssemblyVersionDownloadValidator : IDownloadValidator
 {
     private readonly string _expectedAssemblyVersion;
 
+    // ReSharper disable once ConvertToPrimaryConstructor
     public AssemblyVersionDownloadValidator(string expectedAssemblyVersion)
     {
         _expectedAssemblyVersion = expectedAssemblyVersion;

From 01d40a80c3eabb9f7898865ddb6678e97a85c734 Mon Sep 17 00:00:00 2001
From: Dean Sheather <dean@deansheather.com>
Date: Tue, 28 Jan 2025 15:10:30 +1100
Subject: [PATCH 2/7] fixup! chore: add GitHub actions

---
 .github/workflows/ci.yaml                  |  25 +-
 App/App.csproj                             |   1 +
 App/packages.lock.json                     | 160 +++++++
 CoderSdk/CoderSdk.csproj                   |   3 +-
 CoderSdk/packages.lock.json                |   6 +
 Package/Package.wapproj                    |   2 +-
 Tests.Vpn.Proto/Tests.Vpn.Proto.csproj     |  15 +-
 Tests.Vpn.Proto/packages.lock.json         |  84 ++++
 Tests.Vpn.Service/Tests.Vpn.Service.csproj |  15 +-
 Tests.Vpn.Service/packages.lock.json       | 458 +++++++++++++++++++++
 Tests.Vpn/Tests.Vpn.csproj                 |  15 +-
 Tests.Vpn/packages.lock.json               |  96 +++++
 Vpn.Proto/Vpn.Proto.csproj                 |   7 +-
 Vpn.Proto/packages.lock.json               |  19 +
 Vpn.Service/Vpn.Service.csproj             |  15 +-
 Vpn.Service/packages.lock.json             | 385 +++++++++++++++++
 Vpn/Vpn.csproj                             |   5 +-
 Vpn/packages.lock.json                     |  24 ++
 18 files changed, 1288 insertions(+), 47 deletions(-)
 create mode 100644 App/packages.lock.json
 create mode 100644 CoderSdk/packages.lock.json
 create mode 100644 Tests.Vpn.Proto/packages.lock.json
 create mode 100644 Tests.Vpn.Service/packages.lock.json
 create mode 100644 Tests.Vpn/packages.lock.json
 create mode 100644 Vpn.Proto/packages.lock.json
 create mode 100644 Vpn.Service/packages.lock.json
 create mode 100644 Vpn/packages.lock.json

diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index c1297b3..f3c4dc5 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -18,10 +18,13 @@ jobs:
         with:
           dotnet-version: 8.0.x
           cache: true
-      - name: Install dotnet format
+          cache-dependency-path: */packages.lock.json
+      - name: Install dotnet format tool
         run: dotnet tool install -g dotnet-format
-      - name: Run dotnet format
-        run: dotnet format --verify-no-changes .\Coder.Desktop.sln
+      - name: dotnet restore
+        run: dotnet restore --locked-mode .\Coder.Desktop.sln
+      - name: dotnet format
+        run: dotnet format --verify-no-changes --no-restore .\Coder.Desktop.sln
 
   test:
     runs-on: windows-latest
@@ -32,10 +35,10 @@ jobs:
         with:
           dotnet-version: 8.0.x
           cache: true
-      - name: Restore dependencies
-        run: dotnet restore .\Coder.Desktop.sln
-      - name: Test
-        run: dotnet test .\Coder.Desktop.sln
+      - name: dotnet restore
+        run: dotnet restore --locked-mode .\Coder.Desktop.sln
+      - name: dotnet test
+        run: dotnet test --no-restore .\Coder.Desktop.sln
 
   build:
     runs-on: windows-latest
@@ -46,11 +49,11 @@ jobs:
         with:
           dotnet-version: 8.0.x
           cache: true
-      - name: Restore dependencies
-        run: dotnet restore .\Coder.Desktop.sln
-      - name: Build
+      - name: dotnet restore
+        run: dotnet restore --locked-mode .\Coder.Desktop.sln
+      - name: dotnet build
         run: dotnet build .\Coder.Desktop.sln
-      - name: Publish
+      - name: dotnet publish
         run: dotnet publish .\Coder.Desktop.sln -c Release -o .\publish
       - name: Upload artifact
         uses: actions/upload-artifact@v4
diff --git a/App/App.csproj b/App/App.csproj
index 7a9ec8f..ca44062 100644
--- a/App/App.csproj
+++ b/App/App.csproj
@@ -9,6 +9,7 @@
         <RuntimeIdentifiers>win-x86;win-x64;win-arm64</RuntimeIdentifiers>
         <UseWinUI>true</UseWinUI>
         <Nullable>enable</Nullable>
+        <RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
     </PropertyGroup>
 
     <ItemGroup>
diff --git a/App/packages.lock.json b/App/packages.lock.json
new file mode 100644
index 0000000..14115ab
--- /dev/null
+++ b/App/packages.lock.json
@@ -0,0 +1,160 @@
+{
+  "version": 1,
+  "dependencies": {
+    "net8.0-windows10.0.19041": {
+      "CommunityToolkit.Mvvm": {
+        "type": "Direct",
+        "requested": "[8.4.0, )",
+        "resolved": "8.4.0",
+        "contentHash": "tqVU8yc/ADO9oiTRyTnwhFN68hCwvkliMierptWOudIAvWY1mWCh5VFh+guwHJmpMwfg0J0rY+yyd5Oy7ty9Uw=="
+      },
+      "DependencyPropertyGenerator": {
+        "type": "Direct",
+        "requested": "[1.5.0, )",
+        "resolved": "1.5.0",
+        "contentHash": "y9XF3i+U6nnzuVvmwBw7zW+1ztOXT8lMu084Ir4nNeLGVd1308qR2nCR6Yl06FXfjYMKU3+bknZ/otqkz6VEUQ=="
+      },
+      "H.NotifyIcon.WinUI": {
+        "type": "Direct",
+        "requested": "[2.2.0, )",
+        "resolved": "2.2.0",
+        "contentHash": "iWaNX3qbcOWisPygW091RFzY1tl//Nm2HCIzBxFDoz+cpRhdInuV/xelqQ8WQTM0z5PamA9+FgGlSYygzu18Gw==",
+        "dependencies": {
+          "H.NotifyIcon": "2.2.0",
+          "Microsoft.WindowsAppSDK": "1.6.241114003",
+          "System.Collections.Immutable": "9.0.0",
+          "System.Reflection.Metadata": "9.0.0"
+        }
+      },
+      "Microsoft.Windows.SDK.BuildTools": {
+        "type": "Direct",
+        "requested": "[10.0.26100.1742, )",
+        "resolved": "10.0.26100.1742",
+        "contentHash": "ypcHjr4KEi6xQhgClnbXoANHcyyX/QsC4Rky4igs6M4GiDa+weegPo8JuV/VMxqrZCV4zlqDsp2krgkN7ReAAg=="
+      },
+      "Microsoft.WindowsAppSDK": {
+        "type": "Direct",
+        "requested": "[1.6.250108002, )",
+        "resolved": "1.6.250108002",
+        "contentHash": "dOJ3oFeHnehVBAR98lR6YfX0JgDJkrehLqwZQqQE/iZgyye1c1TZu9oj3MBbgWP9xFApWUrb0G49KKnQWEZLtA==",
+        "dependencies": {
+          "Microsoft.Web.WebView2": "1.0.2651.64",
+          "Microsoft.Windows.SDK.BuildTools": "10.0.22621.756"
+        }
+      },
+      "H.GeneratedIcons.System.Drawing": {
+        "type": "Transitive",
+        "resolved": "2.2.0",
+        "contentHash": "KJ/hQZTtW3w8Rw8ktogAwY9AcVzpUldVprTD3eyQ1XyZINLHBQqgtu0ZArmuYlp+lUR0lsqAmaARXZIOFBWmQg==",
+        "dependencies": {
+          "System.Drawing.Common": "9.0.0"
+        }
+      },
+      "H.NotifyIcon": {
+        "type": "Transitive",
+        "resolved": "2.2.0",
+        "contentHash": "x3rk9dLAXpMrfngnC7oHmYF4sULEpEdD9hLMIt0W41MZ8WYeCY63SeyJX1TKcvJ7BI24JciLxhXOuWrSRCEiQA==",
+        "dependencies": {
+          "H.GeneratedIcons.System.Drawing": "2.2.0"
+        }
+      },
+      "Microsoft.Web.WebView2": {
+        "type": "Transitive",
+        "resolved": "1.0.2651.64",
+        "contentHash": "f5sc/vcAoTCTEW7Nqzp4galAuTRguZViw8ksn+Nx2uskEBPm0/ubzy6gVjvXS/P96jLS89C8T9I0hPc417xpNg=="
+      },
+      "Microsoft.Win32.SystemEvents": {
+        "type": "Transitive",
+        "resolved": "9.0.0",
+        "contentHash": "z8FfGIaoeALdD+KF44A2uP8PZIQQtDGiXsOLuN8nohbKhkyKt7zGaZb+fKiCxTuBqG22Q7myIAioSWaIcOOrOw=="
+      },
+      "System.Collections.Immutable": {
+        "type": "Transitive",
+        "resolved": "9.0.0",
+        "contentHash": "QhkXUl2gNrQtvPmtBTQHb0YsUrDiDQ2QS09YbtTTiSjGcf7NBqtYbrG/BE06zcBPCKEwQGzIv13IVdXNOSub2w=="
+      },
+      "System.Drawing.Common": {
+        "type": "Transitive",
+        "resolved": "9.0.0",
+        "contentHash": "uoozjI3+dlgKh2onFJcz8aNLh6TRCPlLSh8Dbuljc8CdvqXrxHOVysJlrHvlsOCqceqGBR1wrMPxlnzzhynktw==",
+        "dependencies": {
+          "Microsoft.Win32.SystemEvents": "9.0.0"
+        }
+      },
+      "System.Reflection.Metadata": {
+        "type": "Transitive",
+        "resolved": "9.0.0",
+        "contentHash": "ANiqLu3DxW9kol/hMmTWbt3414t9ftdIuiIU7j80okq2YzAueo120M442xk1kDJWtmZTqWQn7wHDvMRipVOEOQ==",
+        "dependencies": {
+          "System.Collections.Immutable": "9.0.0"
+        }
+      }
+    },
+    "net8.0-windows10.0.19041/win-arm64": {
+      "Microsoft.WindowsAppSDK": {
+        "type": "Direct",
+        "requested": "[1.6.250108002, )",
+        "resolved": "1.6.250108002",
+        "contentHash": "dOJ3oFeHnehVBAR98lR6YfX0JgDJkrehLqwZQqQE/iZgyye1c1TZu9oj3MBbgWP9xFApWUrb0G49KKnQWEZLtA==",
+        "dependencies": {
+          "Microsoft.Web.WebView2": "1.0.2651.64",
+          "Microsoft.Windows.SDK.BuildTools": "10.0.22621.756"
+        }
+      },
+      "Microsoft.Web.WebView2": {
+        "type": "Transitive",
+        "resolved": "1.0.2651.64",
+        "contentHash": "f5sc/vcAoTCTEW7Nqzp4galAuTRguZViw8ksn+Nx2uskEBPm0/ubzy6gVjvXS/P96jLS89C8T9I0hPc417xpNg=="
+      },
+      "Microsoft.Win32.SystemEvents": {
+        "type": "Transitive",
+        "resolved": "9.0.0",
+        "contentHash": "z8FfGIaoeALdD+KF44A2uP8PZIQQtDGiXsOLuN8nohbKhkyKt7zGaZb+fKiCxTuBqG22Q7myIAioSWaIcOOrOw=="
+      }
+    },
+    "net8.0-windows10.0.19041/win-x64": {
+      "Microsoft.WindowsAppSDK": {
+        "type": "Direct",
+        "requested": "[1.6.250108002, )",
+        "resolved": "1.6.250108002",
+        "contentHash": "dOJ3oFeHnehVBAR98lR6YfX0JgDJkrehLqwZQqQE/iZgyye1c1TZu9oj3MBbgWP9xFApWUrb0G49KKnQWEZLtA==",
+        "dependencies": {
+          "Microsoft.Web.WebView2": "1.0.2651.64",
+          "Microsoft.Windows.SDK.BuildTools": "10.0.22621.756"
+        }
+      },
+      "Microsoft.Web.WebView2": {
+        "type": "Transitive",
+        "resolved": "1.0.2651.64",
+        "contentHash": "f5sc/vcAoTCTEW7Nqzp4galAuTRguZViw8ksn+Nx2uskEBPm0/ubzy6gVjvXS/P96jLS89C8T9I0hPc417xpNg=="
+      },
+      "Microsoft.Win32.SystemEvents": {
+        "type": "Transitive",
+        "resolved": "9.0.0",
+        "contentHash": "z8FfGIaoeALdD+KF44A2uP8PZIQQtDGiXsOLuN8nohbKhkyKt7zGaZb+fKiCxTuBqG22Q7myIAioSWaIcOOrOw=="
+      }
+    },
+    "net8.0-windows10.0.19041/win-x86": {
+      "Microsoft.WindowsAppSDK": {
+        "type": "Direct",
+        "requested": "[1.6.250108002, )",
+        "resolved": "1.6.250108002",
+        "contentHash": "dOJ3oFeHnehVBAR98lR6YfX0JgDJkrehLqwZQqQE/iZgyye1c1TZu9oj3MBbgWP9xFApWUrb0G49KKnQWEZLtA==",
+        "dependencies": {
+          "Microsoft.Web.WebView2": "1.0.2651.64",
+          "Microsoft.Windows.SDK.BuildTools": "10.0.22621.756"
+        }
+      },
+      "Microsoft.Web.WebView2": {
+        "type": "Transitive",
+        "resolved": "1.0.2651.64",
+        "contentHash": "f5sc/vcAoTCTEW7Nqzp4galAuTRguZViw8ksn+Nx2uskEBPm0/ubzy6gVjvXS/P96jLS89C8T9I0hPc417xpNg=="
+      },
+      "Microsoft.Win32.SystemEvents": {
+        "type": "Transitive",
+        "resolved": "9.0.0",
+        "contentHash": "z8FfGIaoeALdD+KF44A2uP8PZIQQtDGiXsOLuN8nohbKhkyKt7zGaZb+fKiCxTuBqG22Q7myIAioSWaIcOOrOw=="
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/CoderSdk/CoderSdk.csproj b/CoderSdk/CoderSdk.csproj
index 1ca7d3c..87a04a8 100644
--- a/CoderSdk/CoderSdk.csproj
+++ b/CoderSdk/CoderSdk.csproj
@@ -1,10 +1,9 @@
 <Project Sdk="Microsoft.NET.Sdk">
-
     <PropertyGroup>
         <RootNamespace>Coder.Desktop.CoderSdk</RootNamespace>
         <TargetFramework>net8.0</TargetFramework>
         <ImplicitUsings>enable</ImplicitUsings>
         <Nullable>enable</Nullable>
+        <RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
     </PropertyGroup>
-
 </Project>
diff --git a/CoderSdk/packages.lock.json b/CoderSdk/packages.lock.json
new file mode 100644
index 0000000..807ab82
--- /dev/null
+++ b/CoderSdk/packages.lock.json
@@ -0,0 +1,6 @@
+{
+  "version": 1,
+  "dependencies": {
+    "net8.0": {}
+  }
+}
\ No newline at end of file
diff --git a/Package/Package.wapproj b/Package/Package.wapproj
index 10fb751..76d48c6 100644
--- a/Package/Package.wapproj
+++ b/Package/Package.wapproj
@@ -64,4 +64,4 @@
     <PackageReference Include="Microsoft.WindowsAppSDK" Version="1.6.250108002" />
   </ItemGroup>
   <Import Project="$(WapProjPath)\Microsoft.DesktopBridge.targets" />
-</Project>
\ No newline at end of file
+</Project>
diff --git a/Tests.Vpn.Proto/Tests.Vpn.Proto.csproj b/Tests.Vpn.Proto/Tests.Vpn.Proto.csproj
index 54b7b33..25b05ce 100644
--- a/Tests.Vpn.Proto/Tests.Vpn.Proto.csproj
+++ b/Tests.Vpn.Proto/Tests.Vpn.Proto.csproj
@@ -5,31 +5,32 @@
         <TargetFramework>net8.0</TargetFramework>
         <ImplicitUsings>enable</ImplicitUsings>
         <Nullable>enable</Nullable>
+        <RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
 
         <IsPackable>false</IsPackable>
         <IsTestProject>true</IsTestProject>
     </PropertyGroup>
 
     <ItemGroup>
-        <PackageReference Include="coverlet.collector" Version="6.0.2">
+        <PackageReference Include="coverlet.collector" Version="6.0.4">
             <PrivateAssets>all</PrivateAssets>
             <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
         </PackageReference>
-        <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0"/>
-        <PackageReference Include="NUnit" Version="4.2.2"/>
-        <PackageReference Include="NUnit.Analyzers" Version="4.4.0">
+        <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
+        <PackageReference Include="NUnit" Version="4.3.2" />
+        <PackageReference Include="NUnit.Analyzers" Version="4.6.0">
             <PrivateAssets>all</PrivateAssets>
             <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
         </PackageReference>
-        <PackageReference Include="NUnit3TestAdapter" Version="4.6.0"/>
+        <PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
     </ItemGroup>
 
     <ItemGroup>
-        <Using Include="NUnit.Framework"/>
+        <Using Include="NUnit.Framework" />
     </ItemGroup>
 
     <ItemGroup>
-        <ProjectReference Include="..\Vpn.Proto\Vpn.Proto.csproj"/>
+        <ProjectReference Include="..\Vpn.Proto\Vpn.Proto.csproj" />
     </ItemGroup>
 
 </Project>
diff --git a/Tests.Vpn.Proto/packages.lock.json b/Tests.Vpn.Proto/packages.lock.json
new file mode 100644
index 0000000..18a41b2
--- /dev/null
+++ b/Tests.Vpn.Proto/packages.lock.json
@@ -0,0 +1,84 @@
+{
+  "version": 1,
+  "dependencies": {
+    "net8.0": {
+      "coverlet.collector": {
+        "type": "Direct",
+        "requested": "[6.0.4, )",
+        "resolved": "6.0.4",
+        "contentHash": "lkhqpF8Pu2Y7IiN7OntbsTtdbpR1syMsm2F3IgX6ootA4ffRqWL5jF7XipHuZQTdVuWG/gVAAcf8mjk8Tz0xPg=="
+      },
+      "Microsoft.NET.Test.Sdk": {
+        "type": "Direct",
+        "requested": "[17.12.0, )",
+        "resolved": "17.12.0",
+        "contentHash": "kt/PKBZ91rFCWxVIJZSgVLk+YR+4KxTuHf799ho8WNiK5ZQpJNAEZCAWX86vcKrs+DiYjiibpYKdGZP6+/N17w==",
+        "dependencies": {
+          "Microsoft.CodeCoverage": "17.12.0",
+          "Microsoft.TestPlatform.TestHost": "17.12.0"
+        }
+      },
+      "NUnit": {
+        "type": "Direct",
+        "requested": "[4.3.2, )",
+        "resolved": "4.3.2",
+        "contentHash": "puVXayXNmEu7MFQSUswGmUjOy3M3baprMbkLl5PAutpeDoGTr+jPv33qAYsqxywi2wJCq8l/O3EhHoLulPE1iQ=="
+      },
+      "NUnit.Analyzers": {
+        "type": "Direct",
+        "requested": "[4.6.0, )",
+        "resolved": "4.6.0",
+        "contentHash": "uK1TEViVBugOO6uDou1amu7CoNhrd2sEUFr/iaEmVfoeY8qq/zzWCCUZi97aCCSZmjnHKCCWKh3RucU27qPlKg=="
+      },
+      "NUnit3TestAdapter": {
+        "type": "Direct",
+        "requested": "[4.6.0, )",
+        "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.TestPlatform.ObjectModel": {
+        "type": "Transitive",
+        "resolved": "17.12.0",
+        "contentHash": "TDqkTKLfQuAaPcEb3pDDWnh7b3SyZF+/W9OZvWFp6eJCIiiYFdSB6taE2I6tWrFw5ywhzOb6sreoGJTI6m3rSQ==",
+        "dependencies": {
+          "System.Reflection.Metadata": "1.6.0"
+        }
+      },
+      "Microsoft.TestPlatform.TestHost": {
+        "type": "Transitive",
+        "resolved": "17.12.0",
+        "contentHash": "MiPEJQNyADfwZ4pJNpQex+t9/jOClBGMiCiVVFuELCMSX2nmNfvUor3uFVxNNCg30uxDP8JDYfPnMXQzsfzYyg==",
+        "dependencies": {
+          "Microsoft.TestPlatform.ObjectModel": "17.12.0",
+          "Newtonsoft.Json": "13.0.1"
+        }
+      },
+      "Newtonsoft.Json": {
+        "type": "Transitive",
+        "resolved": "13.0.1",
+        "contentHash": "ppPFpBcvxdsfUonNcvITKqLl3bqxWbDCZIzDWHzjpdAHRFfZe0Dw9HmA0+za13IdyrgJwpkDTDA9fHaxOrt20A=="
+      },
+      "System.Reflection.Metadata": {
+        "type": "Transitive",
+        "resolved": "1.6.0",
+        "contentHash": "COC1aiAJjCoA5GBF+QKL2uLqEBew4JsCkQmoHKbN3TlOZKa2fKLz5CpiRQKDz0RsAOEGsVKqOD5bomsXq/4STQ=="
+      },
+      "vpn.proto": {
+        "type": "Project",
+        "dependencies": {
+          "Google.Protobuf": "[3.29.3, )"
+        }
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/Tests.Vpn.Service/Tests.Vpn.Service.csproj b/Tests.Vpn.Service/Tests.Vpn.Service.csproj
index 2fdfa76..e127a0d 100644
--- a/Tests.Vpn.Service/Tests.Vpn.Service.csproj
+++ b/Tests.Vpn.Service/Tests.Vpn.Service.csproj
@@ -5,31 +5,32 @@
         <TargetFramework>net8.0-windows</TargetFramework>
         <ImplicitUsings>enable</ImplicitUsings>
         <Nullable>enable</Nullable>
+        <RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
 
         <IsPackable>false</IsPackable>
         <IsTestProject>true</IsTestProject>
     </PropertyGroup>
 
     <ItemGroup>
-        <PackageReference Include="coverlet.collector" Version="6.0.2">
+        <PackageReference Include="coverlet.collector" Version="6.0.4">
             <PrivateAssets>all</PrivateAssets>
             <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
         </PackageReference>
-        <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0"/>
-        <PackageReference Include="NUnit" Version="4.2.2"/>
-        <PackageReference Include="NUnit.Analyzers" Version="4.4.0">
+        <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
+        <PackageReference Include="NUnit" Version="4.3.2" />
+        <PackageReference Include="NUnit.Analyzers" Version="4.6.0">
             <PrivateAssets>all</PrivateAssets>
             <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
         </PackageReference>
-        <PackageReference Include="NUnit3TestAdapter" Version="4.6.0"/>
+        <PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
     </ItemGroup>
 
     <ItemGroup>
-        <Using Include="NUnit.Framework"/>
+        <Using Include="NUnit.Framework" />
     </ItemGroup>
 
     <ItemGroup>
-        <ProjectReference Include="..\Vpn.Service\Vpn.Service.csproj"/>
+        <ProjectReference Include="..\Vpn.Service\Vpn.Service.csproj" />
     </ItemGroup>
 
 </Project>
diff --git a/Tests.Vpn.Service/packages.lock.json b/Tests.Vpn.Service/packages.lock.json
new file mode 100644
index 0000000..0b0ece4
--- /dev/null
+++ b/Tests.Vpn.Service/packages.lock.json
@@ -0,0 +1,458 @@
+{
+  "version": 1,
+  "dependencies": {
+    "net8.0-windows7.0": {
+      "coverlet.collector": {
+        "type": "Direct",
+        "requested": "[6.0.4, )",
+        "resolved": "6.0.4",
+        "contentHash": "lkhqpF8Pu2Y7IiN7OntbsTtdbpR1syMsm2F3IgX6ootA4ffRqWL5jF7XipHuZQTdVuWG/gVAAcf8mjk8Tz0xPg=="
+      },
+      "Microsoft.NET.Test.Sdk": {
+        "type": "Direct",
+        "requested": "[17.12.0, )",
+        "resolved": "17.12.0",
+        "contentHash": "kt/PKBZ91rFCWxVIJZSgVLk+YR+4KxTuHf799ho8WNiK5ZQpJNAEZCAWX86vcKrs+DiYjiibpYKdGZP6+/N17w==",
+        "dependencies": {
+          "Microsoft.CodeCoverage": "17.12.0",
+          "Microsoft.TestPlatform.TestHost": "17.12.0"
+        }
+      },
+      "NUnit": {
+        "type": "Direct",
+        "requested": "[4.3.2, )",
+        "resolved": "4.3.2",
+        "contentHash": "puVXayXNmEu7MFQSUswGmUjOy3M3baprMbkLl5PAutpeDoGTr+jPv33qAYsqxywi2wJCq8l/O3EhHoLulPE1iQ=="
+      },
+      "NUnit.Analyzers": {
+        "type": "Direct",
+        "requested": "[4.6.0, )",
+        "resolved": "4.6.0",
+        "contentHash": "uK1TEViVBugOO6uDou1amu7CoNhrd2sEUFr/iaEmVfoeY8qq/zzWCCUZi97aCCSZmjnHKCCWKh3RucU27qPlKg=="
+      },
+      "NUnit3TestAdapter": {
+        "type": "Direct",
+        "requested": "[4.6.0, )",
+        "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.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": {
+        "type": "Transitive",
+        "resolved": "9.0.1",
+        "contentHash": "qZI42ASAe3hr2zMSA6UjM92pO1LeDq5DcwkgSowXXPY8I56M76pEKrnmsKKbxagAf39AJxkH2DY4sb72ixyOrg==",
+        "dependencies": {
+          "Microsoft.Extensions.DependencyInjection.Abstractions": "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": {
+        "type": "Transitive",
+        "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.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.Hosting.WindowsServices": {
+        "type": "Transitive",
+        "resolved": "9.0.1",
+        "contentHash": "FLapgOXQzPjUsbMqjjagCFCiGjroRmrmHQVK3/PEovRIvDU6nLk7KKs4PalzEHaIfqG+PySlY/BeLTyZtjcshg==",
+        "dependencies": {
+          "Microsoft.Extensions.Hosting": "9.0.1",
+          "Microsoft.Extensions.Logging.EventLog": "9.0.1",
+          "System.ServiceProcess.ServiceController": "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": {
+        "type": "Transitive",
+        "resolved": "9.0.1",
+        "contentHash": "nggoNKnWcsBIAaOWHA+53XZWrslC7aGeok+aR+epDPRy7HI7GwMnGZE8yEsL2Onw7kMOHVHwKcsDls1INkNUJQ==",
+        "dependencies": {
+          "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
+          "Microsoft.Extensions.Primitives": "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.Options.DataAnnotations": {
+        "type": "Transitive",
+        "resolved": "9.0.1",
+        "contentHash": "T16k12gDWOoi9W/oueC7knsZxm3ZjqmrQBFLXx9UH3Kv4fbehMyiOdhi5u1Vw7M4g0uMj21InBfgDE0570byEQ==",
+        "dependencies": {
+          "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
+          "Microsoft.Extensions.Options": "9.0.1"
+        }
+      },
+      "Microsoft.Extensions.Primitives": {
+        "type": "Transitive",
+        "resolved": "9.0.1",
+        "contentHash": "bHtTesA4lrSGD1ZUaMIx6frU3wyy0vYtTa/hM6gGQu5QNrydObv8T5COiGUWsisflAfmsaFOe9Xvw5NSO99z0g=="
+      },
+      "Microsoft.Security.Extensions": {
+        "type": "Transitive",
+        "resolved": "1.3.0",
+        "contentHash": "xK8WFEo5WMUE8DI8W+GjhRwpVcPrxc4DyTjfxh39+yOyhAtC5TBHDlFEJks5toNZHsUeUuiWELIX25oTWOKPBw=="
+      },
+      "Microsoft.TestPlatform.ObjectModel": {
+        "type": "Transitive",
+        "resolved": "17.12.0",
+        "contentHash": "TDqkTKLfQuAaPcEb3pDDWnh7b3SyZF+/W9OZvWFp6eJCIiiYFdSB6taE2I6tWrFw5ywhzOb6sreoGJTI6m3rSQ==",
+        "dependencies": {
+          "System.Reflection.Metadata": "1.6.0"
+        }
+      },
+      "Microsoft.TestPlatform.TestHost": {
+        "type": "Transitive",
+        "resolved": "17.12.0",
+        "contentHash": "MiPEJQNyADfwZ4pJNpQex+t9/jOClBGMiCiVVFuELCMSX2nmNfvUor3uFVxNNCg30uxDP8JDYfPnMXQzsfzYyg==",
+        "dependencies": {
+          "Microsoft.TestPlatform.ObjectModel": "17.12.0",
+          "Newtonsoft.Json": "13.0.1"
+        }
+      },
+      "Newtonsoft.Json": {
+        "type": "Transitive",
+        "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.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.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=="
+      },
+      "System.ServiceProcess.ServiceController": {
+        "type": "Transitive",
+        "resolved": "9.0.1",
+        "contentHash": "Ghm4yP29P3cC65Qof8CrgU3WO/q3ERtht6/CrvcUl1FgRs6D7exj75GuG4ciRv0sjygtvyd675924DFsxxnEgA==",
+        "dependencies": {
+          "System.Diagnostics.EventLog": "9.0.1"
+        }
+      },
+      "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"
+        }
+      },
+      "codersdk": {
+        "type": "Project"
+      },
+      "vpn": {
+        "type": "Project",
+        "dependencies": {
+          "System.IO.Pipelines": "[9.0.1, )",
+          "Vpn.Proto": "[1.0.0, )"
+        }
+      },
+      "vpn.proto": {
+        "type": "Project",
+        "dependencies": {
+          "Google.Protobuf": "[3.29.3, )"
+        }
+      },
+      "vpn.service": {
+        "type": "Project",
+        "dependencies": {
+          "CoderSdk": "[1.0.0, )",
+          "Microsoft.Extensions.Hosting": "[9.0.1, )",
+          "Microsoft.Extensions.Hosting.WindowsServices": "[9.0.1, )",
+          "Microsoft.Extensions.Options.DataAnnotations": "[9.0.1, )",
+          "Microsoft.Security.Extensions": "[1.3.0, )",
+          "Semver": "[3.0.0, )",
+          "Vpn": "[1.0.0, )"
+        }
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/Tests.Vpn/Tests.Vpn.csproj b/Tests.Vpn/Tests.Vpn.csproj
index df00e81..7216a35 100644
--- a/Tests.Vpn/Tests.Vpn.csproj
+++ b/Tests.Vpn/Tests.Vpn.csproj
@@ -5,31 +5,32 @@
         <TargetFramework>net8.0</TargetFramework>
         <ImplicitUsings>enable</ImplicitUsings>
         <Nullable>enable</Nullable>
+        <RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
 
         <IsPackable>false</IsPackable>
         <IsTestProject>true</IsTestProject>
     </PropertyGroup>
 
     <ItemGroup>
-        <PackageReference Include="coverlet.collector" Version="6.0.2">
+        <PackageReference Include="coverlet.collector" Version="6.0.4">
             <PrivateAssets>all</PrivateAssets>
             <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
         </PackageReference>
-        <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0"/>
-        <PackageReference Include="NUnit" Version="4.2.2"/>
-        <PackageReference Include="NUnit.Analyzers" Version="4.4.0">
+        <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
+        <PackageReference Include="NUnit" Version="4.3.2" />
+        <PackageReference Include="NUnit.Analyzers" Version="4.6.0">
             <PrivateAssets>all</PrivateAssets>
             <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
         </PackageReference>
-        <PackageReference Include="NUnit3TestAdapter" Version="4.6.0"/>
+        <PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
     </ItemGroup>
 
     <ItemGroup>
-        <Using Include="NUnit.Framework"/>
+        <Using Include="NUnit.Framework" />
     </ItemGroup>
 
     <ItemGroup>
-        <ProjectReference Include="..\Vpn\Vpn.csproj"/>
+        <ProjectReference Include="..\Vpn\Vpn.csproj" />
     </ItemGroup>
 
 </Project>
diff --git a/Tests.Vpn/packages.lock.json b/Tests.Vpn/packages.lock.json
new file mode 100644
index 0000000..63ae6bd
--- /dev/null
+++ b/Tests.Vpn/packages.lock.json
@@ -0,0 +1,96 @@
+{
+  "version": 1,
+  "dependencies": {
+    "net8.0": {
+      "coverlet.collector": {
+        "type": "Direct",
+        "requested": "[6.0.4, )",
+        "resolved": "6.0.4",
+        "contentHash": "lkhqpF8Pu2Y7IiN7OntbsTtdbpR1syMsm2F3IgX6ootA4ffRqWL5jF7XipHuZQTdVuWG/gVAAcf8mjk8Tz0xPg=="
+      },
+      "Microsoft.NET.Test.Sdk": {
+        "type": "Direct",
+        "requested": "[17.12.0, )",
+        "resolved": "17.12.0",
+        "contentHash": "kt/PKBZ91rFCWxVIJZSgVLk+YR+4KxTuHf799ho8WNiK5ZQpJNAEZCAWX86vcKrs+DiYjiibpYKdGZP6+/N17w==",
+        "dependencies": {
+          "Microsoft.CodeCoverage": "17.12.0",
+          "Microsoft.TestPlatform.TestHost": "17.12.0"
+        }
+      },
+      "NUnit": {
+        "type": "Direct",
+        "requested": "[4.3.2, )",
+        "resolved": "4.3.2",
+        "contentHash": "puVXayXNmEu7MFQSUswGmUjOy3M3baprMbkLl5PAutpeDoGTr+jPv33qAYsqxywi2wJCq8l/O3EhHoLulPE1iQ=="
+      },
+      "NUnit.Analyzers": {
+        "type": "Direct",
+        "requested": "[4.6.0, )",
+        "resolved": "4.6.0",
+        "contentHash": "uK1TEViVBugOO6uDou1amu7CoNhrd2sEUFr/iaEmVfoeY8qq/zzWCCUZi97aCCSZmjnHKCCWKh3RucU27qPlKg=="
+      },
+      "NUnit3TestAdapter": {
+        "type": "Direct",
+        "requested": "[4.6.0, )",
+        "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.TestPlatform.ObjectModel": {
+        "type": "Transitive",
+        "resolved": "17.12.0",
+        "contentHash": "TDqkTKLfQuAaPcEb3pDDWnh7b3SyZF+/W9OZvWFp6eJCIiiYFdSB6taE2I6tWrFw5ywhzOb6sreoGJTI6m3rSQ==",
+        "dependencies": {
+          "System.Reflection.Metadata": "1.6.0"
+        }
+      },
+      "Microsoft.TestPlatform.TestHost": {
+        "type": "Transitive",
+        "resolved": "17.12.0",
+        "contentHash": "MiPEJQNyADfwZ4pJNpQex+t9/jOClBGMiCiVVFuELCMSX2nmNfvUor3uFVxNNCg30uxDP8JDYfPnMXQzsfzYyg==",
+        "dependencies": {
+          "Microsoft.TestPlatform.ObjectModel": "17.12.0",
+          "Newtonsoft.Json": "13.0.1"
+        }
+      },
+      "Newtonsoft.Json": {
+        "type": "Transitive",
+        "resolved": "13.0.1",
+        "contentHash": "ppPFpBcvxdsfUonNcvITKqLl3bqxWbDCZIzDWHzjpdAHRFfZe0Dw9HmA0+za13IdyrgJwpkDTDA9fHaxOrt20A=="
+      },
+      "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=="
+      },
+      "vpn": {
+        "type": "Project",
+        "dependencies": {
+          "System.IO.Pipelines": "[9.0.1, )",
+          "Vpn.Proto": "[1.0.0, )"
+        }
+      },
+      "vpn.proto": {
+        "type": "Project",
+        "dependencies": {
+          "Google.Protobuf": "[3.29.3, )"
+        }
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/Vpn.Proto/Vpn.Proto.csproj b/Vpn.Proto/Vpn.Proto.csproj
index 6acb12e..f1cca78 100644
--- a/Vpn.Proto/Vpn.Proto.csproj
+++ b/Vpn.Proto/Vpn.Proto.csproj
@@ -5,15 +5,16 @@
         <TargetFramework>net8.0</TargetFramework>
         <ImplicitUsings>enable</ImplicitUsings>
         <Nullable>enable</Nullable>
+        <RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
     </PropertyGroup>
 
     <ItemGroup>
-        <Protobuf Include="vpn.proto"/>
+        <Protobuf Include="vpn.proto" />
     </ItemGroup>
 
     <ItemGroup>
-        <PackageReference Include="Google.Protobuf" Version="3.29.1"/>
-        <PackageReference Include="Grpc.Tools" Version="2.68.1">
+        <PackageReference Include="Google.Protobuf" Version="3.29.3" />
+        <PackageReference Include="Grpc.Tools" Version="2.69.0">
             <PrivateAssets>all</PrivateAssets>
             <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
         </PackageReference>
diff --git a/Vpn.Proto/packages.lock.json b/Vpn.Proto/packages.lock.json
new file mode 100644
index 0000000..3cbfd8e
--- /dev/null
+++ b/Vpn.Proto/packages.lock.json
@@ -0,0 +1,19 @@
+{
+  "version": 1,
+  "dependencies": {
+    "net8.0": {
+      "Google.Protobuf": {
+        "type": "Direct",
+        "requested": "[3.29.3, )",
+        "resolved": "3.29.3",
+        "contentHash": "t7nZFFUFwigCwZ+nIXHDLweXvwIpsOXi+P7J7smPT/QjI3EKxnCzTQOhBqyEh6XEzc/pNH+bCFOOSjatrPt6Tw=="
+      },
+      "Grpc.Tools": {
+        "type": "Direct",
+        "requested": "[2.69.0, )",
+        "resolved": "2.69.0",
+        "contentHash": "W5hW4R1h19FCzKb8ToqIJMI5YxnQqGmREEpV8E5XkfCtLPIK5MSHztwQ8gZUfG8qu9fg5MhItjzyPRqQBjnrbA=="
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/Vpn.Service/Vpn.Service.csproj b/Vpn.Service/Vpn.Service.csproj
index e6da70d..74e75b9 100644
--- a/Vpn.Service/Vpn.Service.csproj
+++ b/Vpn.Service/Vpn.Service.csproj
@@ -6,19 +6,20 @@
         <TargetFramework>net8.0-windows</TargetFramework>
         <ImplicitUsings>enable</ImplicitUsings>
         <Nullable>enable</Nullable>
+        <RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
     </PropertyGroup>
 
     <ItemGroup>
-        <PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.0"/>
-        <PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="9.0.0"/>
-        <PackageReference Include="Microsoft.Extensions.Options.DataAnnotations" Version="9.0.0"/>
-        <PackageReference Include="Microsoft.Security.Extensions" Version="1.3.0"/>
-        <PackageReference Include="Semver" Version="3.0.0"/>
+        <PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.1" />
+        <PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="9.0.1" />
+        <PackageReference Include="Microsoft.Extensions.Options.DataAnnotations" Version="9.0.1" />
+        <PackageReference Include="Microsoft.Security.Extensions" Version="1.3.0" />
+        <PackageReference Include="Semver" Version="3.0.0" />
     </ItemGroup>
 
     <ItemGroup>
-        <ProjectReference Include="..\CoderSdk\CoderSdk.csproj"/>
-        <ProjectReference Include="..\Vpn\Vpn.csproj"/>
+        <ProjectReference Include="..\CoderSdk\CoderSdk.csproj" />
+        <ProjectReference Include="..\Vpn\Vpn.csproj" />
     </ItemGroup>
 
 </Project>
diff --git a/Vpn.Service/packages.lock.json b/Vpn.Service/packages.lock.json
new file mode 100644
index 0000000..f410885
--- /dev/null
+++ b/Vpn.Service/packages.lock.json
@@ -0,0 +1,385 @@
+{
+  "version": 1,
+  "dependencies": {
+    "net8.0-windows7.0": {
+      "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.Hosting.WindowsServices": {
+        "type": "Direct",
+        "requested": "[9.0.1, )",
+        "resolved": "9.0.1",
+        "contentHash": "FLapgOXQzPjUsbMqjjagCFCiGjroRmrmHQVK3/PEovRIvDU6nLk7KKs4PalzEHaIfqG+PySlY/BeLTyZtjcshg==",
+        "dependencies": {
+          "Microsoft.Extensions.Hosting": "9.0.1",
+          "Microsoft.Extensions.Logging.EventLog": "9.0.1",
+          "System.ServiceProcess.ServiceController": "9.0.1"
+        }
+      },
+      "Microsoft.Extensions.Options.DataAnnotations": {
+        "type": "Direct",
+        "requested": "[9.0.1, )",
+        "resolved": "9.0.1",
+        "contentHash": "T16k12gDWOoi9W/oueC7knsZxm3ZjqmrQBFLXx9UH3Kv4fbehMyiOdhi5u1Vw7M4g0uMj21InBfgDE0570byEQ==",
+        "dependencies": {
+          "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
+          "Microsoft.Extensions.Options": "9.0.1"
+        }
+      },
+      "Microsoft.Security.Extensions": {
+        "type": "Direct",
+        "requested": "[1.3.0, )",
+        "resolved": "1.3.0",
+        "contentHash": "xK8WFEo5WMUE8DI8W+GjhRwpVcPrxc4DyTjfxh39+yOyhAtC5TBHDlFEJks5toNZHsUeUuiWELIX25oTWOKPBw=="
+      },
+      "Semver": {
+        "type": "Direct",
+        "requested": "[3.0.0, )",
+        "resolved": "3.0.0",
+        "contentHash": "9jZCicsVgTebqkAujRWtC9J1A5EQVlu0TVKHcgoCuv345ve5DYf4D1MjhKEnQjdRZo6x/vdv6QQrYFs7ilGzLA==",
+        "dependencies": {
+          "Microsoft.Extensions.Primitives": "5.0.1"
+        }
+      },
+      "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.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": {
+        "type": "Transitive",
+        "resolved": "9.0.1",
+        "contentHash": "qZI42ASAe3hr2zMSA6UjM92pO1LeDq5DcwkgSowXXPY8I56M76pEKrnmsKKbxagAf39AJxkH2DY4sb72ixyOrg==",
+        "dependencies": {
+          "Microsoft.Extensions.DependencyInjection.Abstractions": "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": {
+        "type": "Transitive",
+        "resolved": "9.0.1",
+        "contentHash": "nggoNKnWcsBIAaOWHA+53XZWrslC7aGeok+aR+epDPRy7HI7GwMnGZE8yEsL2Onw7kMOHVHwKcsDls1INkNUJQ==",
+        "dependencies": {
+          "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
+          "Microsoft.Extensions.Primitives": "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": "9.0.1",
+        "contentHash": "bHtTesA4lrSGD1ZUaMIx6frU3wyy0vYtTa/hM6gGQu5QNrydObv8T5COiGUWsisflAfmsaFOe9Xvw5NSO99z0g=="
+      },
+      "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.IO.Pipelines": {
+        "type": "Transitive",
+        "resolved": "9.0.1",
+        "contentHash": "uXf5o8eV/gtzDQY4lGROLFMWQvcViKcF8o4Q6KpIOjloAQXrnscQSu6gTxYJMHuNJnh7szIF9AzkaEq+zDLoEg=="
+      },
+      "System.ServiceProcess.ServiceController": {
+        "type": "Transitive",
+        "resolved": "9.0.1",
+        "contentHash": "Ghm4yP29P3cC65Qof8CrgU3WO/q3ERtht6/CrvcUl1FgRs6D7exj75GuG4ciRv0sjygtvyd675924DFsxxnEgA==",
+        "dependencies": {
+          "System.Diagnostics.EventLog": "9.0.1"
+        }
+      },
+      "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"
+        }
+      },
+      "codersdk": {
+        "type": "Project"
+      },
+      "vpn": {
+        "type": "Project",
+        "dependencies": {
+          "System.IO.Pipelines": "[9.0.1, )",
+          "Vpn.Proto": "[1.0.0, )"
+        }
+      },
+      "vpn.proto": {
+        "type": "Project",
+        "dependencies": {
+          "Google.Protobuf": "[3.29.3, )"
+        }
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/Vpn/Vpn.csproj b/Vpn/Vpn.csproj
index 22b585f..abe75c7 100644
--- a/Vpn/Vpn.csproj
+++ b/Vpn/Vpn.csproj
@@ -5,14 +5,15 @@
         <TargetFramework>net8.0</TargetFramework>
         <ImplicitUsings>enable</ImplicitUsings>
         <Nullable>enable</Nullable>
+        <RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
     </PropertyGroup>
 
     <ItemGroup>
-        <ProjectReference Include="..\Vpn.Proto\Vpn.Proto.csproj"/>
+        <ProjectReference Include="..\Vpn.Proto\Vpn.Proto.csproj" />
     </ItemGroup>
 
     <ItemGroup>
-        <PackageReference Include="System.IO.Pipelines" Version="9.0.0"/>
+        <PackageReference Include="System.IO.Pipelines" Version="9.0.1" />
     </ItemGroup>
 
 </Project>
diff --git a/Vpn/packages.lock.json b/Vpn/packages.lock.json
new file mode 100644
index 0000000..07aef86
--- /dev/null
+++ b/Vpn/packages.lock.json
@@ -0,0 +1,24 @@
+{
+  "version": 1,
+  "dependencies": {
+    "net8.0": {
+      "System.IO.Pipelines": {
+        "type": "Direct",
+        "requested": "[9.0.1, )",
+        "resolved": "9.0.1",
+        "contentHash": "uXf5o8eV/gtzDQY4lGROLFMWQvcViKcF8o4Q6KpIOjloAQXrnscQSu6gTxYJMHuNJnh7szIF9AzkaEq+zDLoEg=="
+      },
+      "Google.Protobuf": {
+        "type": "Transitive",
+        "resolved": "3.29.3",
+        "contentHash": "t7nZFFUFwigCwZ+nIXHDLweXvwIpsOXi+P7J7smPT/QjI3EKxnCzTQOhBqyEh6XEzc/pNH+bCFOOSjatrPt6Tw=="
+      },
+      "vpn.proto": {
+        "type": "Project",
+        "dependencies": {
+          "Google.Protobuf": "[3.29.3, )"
+        }
+      }
+    }
+  }
+}
\ No newline at end of file

From 6c610a51d426945d6434d07ac2e5d7dcd0ab8f36 Mon Sep 17 00:00:00 2001
From: Dean Sheather <dean@deansheather.com>
Date: Tue, 28 Jan 2025 15:16:04 +1100
Subject: [PATCH 3/7] fixup! chore: add GitHub actions

---
 .github/workflows/ci.yaml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index f3c4dc5..0f09e34 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -18,7 +18,7 @@ jobs:
         with:
           dotnet-version: 8.0.x
           cache: true
-          cache-dependency-path: */packages.lock.json
+          cache-dependency-path: '**/packages.lock.json'
       - name: Install dotnet format tool
         run: dotnet tool install -g dotnet-format
       - name: dotnet restore

From bc4ae727f4ace52b9cf0ee362e21cce816ab5e16 Mon Sep 17 00:00:00 2001
From: Dean Sheather <dean@deansheather.com>
Date: Tue, 28 Jan 2025 15:22:37 +1100
Subject: [PATCH 4/7] fixup! chore: add GitHub actions

---
 .github/workflows/ci.yaml     | 18 +++++++++---------
 CoderSdk/CoderApiClient.cs    |  2 +-
 Vpn.Proto/RpcMessage.cs       |  2 +-
 Vpn.Proto/RpcVersion.cs       | 14 +++++++-------
 Vpn.Service/ManagerService.cs |  2 +-
 Vpn/Serdes.cs                 |  2 +-
 Vpn/Speaker.cs                |  4 ++--
 7 files changed, 22 insertions(+), 22 deletions(-)

diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 0f09e34..5e66989 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -19,12 +19,10 @@ jobs:
           dotnet-version: 8.0.x
           cache: true
           cache-dependency-path: '**/packages.lock.json'
-      - name: Install dotnet format tool
-        run: dotnet tool install -g dotnet-format
       - name: dotnet restore
-        run: dotnet restore --locked-mode .\Coder.Desktop.sln
+        run: dotnet restore --locked-mode
       - name: dotnet format
-        run: dotnet format --verify-no-changes --no-restore .\Coder.Desktop.sln
+        run: dotnet format --verify-no-changes --no-restore
 
   test:
     runs-on: windows-latest
@@ -35,10 +33,11 @@ jobs:
         with:
           dotnet-version: 8.0.x
           cache: true
+          cache-dependency-path: '**/packages.lock.json'
       - name: dotnet restore
-        run: dotnet restore --locked-mode .\Coder.Desktop.sln
+        run: dotnet restore --locked-mode
       - name: dotnet test
-        run: dotnet test --no-restore .\Coder.Desktop.sln
+        run: dotnet test --no-restore
 
   build:
     runs-on: windows-latest
@@ -49,12 +48,13 @@ jobs:
         with:
           dotnet-version: 8.0.x
           cache: true
+          cache-dependency-path: '**/packages.lock.json'
       - name: dotnet restore
-        run: dotnet restore --locked-mode .\Coder.Desktop.sln
+        run: dotnet restore --locked-mode
       - name: dotnet build
-        run: dotnet build .\Coder.Desktop.sln
+        run: dotnet build
       - name: dotnet publish
-        run: dotnet publish .\Coder.Desktop.sln -c Release -o .\publish
+        run: dotnet publish --no-restore --configuration Release --output .\publish
       - name: Upload artifact
         uses: actions/upload-artifact@v4
         with:
diff --git a/CoderSdk/CoderApiClient.cs b/CoderSdk/CoderApiClient.cs
index 90343f3..cfcfdb7 100644
--- a/CoderSdk/CoderApiClient.cs
+++ b/CoderSdk/CoderApiClient.cs
@@ -1,4 +1,4 @@
-using System.Text;
+using System.Text;
 using System.Text.Json;
 using System.Text.Json.Serialization;
 
diff --git a/Vpn.Proto/RpcMessage.cs b/Vpn.Proto/RpcMessage.cs
index fc1af11..8a4ea26 100644
--- a/Vpn.Proto/RpcMessage.cs
+++ b/Vpn.Proto/RpcMessage.cs
@@ -1,4 +1,4 @@
-using System.Reflection;
+using System.Reflection;
 using Google.Protobuf;
 
 namespace Coder.Desktop.Vpn.Proto;
diff --git a/Vpn.Proto/RpcVersion.cs b/Vpn.Proto/RpcVersion.cs
index 8eb0de0..d5d0520 100644
--- a/Vpn.Proto/RpcVersion.cs
+++ b/Vpn.Proto/RpcVersion.cs
@@ -1,4 +1,4 @@
-namespace Coder.Desktop.Vpn.Proto;
+namespace Coder.Desktop.Vpn.Proto;
 
 /// <summary>
 ///     A version of the RPC API. Can be compared other versions to determine compatibility between two peers.
@@ -152,12 +152,12 @@ public void Validate()
     {
         RpcVersion? bestVersion = null;
         foreach (var v1 in this)
-        foreach (var v2 in other)
-            if (v1.Major == v2.Major && (bestVersion is null || v1.Major > bestVersion.Major))
-            {
-                var v = v1.IsCompatibleWith(v2);
-                if (v is not null) bestVersion = v;
-            }
+            foreach (var v2 in other)
+                if (v1.Major == v2.Major && (bestVersion is null || v1.Major > bestVersion.Major))
+                {
+                    var v = v1.IsCompatibleWith(v2);
+                    if (v is not null) bestVersion = v;
+                }
 
         return bestVersion;
     }
diff --git a/Vpn.Service/ManagerService.cs b/Vpn.Service/ManagerService.cs
index b7b2e34..b7328e9 100644
--- a/Vpn.Service/ManagerService.cs
+++ b/Vpn.Service/ManagerService.cs
@@ -1,4 +1,4 @@
-using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Hosting;
 using Microsoft.Extensions.Logging;
 
 namespace Coder.Desktop.Vpn.Service;
diff --git a/Vpn/Serdes.cs b/Vpn/Serdes.cs
index 00837b7..b266f93 100644
--- a/Vpn/Serdes.cs
+++ b/Vpn/Serdes.cs
@@ -1,4 +1,4 @@
-using System.Buffers.Binary;
+using System.Buffers.Binary;
 using Coder.Desktop.Vpn.Proto;
 using Coder.Desktop.Vpn.Utilities;
 using Google.Protobuf;
diff --git a/Vpn/Speaker.cs b/Vpn/Speaker.cs
index 899c346..f2f2f68 100644
--- a/Vpn/Speaker.cs
+++ b/Vpn/Speaker.cs
@@ -1,4 +1,4 @@
-using System.Collections.Concurrent;
+using System.Collections.Concurrent;
 using System.Text;
 using Coder.Desktop.Vpn.Proto;
 using Coder.Desktop.Vpn.Utilities;
@@ -195,7 +195,7 @@ private async Task ReceiveLoop(CancellationToken ct = default)
             while (!ct.IsCancellationRequested)
             {
                 var message = await _serdes.ReadMessage(_conn, ct);
-                if (message is { RpcField.ResponseTo : not 0 })
+                if (message is { RpcField.ResponseTo: not 0 })
                 {
                     // Look up the TaskCompletionSource for the message ID and
                     // complete it with the message.

From 7a36ab77505c460c71b2084b210d6d49c0cf356f Mon Sep 17 00:00:00 2001
From: Dean Sheather <dean@deansheather.com>
Date: Tue, 28 Jan 2025 16:13:11 +1100
Subject: [PATCH 5/7] fixup! chore: add GitHub actions

---
 .github/workflows/ci.yaml                     |   2 -
 .gitignore                                    |   2 +-
 App/App.csproj                                |  42 ++++++++++++--
 .../Images/SplashScreen.scale-200.png         | Bin
 .../Images/Square150x150Logo.scale-200.png    | Bin
 .../Images/Square44x44Logo.scale-200.png      | Bin
 App/Package.appxmanifest                      |  52 ++++++++++++++++++
 .../PublishProfiles/win-arm64.pubxml          |  14 +++++
 App/Properties/PublishProfiles/win-x64.pubxml |  14 +++++
 App/Properties/PublishProfiles/win-x86.pubxml |  14 +++++
 App/Properties/launchSettings.json            |  10 ++++
 Coder.Desktop.sln                             |  36 +++---------
 Tests.Vpn/Utilities/TaskUtilitiesTest.cs      |   8 ---
 13 files changed, 151 insertions(+), 43 deletions(-)
 rename {Package => App}/Images/SplashScreen.scale-200.png (100%)
 rename {Package => App}/Images/Square150x150Logo.scale-200.png (100%)
 rename {Package => App}/Images/Square44x44Logo.scale-200.png (100%)
 create mode 100644 App/Package.appxmanifest
 create mode 100644 App/Properties/PublishProfiles/win-arm64.pubxml
 create mode 100644 App/Properties/PublishProfiles/win-x64.pubxml
 create mode 100644 App/Properties/PublishProfiles/win-x86.pubxml
 create mode 100644 App/Properties/launchSettings.json

diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 5e66989..6575860 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -51,8 +51,6 @@ jobs:
           cache-dependency-path: '**/packages.lock.json'
       - name: dotnet restore
         run: dotnet restore --locked-mode
-      - name: dotnet build
-        run: dotnet build
       - name: dotnet publish
         run: dotnet publish --no-restore --configuration Release --output .\publish
       - name: Upload artifact
diff --git a/.gitignore b/.gitignore
index e191fc6..d378f88 100644
--- a/.gitignore
+++ b/.gitignore
@@ -186,7 +186,7 @@ publish/
 *.azurePubxml
 # Note: Comment the next line if you want to checkin your web deploy settings,
 # but database connection strings (with potential passwords) will be unencrypted
-*.pubxml
+#*.pubxml
 *.publishproj
 
 # Microsoft Azure Web App publish settings. Comment the next line if you want to
diff --git a/App/App.csproj b/App/App.csproj
index ca44062..0c014e1 100644
--- a/App/App.csproj
+++ b/App/App.csproj
@@ -7,24 +7,38 @@
         <ApplicationManifest>app.manifest</ApplicationManifest>
         <Platforms>x86;x64;ARM64</Platforms>
         <RuntimeIdentifiers>win-x86;win-x64;win-arm64</RuntimeIdentifiers>
+        <PublishProfile>Properties\PublishProfiles\win-$(Platform).pubxml</PublishProfile>
         <UseWinUI>true</UseWinUI>
         <Nullable>enable</Nullable>
+        <EnableMsixTooling>true</EnableMsixTooling>
         <RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
     </PropertyGroup>
 
     <ItemGroup>
-        <Manifest Include="$(ApplicationManifest)"/>
+        <AppxManifest Include="Package.appxmanifest">
+            <SubType>Designer</SubType>
+        </AppxManifest>
     </ItemGroup>
 
     <ItemGroup>
-        <PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0"/>
+        <Manifest Include="$(ApplicationManifest)" />
+    </ItemGroup>
+
+    <ItemGroup>
+        <Content Include="Images\SplashScreen.scale-200.png" />
+        <Content Include="Images\Square150x150Logo.scale-200.png" />
+        <Content Include="Images\Square44x44Logo.scale-200.png" />
+    </ItemGroup>
+
+    <ItemGroup>
+        <PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
         <PackageReference Include="DependencyPropertyGenerator" Version="1.5.0">
             <PrivateAssets>all</PrivateAssets>
             <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
         </PackageReference>
-        <PackageReference Include="H.NotifyIcon.WinUI" Version="2.2.0"/>
-        <PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.1742"/>
-        <PackageReference Include="Microsoft.WindowsAppSDK" Version="1.6.250108002"/>
+        <PackageReference Include="H.NotifyIcon.WinUI" Version="2.2.0" />
+        <PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.1742" />
+        <PackageReference Include="Microsoft.WindowsAppSDK" Version="1.6.250108002" />
     </ItemGroup>
 
     <ItemGroup>
@@ -39,6 +53,24 @@
         </Page>
     </ItemGroup>
 
+    <!--
+      Defining the "Msix" ProjectCapability here allows the Single-project MSIX Packaging
+      Tools extension to be activated for this project even if the Windows App SDK Nuget
+      package has not yet been restored.
+    -->
+    <ItemGroup Condition="'$(DisableMsixProjectCapabilityAddedByProject)'!='true' and '$(EnableMsixTooling)'=='true'">
+        <ProjectCapability Include="Msix" />
+    </ItemGroup>
+
+    <!--
+      Defining the "HasPackageAndPublishMenuAddedByProject" property here allows the Solution
+      Explorer "Package and Publish" context menu entry to be enabled for this project even if
+      the Windows App SDK Nuget package has not yet been restored.
+    -->
+    <PropertyGroup Condition="'$(DisableHasPackageAndPublishMenuAddedByProject)'!='true' and '$(EnableMsixTooling)'=='true'">
+        <HasPackageAndPublishMenu>true</HasPackageAndPublishMenu>
+    </PropertyGroup>
+
     <!-- Publish Properties -->
     <PropertyGroup>
         <PublishReadyToRun Condition="'$(Configuration)' == 'Debug'">False</PublishReadyToRun>
diff --git a/Package/Images/SplashScreen.scale-200.png b/App/Images/SplashScreen.scale-200.png
similarity index 100%
rename from Package/Images/SplashScreen.scale-200.png
rename to App/Images/SplashScreen.scale-200.png
diff --git a/Package/Images/Square150x150Logo.scale-200.png b/App/Images/Square150x150Logo.scale-200.png
similarity index 100%
rename from Package/Images/Square150x150Logo.scale-200.png
rename to App/Images/Square150x150Logo.scale-200.png
diff --git a/Package/Images/Square44x44Logo.scale-200.png b/App/Images/Square44x44Logo.scale-200.png
similarity index 100%
rename from Package/Images/Square44x44Logo.scale-200.png
rename to App/Images/Square44x44Logo.scale-200.png
diff --git a/App/Package.appxmanifest b/App/Package.appxmanifest
new file mode 100644
index 0000000..0c14c30
--- /dev/null
+++ b/App/Package.appxmanifest
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<Package
+    xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest"
+    xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
+    xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
+    xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
+    IgnorableNamespaces="uap rescap">
+
+    <Identity
+        Name="925b49fc-4648-4967-b4e6-b5473061ee62"
+        Publisher="CN=dean"
+        Version="1.0.0.0"/>
+
+    <mp:PhoneIdentity PhoneProductId="925b49fc-4648-4967-b4e6-b5473061ee62"
+                      PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
+
+    <Properties>
+        <DisplayName>Coder Desktop (Package)</DisplayName>
+        <PublisherDisplayName>dean</PublisherDisplayName>
+        <Logo>Images\StoreLogo.png</Logo>
+    </Properties>
+
+    <Dependencies>
+        <TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0"/>
+        <TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0"/>
+    </Dependencies>
+
+    <Resources>
+        <Resource Language="x-generate"/>
+    </Resources>
+
+    <Applications>
+        <Application Id="App"
+                     Executable="$targetnametoken$.exe"
+                     EntryPoint="$targetentrypoint$">
+            <uap:VisualElements
+                DisplayName="Coder Desktop"
+                Description="Coder"
+                BackgroundColor="transparent"
+                Square150x150Logo="Images\Square150x150Logo.png"
+                Square44x44Logo="Images\Square44x44Logo.png">
+                <uap:DefaultTile Wide310x150Logo="Images\Wide310x150Logo.png"/>
+                <uap:SplashScreen Image="Images\SplashScreen.png"/>
+            </uap:VisualElements>
+        </Application>
+    </Applications>
+
+    <Capabilities>
+        <rescap:Capability Name="runFullTrust"/>
+    </Capabilities>
+</Package>
diff --git a/App/Properties/PublishProfiles/win-arm64.pubxml b/App/Properties/PublishProfiles/win-arm64.pubxml
new file mode 100644
index 0000000..8953cce
--- /dev/null
+++ b/App/Properties/PublishProfiles/win-arm64.pubxml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+https://go.microsoft.com/fwlink/?LinkID=208121.
+-->
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup>
+    <PublishProtocol>FileSystem</PublishProtocol>
+    <Platform>ARM64</Platform>
+    <RuntimeIdentifier>win-arm64</RuntimeIdentifier>
+    <PublishDir>bin\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier)\publish\</PublishDir>
+    <SelfContained>true</SelfContained>
+    <PublishSingleFile>False</PublishSingleFile>
+  </PropertyGroup>
+</Project>
\ No newline at end of file
diff --git a/App/Properties/PublishProfiles/win-x64.pubxml b/App/Properties/PublishProfiles/win-x64.pubxml
new file mode 100644
index 0000000..cd99561
--- /dev/null
+++ b/App/Properties/PublishProfiles/win-x64.pubxml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+https://go.microsoft.com/fwlink/?LinkID=208121.
+-->
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup>
+    <PublishProtocol>FileSystem</PublishProtocol>
+    <Platform>x64</Platform>
+    <RuntimeIdentifier>win-x64</RuntimeIdentifier>
+    <PublishDir>bin\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier)\publish\</PublishDir>
+    <SelfContained>true</SelfContained>
+    <PublishSingleFile>False</PublishSingleFile>
+  </PropertyGroup>
+</Project>
\ No newline at end of file
diff --git a/App/Properties/PublishProfiles/win-x86.pubxml b/App/Properties/PublishProfiles/win-x86.pubxml
new file mode 100644
index 0000000..a70c694
--- /dev/null
+++ b/App/Properties/PublishProfiles/win-x86.pubxml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+https://go.microsoft.com/fwlink/?LinkID=208121.
+-->
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup>
+    <PublishProtocol>FileSystem</PublishProtocol>
+    <Platform>x86</Platform>
+    <RuntimeIdentifier>win-x86</RuntimeIdentifier>
+    <PublishDir>bin\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier)\publish\</PublishDir>
+    <SelfContained>true</SelfContained>
+    <PublishSingleFile>False</PublishSingleFile>
+  </PropertyGroup>
+</Project>
\ No newline at end of file
diff --git a/App/Properties/launchSettings.json b/App/Properties/launchSettings.json
new file mode 100644
index 0000000..4a35a11
--- /dev/null
+++ b/App/Properties/launchSettings.json
@@ -0,0 +1,10 @@
+{
+  "profiles": {
+    "App (Package)": {
+      "commandName": "MsixPackage"
+    },
+    "App (Unpackaged)": {
+      "commandName": "Project"
+    }
+  }
+}
diff --git a/Coder.Desktop.sln b/Coder.Desktop.sln
index e25c8fa..0e73dc5 100644
--- a/Coder.Desktop.sln
+++ b/Coder.Desktop.sln
@@ -1,7 +1,7 @@
 
 Microsoft Visual Studio Solution File, Format Version 12.00
 # Visual Studio Version 17
-VisualStudioVersion = 17.12.35707.178 d17.12
+VisualStudioVersion = 17.12.35707.178
 MinimumVisualStudioVersion = 10.0.40219.1
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Vpn", "Vpn\Vpn.csproj", "{B342F896-C721-4AA5-A0F6-0BFA8EBAFACB}"
 EndProject
@@ -17,8 +17,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests.Vpn.Service", "Tests.
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CoderSdk", "CoderSdk\CoderSdk.csproj", "{A3D2B2B3-A051-46BD-A190-5487A9F24C28}"
 EndProject
-Project("{C7167F0D-BC9F-4E6E-AFE1-012C56B48DB5}") = "Package", "Package\Package.wapproj", "{C184988D-56E0-451F-B6A1-E5FE0405C80B}"
-EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "App", "App\App.csproj", "{800C7E2D-0D86-4554-9903-B879DA6FA2CE}"
 EndProject
 Global
@@ -145,46 +143,30 @@ Global
 		{A3D2B2B3-A051-46BD-A190-5487A9F24C28}.Release|x64.Build.0 = Release|Any CPU
 		{A3D2B2B3-A051-46BD-A190-5487A9F24C28}.Release|x86.ActiveCfg = Release|Any CPU
 		{A3D2B2B3-A051-46BD-A190-5487A9F24C28}.Release|x86.Build.0 = Release|Any CPU
-		{C184988D-56E0-451F-B6A1-E5FE0405C80B}.Debug|Any CPU.ActiveCfg = Debug|x64
-		{C184988D-56E0-451F-B6A1-E5FE0405C80B}.Debug|Any CPU.Build.0 = Debug|x64
-		{C184988D-56E0-451F-B6A1-E5FE0405C80B}.Debug|Any CPU.Deploy.0 = Debug|x64
-		{C184988D-56E0-451F-B6A1-E5FE0405C80B}.Debug|ARM64.ActiveCfg = Debug|ARM64
-		{C184988D-56E0-451F-B6A1-E5FE0405C80B}.Debug|ARM64.Build.0 = Debug|ARM64
-		{C184988D-56E0-451F-B6A1-E5FE0405C80B}.Debug|ARM64.Deploy.0 = Debug|ARM64
-		{C184988D-56E0-451F-B6A1-E5FE0405C80B}.Debug|x64.ActiveCfg = Debug|x64
-		{C184988D-56E0-451F-B6A1-E5FE0405C80B}.Debug|x64.Build.0 = Debug|x64
-		{C184988D-56E0-451F-B6A1-E5FE0405C80B}.Debug|x64.Deploy.0 = Debug|x64
-		{C184988D-56E0-451F-B6A1-E5FE0405C80B}.Debug|x86.ActiveCfg = Debug|x86
-		{C184988D-56E0-451F-B6A1-E5FE0405C80B}.Debug|x86.Build.0 = Debug|x86
-		{C184988D-56E0-451F-B6A1-E5FE0405C80B}.Debug|x86.Deploy.0 = Debug|x86
-		{C184988D-56E0-451F-B6A1-E5FE0405C80B}.Release|Any CPU.ActiveCfg = Release|x64
-		{C184988D-56E0-451F-B6A1-E5FE0405C80B}.Release|Any CPU.Build.0 = Release|x64
-		{C184988D-56E0-451F-B6A1-E5FE0405C80B}.Release|Any CPU.Deploy.0 = Release|x64
-		{C184988D-56E0-451F-B6A1-E5FE0405C80B}.Release|ARM64.ActiveCfg = Release|ARM64
-		{C184988D-56E0-451F-B6A1-E5FE0405C80B}.Release|ARM64.Build.0 = Release|ARM64
-		{C184988D-56E0-451F-B6A1-E5FE0405C80B}.Release|ARM64.Deploy.0 = Release|ARM64
-		{C184988D-56E0-451F-B6A1-E5FE0405C80B}.Release|x64.ActiveCfg = Release|x64
-		{C184988D-56E0-451F-B6A1-E5FE0405C80B}.Release|x64.Build.0 = Release|x64
-		{C184988D-56E0-451F-B6A1-E5FE0405C80B}.Release|x64.Deploy.0 = Release|x64
-		{C184988D-56E0-451F-B6A1-E5FE0405C80B}.Release|x86.ActiveCfg = Release|x86
-		{C184988D-56E0-451F-B6A1-E5FE0405C80B}.Release|x86.Build.0 = Release|x86
-		{C184988D-56E0-451F-B6A1-E5FE0405C80B}.Release|x86.Deploy.0 = Release|x86
 		{800C7E2D-0D86-4554-9903-B879DA6FA2CE}.Debug|Any CPU.ActiveCfg = Debug|x64
 		{800C7E2D-0D86-4554-9903-B879DA6FA2CE}.Debug|Any CPU.Build.0 = Debug|x64
+		{800C7E2D-0D86-4554-9903-B879DA6FA2CE}.Debug|Any CPU.Deploy.0 = Debug|x64
 		{800C7E2D-0D86-4554-9903-B879DA6FA2CE}.Debug|ARM64.ActiveCfg = Debug|ARM64
 		{800C7E2D-0D86-4554-9903-B879DA6FA2CE}.Debug|ARM64.Build.0 = Debug|ARM64
+		{800C7E2D-0D86-4554-9903-B879DA6FA2CE}.Debug|ARM64.Deploy.0 = Debug|ARM64
 		{800C7E2D-0D86-4554-9903-B879DA6FA2CE}.Debug|x64.ActiveCfg = Debug|x64
 		{800C7E2D-0D86-4554-9903-B879DA6FA2CE}.Debug|x64.Build.0 = Debug|x64
+		{800C7E2D-0D86-4554-9903-B879DA6FA2CE}.Debug|x64.Deploy.0 = Debug|x64
 		{800C7E2D-0D86-4554-9903-B879DA6FA2CE}.Debug|x86.ActiveCfg = Debug|x86
 		{800C7E2D-0D86-4554-9903-B879DA6FA2CE}.Debug|x86.Build.0 = Debug|x86
+		{800C7E2D-0D86-4554-9903-B879DA6FA2CE}.Debug|x86.Deploy.0 = Debug|x86
 		{800C7E2D-0D86-4554-9903-B879DA6FA2CE}.Release|Any CPU.ActiveCfg = Release|x64
 		{800C7E2D-0D86-4554-9903-B879DA6FA2CE}.Release|Any CPU.Build.0 = Release|x64
+		{800C7E2D-0D86-4554-9903-B879DA6FA2CE}.Release|Any CPU.Deploy.0 = Release|x64
 		{800C7E2D-0D86-4554-9903-B879DA6FA2CE}.Release|ARM64.ActiveCfg = Release|ARM64
 		{800C7E2D-0D86-4554-9903-B879DA6FA2CE}.Release|ARM64.Build.0 = Release|ARM64
+		{800C7E2D-0D86-4554-9903-B879DA6FA2CE}.Release|ARM64.Deploy.0 = Release|ARM64
 		{800C7E2D-0D86-4554-9903-B879DA6FA2CE}.Release|x64.ActiveCfg = Release|x64
 		{800C7E2D-0D86-4554-9903-B879DA6FA2CE}.Release|x64.Build.0 = Release|x64
+		{800C7E2D-0D86-4554-9903-B879DA6FA2CE}.Release|x64.Deploy.0 = Release|x64
 		{800C7E2D-0D86-4554-9903-B879DA6FA2CE}.Release|x86.ActiveCfg = Release|x86
 		{800C7E2D-0D86-4554-9903-B879DA6FA2CE}.Release|x86.Build.0 = Release|x86
+		{800C7E2D-0D86-4554-9903-B879DA6FA2CE}.Release|x86.Deploy.0 = Release|x86
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
diff --git a/Tests.Vpn/Utilities/TaskUtilitiesTest.cs b/Tests.Vpn/Utilities/TaskUtilitiesTest.cs
index a6a4583..47f258f 100644
--- a/Tests.Vpn/Utilities/TaskUtilitiesTest.cs
+++ b/Tests.Vpn/Utilities/TaskUtilitiesTest.cs
@@ -6,7 +6,6 @@ namespace Coder.Desktop.Tests.Vpn.Utilities;
 public class TaskUtilitiesTest
 {
     [Test(Description = "CancellableWhenAll with no tasks should complete immediately")]
-    [Timeout(30_000)]
     public void CancellableWhenAll_NoTasks()
     {
         var task = TaskUtilities.CancellableWhenAll(new CancellationTokenSource());
@@ -14,7 +13,6 @@ public void CancellableWhenAll_NoTasks()
     }
 
     [Test(Description = "CancellableWhenAll with a single task should complete")]
-    [Timeout(30_000)]
     public async Task CancellableWhenAll_SingleTask()
     {
         var innerTask = new TaskCompletionSource();
@@ -25,7 +23,6 @@ public async Task CancellableWhenAll_SingleTask()
     }
 
     [Test(Description = "CancellableWhenAll with a single task that faults should propagate the exception")]
-    [Timeout(30_000)]
     public void CancellableWhenAll_SingleTaskFault()
     {
         var cts = new CancellationTokenSource();
@@ -38,7 +35,6 @@ public void CancellableWhenAll_SingleTaskFault()
     }
 
     [Test(Description = "CancellableWhenAll with a single task that is canceled should propagate the cancellation")]
-    [Timeout(30_000)]
     public void CancellableWhenAll_SingleTaskCanceled()
     {
         var cts = new CancellationTokenSource();
@@ -51,7 +47,6 @@ public void CancellableWhenAll_SingleTaskCanceled()
     }
 
     [Test(Description = "CancellableWhenAll with multiple tasks should complete when all tasks are completed")]
-    [Timeout(30_000)]
     public async Task CancellableWhenAll_MultipleTasks()
     {
         var cts = new CancellationTokenSource();
@@ -74,7 +69,6 @@ public async Task CancellableWhenAll_MultipleTasks()
     }
 
     [Test(Description = "CancellableWhenAll with multiple tasks that fault should propagate the first exception only")]
-    [Timeout(30_000)]
     public async Task CancellableWhenAll_MultipleTasksFault()
     {
         var cts = new CancellationTokenSource();
@@ -95,7 +89,6 @@ public async Task CancellableWhenAll_MultipleTasksFault()
     }
 
     [Test(Description = "CancellableWhenAll with an exception and a cancellation should propagate the first thing")]
-    [Timeout(30_000)]
     public async Task CancellableWhenAll_MultipleTasksFaultAndCanceled()
     {
         var cts = new CancellationTokenSource();
@@ -118,7 +111,6 @@ public async Task CancellableWhenAll_MultipleTasksFaultAndCanceled()
     }
 
     [Test(Description = "CancellableWhenAll with a cancellation and an exception should propagate the first thing")]
-    [Timeout(30_000)]
     public async Task CancellableWhenAll_MultipleTasksCanceledAndFault()
     {
         var cts = new CancellationTokenSource();

From 95f8a785a822a96b532988be5633c82d72ee30fb Mon Sep 17 00:00:00 2001
From: Dean Sheather <dean@deansheather.com>
Date: Tue, 28 Jan 2025 16:30:48 +1100
Subject: [PATCH 6/7] fixup! chore: add GitHub actions

---
 App/App.csproj | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/App/App.csproj b/App/App.csproj
index 0c014e1..cae1812 100644
--- a/App/App.csproj
+++ b/App/App.csproj
@@ -73,8 +73,13 @@
 
     <!-- Publish Properties -->
     <PropertyGroup>
+        <!--
+        This does not work in CI at the moment, so we need to set it to false
+        Error: C:\Program Files\dotnet\sdk\9.0.102\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.Publish.targets(400,5): error NETSDK1094: Unable to optimize assemblies for performance: a valid runtime package was not found. Either set the PublishReadyToRun property to false, or use a supported runtime identifier when publishing. When targeting .NET 6 or higher, make sure to restore packages with the PublishReadyToRun property set to true. [D:\a\coder-desktop-windows\coder-desktop-windows\App\App.csproj]
         <PublishReadyToRun Condition="'$(Configuration)' == 'Debug'">False</PublishReadyToRun>
         <PublishReadyToRun Condition="'$(Configuration)' != 'Debug'">True</PublishReadyToRun>
+        -->
+        <PublishReadyToRun>False</PublishReadyToRun>
         <PublishTrimmed Condition="'$(Configuration)' == 'Debug'">False</PublishTrimmed>
         <PublishTrimmed Condition="'$(Configuration)' != 'Debug'">True</PublishTrimmed>
     </PropertyGroup>

From 3f1ef4c0d13bfe44596b1dfdb79ec08fc5cfd6b1 Mon Sep 17 00:00:00 2001
From: Dean Sheather <dean@deansheather.com>
Date: Wed, 29 Jan 2025 18:41:03 +1100
Subject: [PATCH 7/7] fixup! chore: add GitHub actions

---
 App/Package.appxmanifest | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/App/Package.appxmanifest b/App/Package.appxmanifest
index 0c14c30..89a8f87 100644
--- a/App/Package.appxmanifest
+++ b/App/Package.appxmanifest
@@ -9,7 +9,7 @@
 
     <Identity
         Name="925b49fc-4648-4967-b4e6-b5473061ee62"
-        Publisher="CN=dean"
+        Publisher="CN=Coder Technologies Inc."
         Version="1.0.0.0"/>
 
     <mp:PhoneIdentity PhoneProductId="925b49fc-4648-4967-b4e6-b5473061ee62"
@@ -17,7 +17,7 @@
 
     <Properties>
         <DisplayName>Coder Desktop (Package)</DisplayName>
-        <PublisherDisplayName>dean</PublisherDisplayName>
+        <PublisherDisplayName>Coder Technologies Inc.</PublisherDisplayName>
         <Logo>Images\StoreLogo.png</Logo>
     </Properties>