Skip to content

Commit 1c5e4d9

Browse files
authored
feat: installer and uninstaller (#32)
Adds new package `Installer` which uses [WixSharp](https://github.com/oleg-shilo/wixsharp) to build WixToolset installers for WIndows. We build two installers: - `Coder Desktop (Core)`, which installs the app files, service files, VPN files (`wintun.dll`), creates a system service and an app shortcut in the start menu - `Coder Desktop`, which will netinstall the .NET runtime if it's not installed and then chain install `Coder Desktop (Core) We will only be shipping the `Coder Desktop` installer, which contains the Core installer. The chained Core installation doesn't show up in Settings > Apps. ![image](https://github.com/user-attachments/assets/0dbcc2fb-8618-4f7e-8973-8a8226c1f3e0) ![image](https://github.com/user-attachments/assets/8a33ac0d-f2aa-4dfe-a0df-efd7da3d1d22) ![image](https://github.com/user-attachments/assets/cc5bfc38-2587-4f83-9586-c21e3d663dda) ![image](https://github.com/user-attachments/assets/6e15ed0e-273c-4dfc-99ce-22b35ef5668f) Follow-up PR will integrate to CI and add authenticode signing to the installer binaries. Closes #11 Closes #30
1 parent e1ef774 commit 1c5e4d9

38 files changed

+1801
-266
lines changed

.github/workflows/release.yaml

Lines changed: 69 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,18 @@ on:
44
push:
55
tags:
66
- '*'
7+
workflow_dispatch:
8+
inputs:
9+
version:
10+
description: 'Version number (e.g. v1.2.3)'
11+
required: true
12+
default: 'v1.2.3'
713

814
permissions:
915
contents: write
1016

1117
jobs:
12-
build:
18+
release:
1319
runs-on: windows-latest
1420

1521
steps:
@@ -20,42 +26,83 @@ jobs:
2026
with:
2127
dotnet-version: '8.0.x'
2228

29+
# Necessary for signing Windows binaries.
30+
- name: Setup Java
31+
uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4.7.0
32+
with:
33+
distribution: "zulu"
34+
java-version: "11.0"
35+
2336
- name: Get version from tag
2437
id: version
2538
shell: pwsh
2639
run: |
27-
$tag = $env:GITHUB_REF -replace 'refs/tags/',''
40+
$ErrorActionPreference = "Stop"
41+
if ($env:INPUT_VERSION) {
42+
$tag = $env:INPUT_VERSION
43+
} else {
44+
$tag = $env:GITHUB_REF -replace 'refs/tags/',''
45+
}
2846
if ($tag -notmatch '^v\d+\.\d+\.\d+$') {
29-
throw "Tag must be in format v1.2.3"
47+
throw "Version must be in format v1.2.3, got $tag"
3048
}
3149
$version = $tag -replace '^v',''
32-
$assemblyVersion = "$version.0"
33-
echo "VERSION=$version" >> $env:GITHUB_OUTPUT
34-
echo "ASSEMBLY_VERSION=$assemblyVersion" >> $env:GITHUB_OUTPUT
50+
$assemblyVersion = "$($version).0"
51+
Add-Content -Path $env:GITHUB_OUTPUT -Value "VERSION=$version"
52+
Add-Content -Path $env:GITHUB_OUTPUT -Value "ASSEMBLY_VERSION=$assemblyVersion"
53+
Write-Host "Version: $version"
54+
Write-Host "Assembly version: $assemblyVersion"
55+
env:
56+
INPUT_VERSION: ${{ inputs.version }}
3557

36-
- name: Build and publish x64
37-
run: |
38-
dotnet publish src/App/App.csproj -c Release -r win-x64 -p:Version=${{ steps.version.outputs.ASSEMBLY_VERSION }} -o publish/x64
39-
dotnet publish src/Vpn.Service/Vpn.Service.csproj -c Release -r win-x64 -p:Version=${{ steps.version.outputs.ASSEMBLY_VERSION }} -o publish/x64
58+
# Setup GCloud for signing Windows binaries.
59+
- name: Authenticate to Google Cloud
60+
id: gcloud_auth
61+
uses: google-github-actions/auth@71f986410dfbc7added4569d411d040a91dc6935 # v2.1.8
62+
with:
63+
workload_identity_provider: ${{ secrets.GCP_CODE_SIGNING_WORKLOAD_ID_PROVIDER }}
64+
service_account: ${{ secrets.GCP_CODE_SIGNING_SERVICE_ACCOUNT }}
65+
token_format: "access_token"
4066

41-
- name: Build and publish arm64
42-
run: |
43-
dotnet publish src/App/App.csproj -c Release -r win-arm64 -p:Version=${{ steps.version.outputs.ASSEMBLY_VERSION }} -o publish/arm64
44-
dotnet publish src/Vpn.Service/Vpn.Service.csproj -c Release -r win-arm64 -p:Version=${{ steps.version.outputs.ASSEMBLY_VERSION }} -o publish/arm64
67+
- name: Setup GCloud SDK
68+
uses: google-github-actions/setup-gcloud@77e7a554d41e2ee56fc945c52dfd3f33d12def9a # v2.1.4
4569

46-
- name: Create ZIP archives
70+
- name: scripts/Release.ps1
71+
id: release
4772
shell: pwsh
4873
run: |
49-
Compress-Archive -Path "publish/x64/*" -DestinationPath "./publish/CoderDesktop-${{ steps.version.outputs.VERSION }}-x64.zip"
50-
Compress-Archive -Path "publish/arm64/*" -DestinationPath "./publish/CoderDesktop-${{ steps.version.outputs.VERSION }}-arm64.zip"
74+
$ErrorActionPreference = "Stop"
5175
52-
- name: Create Release
53-
uses: softprops/action-gh-release@v1
76+
$env:EV_CERTIFICATE_PATH = Join-Path $env:TEMP "ev_cert.pem"
77+
$env:JSIGN_PATH = Join-Path $env:TEMP "jsign-6.0.jar"
78+
Invoke-WebRequest -Uri "https://github.com/ebourg/jsign/releases/download/6.0/jsign-6.0.jar" -OutFile $env:JSIGN_PATH
79+
80+
& ./scripts/Release.ps1 `
81+
-version ${{ steps.version.outputs.VERSION }} `
82+
-assemblyVersion ${{ steps.version.outputs.ASSEMBLY_VERSION }}
83+
if ($LASTEXITCODE -ne 0) { throw "Failed to publish" }
84+
env:
85+
EV_SIGNING_CERT: ${{ secrets.EV_SIGNING_CERT }}
86+
EV_KEYSTORE: ${{ secrets.EV_KEYSTORE }}
87+
EV_KEY: ${{ secrets.EV_KEY }}
88+
EV_TSA_URL: ${{ secrets.EV_TSA_URL }}
89+
GCLOUD_ACCESS_TOKEN: ${{ steps.gcloud_auth.outputs.access_token }}
90+
91+
- name: Upload artifact
92+
uses: actions/upload-artifact@v4
93+
with:
94+
name: publish
95+
path: .\publish\
96+
97+
- name: Create release
98+
uses: softprops/action-gh-release@v2
99+
if: startsWith(github.ref, 'refs/tags/')
54100
with:
55-
files: |
56-
./publish/CoderDesktop-${{ steps.version.outputs.VERSION }}-x64.zip
57-
./publish/CoderDesktop-${{ steps.version.outputs.VERSION }}-arm64.zip
58101
name: Release ${{ steps.version.outputs.VERSION }}
59102
generate_release_notes: true
103+
# We currently only release the bootstrappers, not the MSIs.
104+
files: |
105+
${{ steps.release.outputs.X64_OUTPUT_PATH }}
106+
${{ steps.release.outputs.ARM64_OUTPUT_PATH }}
60107
env:
61108
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

App/App.csproj

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,22 @@
1717
<LangVersion>preview</LangVersion>
1818
<!-- We have our own implementation of main with exception handling -->
1919
<DefineConstants>DISABLE_XAML_GENERATED_MAIN</DefineConstants>
20+
21+
<AssemblyName>Coder Desktop</AssemblyName>
22+
<ApplicationIcon>coder.ico</ApplicationIcon>
2023
</PropertyGroup>
2124

2225
<PropertyGroup Condition="$(Configuration) == 'Release'">
23-
<PublishTrimmed>true</PublishTrimmed>
24-
<TrimMode>CopyUsed</TrimMode>
26+
<PublishTrimmed>false</PublishTrimmed>
27+
<!-- <TrimMode>CopyUsed</TrimMode> -->
2528
<PublishReadyToRun>true</PublishReadyToRun>
26-
<SelfContained>true</SelfContained>
29+
<SelfContained>false</SelfContained>
2730
</PropertyGroup>
2831

32+
<ItemGroup>
33+
<Content Include="coder.ico" />
34+
</ItemGroup>
35+
2936
<ItemGroup>
3037
<Manifest Include="$(ApplicationManifest)" />
3138
</ItemGroup>

App/Services/CredentialManager.cs

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -239,11 +239,9 @@ private struct CREDENTIAL
239239
public int Flags;
240240
public int Type;
241241

242-
[MarshalAs(UnmanagedType.LPWStr)]
243-
public string TargetName;
242+
[MarshalAs(UnmanagedType.LPWStr)] public string TargetName;
244243

245-
[MarshalAs(UnmanagedType.LPWStr)]
246-
public string Comment;
244+
[MarshalAs(UnmanagedType.LPWStr)] public string Comment;
247245

248246
public long LastWritten;
249247
public int CredentialBlobSize;
@@ -252,11 +250,9 @@ private struct CREDENTIAL
252250
public int AttributeCount;
253251
public IntPtr Attributes;
254252

255-
[MarshalAs(UnmanagedType.LPWStr)]
256-
public string TargetAlias;
253+
[MarshalAs(UnmanagedType.LPWStr)] public string TargetAlias;
257254

258-
[MarshalAs(UnmanagedType.LPWStr)]
259-
public string UserName;
255+
[MarshalAs(UnmanagedType.LPWStr)] public string UserName;
260256
}
261257
}
262258
}

App/ViewModels/SignInViewModel.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,9 @@ public partial class SignInViewModel : ObservableObject
3333
[NotifyPropertyChangedFor(nameof(ApiTokenError))]
3434
public partial bool ApiTokenTouched { get; set; } = false;
3535

36-
[ObservableProperty]
37-
public partial string? SignInError { get; set; } = null;
36+
[ObservableProperty] public partial string? SignInError { get; set; } = null;
3837

39-
[ObservableProperty]
40-
public partial bool SignInLoading { get; set; } = false;
38+
[ObservableProperty] public partial bool SignInLoading { get; set; } = false;
4139

4240
public string? CoderUrlError => CoderUrlTouched ? _coderUrlError : null;
4341

App/ViewModels/TrayWindowDisconnectedViewModel.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,7 @@ public partial class TrayWindowDisconnectedViewModel : ObservableObject
1010
{
1111
private readonly IRpcController _rpcController;
1212

13-
[ObservableProperty]
14-
public partial bool ReconnectButtonEnabled { get; set; } = true;
13+
[ObservableProperty] public partial bool ReconnectButtonEnabled { get; set; } = true;
1514

1615
public TrayWindowDisconnectedViewModel(IRpcController rpcController)
1716
{

App/ViewModels/TrayWindowViewModel.cs

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,12 @@ public partial class TrayWindowViewModel : ObservableObject
2323

2424
private DispatcherQueue? _dispatcherQueue;
2525

26-
[ObservableProperty]
27-
public partial VpnLifecycle VpnLifecycle { get; set; } = VpnLifecycle.Unknown;
26+
[ObservableProperty] public partial VpnLifecycle VpnLifecycle { get; set; } = VpnLifecycle.Unknown;
2827

2928
// This is a separate property because we need the switch to be 2-way.
30-
[ObservableProperty]
31-
public partial bool VpnSwitchActive { get; set; } = false;
29+
[ObservableProperty] public partial bool VpnSwitchActive { get; set; } = false;
3230

33-
[ObservableProperty]
34-
public partial string? VpnFailedMessage { get; set; } = null;
31+
[ObservableProperty] public partial string? VpnFailedMessage { get; set; } = null;
3532

3633
[ObservableProperty]
3734
[NotifyPropertyChangedFor(nameof(NoAgents))]
@@ -49,8 +46,7 @@ public partial class TrayWindowViewModel : ObservableObject
4946

5047
public IEnumerable<AgentViewModel> VisibleAgents => ShowAllAgents ? Agents : Agents.Take(MaxAgents);
5148

52-
[ObservableProperty]
53-
public partial string DashboardUrl { get; set; } = "https://coder.com";
49+
[ObservableProperty] public partial string DashboardUrl { get; set; } = "https://coder.com";
5450

5551
public TrayWindowViewModel(IRpcController rpcController, ICredentialManager credentialManager)
5652
{

App/coder.ico

422 KB
Binary file not shown.

App/packages.lock.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -112,17 +112,17 @@
112112
"System.Collections.Immutable": "9.0.0"
113113
}
114114
},
115-
"codersdk": {
115+
"Coder.Desktop.CoderSdk": {
116116
"type": "Project"
117117
},
118-
"vpn": {
118+
"Coder.Desktop.Vpn": {
119119
"type": "Project",
120120
"dependencies": {
121-
"System.IO.Pipelines": "[9.0.1, )",
122-
"Vpn.Proto": "[1.0.0, )"
121+
"Coder.Desktop.Vpn.Proto": "[1.0.0, )",
122+
"System.IO.Pipelines": "[9.0.1, )"
123123
}
124124
},
125-
"vpn.proto": {
125+
"Coder.Desktop.Vpn.Proto": {
126126
"type": "Project",
127127
"dependencies": {
128128
"Google.Protobuf": "[3.29.3, )"

Coder.Desktop.sln

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "App", "App\App.csproj", "{8
2121
EndProject
2222
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Vpn.DebugClient", "Vpn.DebugClient\Vpn.DebugClient.csproj", "{1BBFDF88-B25F-4C07-99C2-30DA384DB730}"
2323
EndProject
24+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Installer", "Installer\Installer.csproj", "{39F5B55A-09D8-477D-A3FA-ADAC29C52605}"
25+
EndProject
2426
Global
2527
GlobalSection(SolutionConfigurationPlatforms) = preSolution
2628
Debug|Any CPU = Debug|Any CPU
@@ -185,6 +187,22 @@ Global
185187
{1BBFDF88-B25F-4C07-99C2-30DA384DB730}.Release|x64.Build.0 = Release|Any CPU
186188
{1BBFDF88-B25F-4C07-99C2-30DA384DB730}.Release|x86.ActiveCfg = Release|Any CPU
187189
{1BBFDF88-B25F-4C07-99C2-30DA384DB730}.Release|x86.Build.0 = Release|Any CPU
190+
{39F5B55A-09D8-477D-A3FA-ADAC29C52605}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
191+
{39F5B55A-09D8-477D-A3FA-ADAC29C52605}.Debug|Any CPU.Build.0 = Debug|Any CPU
192+
{39F5B55A-09D8-477D-A3FA-ADAC29C52605}.Debug|ARM64.ActiveCfg = Debug|Any CPU
193+
{39F5B55A-09D8-477D-A3FA-ADAC29C52605}.Debug|ARM64.Build.0 = Debug|Any CPU
194+
{39F5B55A-09D8-477D-A3FA-ADAC29C52605}.Debug|x64.ActiveCfg = Debug|Any CPU
195+
{39F5B55A-09D8-477D-A3FA-ADAC29C52605}.Debug|x64.Build.0 = Debug|Any CPU
196+
{39F5B55A-09D8-477D-A3FA-ADAC29C52605}.Debug|x86.ActiveCfg = Debug|Any CPU
197+
{39F5B55A-09D8-477D-A3FA-ADAC29C52605}.Debug|x86.Build.0 = Debug|Any CPU
198+
{39F5B55A-09D8-477D-A3FA-ADAC29C52605}.Release|Any CPU.ActiveCfg = Release|Any CPU
199+
{39F5B55A-09D8-477D-A3FA-ADAC29C52605}.Release|Any CPU.Build.0 = Release|Any CPU
200+
{39F5B55A-09D8-477D-A3FA-ADAC29C52605}.Release|ARM64.ActiveCfg = Release|Any CPU
201+
{39F5B55A-09D8-477D-A3FA-ADAC29C52605}.Release|ARM64.Build.0 = Release|Any CPU
202+
{39F5B55A-09D8-477D-A3FA-ADAC29C52605}.Release|x64.ActiveCfg = Release|Any CPU
203+
{39F5B55A-09D8-477D-A3FA-ADAC29C52605}.Release|x64.Build.0 = Release|Any CPU
204+
{39F5B55A-09D8-477D-A3FA-ADAC29C52605}.Release|x86.ActiveCfg = Release|Any CPU
205+
{39F5B55A-09D8-477D-A3FA-ADAC29C52605}.Release|x86.Build.0 = Release|Any CPU
188206
EndGlobalSection
189207
GlobalSection(SolutionProperties) = preSolution
190208
HideSolutionNode = FALSE

CoderSdk/CoderSdk.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22
<PropertyGroup>
3+
<AssemblyName>Coder.Desktop.CoderSdk</AssemblyName>
34
<RootNamespace>Coder.Desktop.CoderSdk</RootNamespace>
45
<TargetFramework>net8.0</TargetFramework>
56
<ImplicitUsings>enable</ImplicitUsings>

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

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

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


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy