await certificate from ssl.com Reviewed-on: #4 Co-authored-by: Aleksandr Neychev <alexnejchev73@gmail.com>
This commit was merged in pull request #4.
This commit is contained in:
@@ -78,8 +78,8 @@ WPF took goes back to the system. The price is a cold start of half a second or
|
||||
the next time the window is asked for.
|
||||
|
||||
`CursorLang.Core.dll` is what both hold in common: the models, the settings file,
|
||||
the layout tracking, the updates. No UI, and it must stay that way — whatever lands
|
||||
there lands in the background process.
|
||||
the layout tracking. No UI, and it must stay that way — whatever lands there lands
|
||||
in the background process.
|
||||
|
||||
The connection between the two is `settings.json` and nothing else. The window
|
||||
writes it — whole, into a temporary file moved into place in one step — and then
|
||||
@@ -162,57 +162,22 @@ after a switch of the mode, and that is a question asked once.
|
||||
|
||||
## Updates
|
||||
|
||||
The check runs when the settings window is opened, not when the machine is
|
||||
switched on: the background half no longer goes to the network at all, and there
|
||||
would be nothing in it to show the answer. The setting in the window says as much.
|
||||
The Store updates the app, and the app itself does nothing about it: there is no
|
||||
updates section in the window, no request to the network and no code for either.
|
||||
|
||||
The app looks for new versions among the releases of its own repository. A
|
||||
release counts when its tag is a plain version — `v1.2.3` or `1.2.3` — and an
|
||||
MSIX package is attached to it. A tag with anything else in it, `v1.2.3-beta`
|
||||
among them, is passed over: a pre-release version is asked for on purpose, not
|
||||
offered by the app.
|
||||
That is not a matter of taste but of what a signature costs. Windows installs an
|
||||
MSIX only when it trusts the signature on it, and a publicly trusted code signing
|
||||
certificate has turned out to be beyond reach — the certificate authorities that
|
||||
sell them will not issue one here, and those that would keep the key in a cloud
|
||||
HSM will not either, while a certificate on a USB token cannot be shipped in.
|
||||
Without a signature a package installs nowhere but a machine in developer mode,
|
||||
so there is nothing to hand out from a release and nothing for an updater to
|
||||
find. The Store signs the package with its own certificate and updates the app
|
||||
by itself, which leaves the whole question to it.
|
||||
|
||||
Out of the attached files the `.msixbundle` is preferred — it carries both
|
||||
architectures. Failing that, the package whose name holds the architecture of
|
||||
this machine is taken: `CursorLang-1.2.3.0-x64.msix`. Those are the names
|
||||
`build-msix.ps1` produces, so a release is made by attaching what it built.
|
||||
|
||||
The package is downloaded to the temp folder and handed to the Windows app
|
||||
installer: it shows the publisher, asks for a confirmation and replaces the
|
||||
installed version. Windows checks the signature, so the package attached to a
|
||||
release has to be signed — an unsigned one installs nowhere but a machine in
|
||||
developer mode. The running app keeps working off the old files until it is
|
||||
restarted.
|
||||
|
||||
An app installed from the Store has no updates section at all: the Store
|
||||
updates it, and a package from the side is something Windows would not accept
|
||||
over it anyway.
|
||||
|
||||
The app asks about releases every time the settings window appears, and the
|
||||
answer stays in the section as it is — an unreachable network included. Opening
|
||||
that window is a deliberate act, rare enough for a request to cost nothing, so
|
||||
nothing is remembered between openings and there is nothing to turn off. The
|
||||
button next to the version asks the same question again on demand.
|
||||
|
||||
Where the releases are looked for is set in `UpdateOptions` — the repository
|
||||
belongs to whoever publishes the app, not to the user, so the values live in the
|
||||
build rather than in `settings.json`:
|
||||
|
||||
```csharp
|
||||
services.AddSingleton(new UpdateOptions
|
||||
{
|
||||
ServiceUri = new Uri("https://git.alrakis.kz/"), // the Gitea server itself
|
||||
Project = "alrakis/cursor-lang",
|
||||
});
|
||||
```
|
||||
|
||||
The releases come from Gitea, and its API sits on the server itself: the address
|
||||
is the one the repository is opened at in a browser, and the app asks
|
||||
`/api/v1/repos/{owner}/{repo}/releases` under it.
|
||||
|
||||
A closed repository needs a token. It is read from the `CURSORLANG_UPDATE_TOKEN`
|
||||
environment variable rather than kept in the source: a secret built into the app
|
||||
is a secret handed to everyone who got the app.
|
||||
The version of the running app is in the title of the settings window:
|
||||
`CursorLang 1.2.3 — Settings`. It is nowhere else in the interface, and it is
|
||||
there so that a bug report can name it.
|
||||
|
||||
## Tests
|
||||
|
||||
@@ -257,11 +222,10 @@ running agent and fails if any part of the WPF renderer is in it.
|
||||
|
||||
The pipelines live in `.gitea/workflows` and run on Gitea Actions. A pull
|
||||
request into `master` is built and tested; a tag of the form `v1.2.3` is built,
|
||||
tested, packed into an MSIX and published as a release with the packages
|
||||
attached. The version is taken from the tag alone — a tag shaped any other way
|
||||
stops the run right at the start. The package version ends up as `1.2.3.0`: the
|
||||
Store takes four numbers and keeps the last one for itself, so the tag has no
|
||||
say in it.
|
||||
tested and packed into an MSIX, which is left in the artifacts of the run. The
|
||||
version is taken from the tag alone — a tag shaped any other way stops the run
|
||||
right at the start. The package version ends up as `1.2.3.0`: the Store takes
|
||||
four numbers and keeps the last one for itself, so the tag has no say in it.
|
||||
|
||||
Both pipelines ask for a Windows runner labelled `windows-x64` with the .NET 10
|
||||
SDK and git-lfs on it. The checkout pulls LFS files: without them the icon is a
|
||||
@@ -271,9 +235,18 @@ need a desktop of their own — the end-to-end ones, and those that ask for the
|
||||
foreground window or the caret — skip themselves on a runner that lives as a
|
||||
service in session 0, where there is no desktop to show a window on.
|
||||
|
||||
The package the release carries goes to Partner Center as it is. The identity
|
||||
comes from repository variables and falls back to the defaults of the script when
|
||||
unset: `MSIX_IDENTITY_NAME`, `MSIX_PUBLISHER` and `MSIX_PUBLISHER_DISPLAY_NAME`.
|
||||
The package goes to the Store and nowhere else: it is unsigned, and Partner
|
||||
Center puts its own signature on it. So the run leaves it in the artifacts under
|
||||
the name `msix-1.2.3.0`, where whoever uploads it picks it up by hand; nothing
|
||||
is attached to the release, which Gitea makes for the tag by itself and which
|
||||
carries the tag alone. An unsigned package hanging off a release would look like
|
||||
something to install and install nowhere — see the section on updates.
|
||||
|
||||
The identity comes from repository variables and falls back to the defaults of
|
||||
the script when unset: `MSIX_IDENTITY_NAME`, `MSIX_PUBLISHER` and
|
||||
`MSIX_PUBLISHER_DISPLAY_NAME`. Together the identity and the publisher decide
|
||||
the family name of the package, so both have to stay as they are from version to
|
||||
version, or the Store takes the next one for a different app.
|
||||
|
||||
## Building the MSIX package
|
||||
|
||||
@@ -286,17 +259,22 @@ to submitting for certification — is written up in
|
||||
# One-off: draw the logos and the exe icon (already committed, rerun after edits)
|
||||
powershell -File Packaging\New-Assets.ps1
|
||||
|
||||
# A check on your own machine: your architecture alone
|
||||
powershell -File Packaging\build-msix.ps1 -Architectures x64
|
||||
# A check on your own machine
|
||||
powershell -File Packaging\build-msix.ps1
|
||||
|
||||
# For Partner Center — the identity is the one reserved there
|
||||
powershell -File Packaging\build-msix.ps1 -Version 1.0.1.0 `
|
||||
-IdentityName 12345AleksandrNeychev.CursorLang -Publisher "CN=ABCD1234-..."
|
||||
-IdentityName 12345AleksandrNeichev.CursorLang -Publisher "CN=ABCD1234-..."
|
||||
```
|
||||
|
||||
The result is `artifacts\packages\CursorLang-<version>.msixbundle` covering x64
|
||||
and arm64; next to it lie the packages of single architectures. Upload the bundle
|
||||
to Partner Center as it is.
|
||||
The result is `artifacts\packages\CursorLang-<version>-x64.msix`. Upload it to
|
||||
Partner Center as it is.
|
||||
|
||||
Only x64 is built. An arm64 build would double the size of every release for the
|
||||
sake of machines that run the x64 one under emulation anyway.
|
||||
|
||||
Nothing here is signed: the Store signs the package itself, and for installing it
|
||||
on this machine the layout is registered instead — see `-Install` below.
|
||||
|
||||
The app ships with its own copy of .NET: Windows does not include .NET 10, and
|
||||
MSIX cannot install a runtime as a package dependency.
|
||||
@@ -304,7 +282,7 @@ MSIX cannot install a runtime as a package dependency.
|
||||
To see the package working on this machine, build it with `-Install`:
|
||||
|
||||
```powershell
|
||||
pwsh -File Packaging\build-msix.ps1 -Architectures x64 -Install
|
||||
pwsh -File Packaging\build-msix.ps1 -Install
|
||||
```
|
||||
|
||||
The application then shows up in the Start menu like any installed one. What
|
||||
@@ -318,5 +296,41 @@ the Store answers to the same name and is left alone.
|
||||
To remove it by hand:
|
||||
|
||||
```powershell
|
||||
Remove-AppxPackage (Get-AppxPackage -Name AleksandrNeychev.CursorLang).PackageFullName
|
||||
Remove-AppxPackage (Get-AppxPackage -Name AleksandrNeichev.CursorLang).PackageFullName
|
||||
```
|
||||
|
||||
## Building the installer
|
||||
|
||||
The same application also comes as an ordinary MSI, for handing round outside the
|
||||
Store — to try it out before the Store has passed judgement, or if it never does.
|
||||
Nothing has to be installed beyond the .NET SDK: WiX comes from a NuGet package,
|
||||
the same way `makeappx` does.
|
||||
|
||||
```powershell
|
||||
# Build it and run it afterwards, to see what a user sees
|
||||
pwsh -File Packaging\build-installer.ps1 -Install
|
||||
|
||||
# Everything a release needs
|
||||
pwsh -File Packaging\build-installer.ps1 -Version 1.0.1
|
||||
```
|
||||
|
||||
The result is `artifacts\installers\CursorLang-<version>-x64.msi`. Everything
|
||||
travels inside the .msi; there is no cabinet to send alongside it.
|
||||
|
||||
It installs for the current user alone, into `%LOCALAPPDATA%\Programs\CursorLang`,
|
||||
and so asks for no administrator rights and no consent dialog. Uninstalling goes
|
||||
through Apps in Settings like any other program and takes the startup entry with
|
||||
it — otherwise Windows would go on listing an application that is no longer there.
|
||||
|
||||
Nobody signs the installer, so Windows warns about an unknown publisher and the
|
||||
user has to insist. Buying a certificate would not silence it at once either:
|
||||
SmartScreen goes by reputation, and a fresh certificate has none until enough
|
||||
people have installed the application.
|
||||
|
||||
Two things about the WiX project are worth knowing before touching it. It pins
|
||||
WiX 5 rather than the current 7: from version 6 the toolset asks every build to
|
||||
accept the Open Source Maintenance Fee licence — free below $10,000 of yearly
|
||||
revenue, but a decision for a person rather than for a build script. And it turns
|
||||
off three ICE validation rules: MSI still assumes an installation for the whole
|
||||
machine, and installing into the user's own profile trips rules that describe
|
||||
exactly what was intended here.
|
||||
|
||||
Reference in New Issue
Block a user