Configure desktop update feed
This commit is contained in:
@@ -5,6 +5,7 @@ set( APPLICATION_VENDOR "QSfera" )
|
|||||||
set( APPLICATION_ICON_NAME "qsfera" )
|
set( APPLICATION_ICON_NAME "qsfera" )
|
||||||
set( APPLICATION_REV_DOMAIN "eu.qsfera.desktop" )
|
set( APPLICATION_REV_DOMAIN "eu.qsfera.desktop" )
|
||||||
set( APPLICATION_DEFAULT_SERVER_URL "https://qsfera.kusoft.xyz" )
|
set( APPLICATION_DEFAULT_SERVER_URL "https://qsfera.kusoft.xyz" )
|
||||||
|
set( APPLICATION_UPDATE_URL "https://qsfera.kusoft.xyz/desktop/updates/stable.xml" )
|
||||||
|
|
||||||
if(BETA_CHANNEL_BUILD)
|
if(BETA_CHANNEL_BUILD)
|
||||||
set( APPLICATION_NAME "${APPLICATION_NAME} Beta")
|
set( APPLICATION_NAME "${APPLICATION_NAME} Beta")
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
# Windows Update Feed
|
||||||
|
|
||||||
|
QSfera Desktop uses the built-in Windows updater from `src/gui/updater`.
|
||||||
|
The updater reads `APPLICATION_UPDATE_URL`, appends query parameters such as
|
||||||
|
`version`, `platform=win32`, and `msi=true`, and expects an XML document with
|
||||||
|
root element `QSfera`.
|
||||||
|
|
||||||
|
Production feed URL:
|
||||||
|
|
||||||
|
```text
|
||||||
|
https://qsfera.kusoft.xyz/desktop/updates/stable.xml
|
||||||
|
```
|
||||||
|
|
||||||
|
The no-update feed is valid when no signed Windows MSI is available yet:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<QSfera>
|
||||||
|
<version></version>
|
||||||
|
<versionstring></versionstring>
|
||||||
|
<web>https://qsfera.kusoft.xyz/</web>
|
||||||
|
<downloadurl></downloadurl>
|
||||||
|
</QSfera>
|
||||||
|
```
|
||||||
|
|
||||||
|
To advertise a signed MSI update, publish the MSI over HTTPS and replace the
|
||||||
|
feed with a concrete version:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<QSfera>
|
||||||
|
<version>1.3.7</version>
|
||||||
|
<versionstring>1.3.7</versionstring>
|
||||||
|
<web>https://qsfera.kusoft.xyz/</web>
|
||||||
|
<downloadurl>https://qsfera.kusoft.xyz/desktop/downloads/QSfera_Desktop-1.3.7-x64.msi</downloadurl>
|
||||||
|
</QSfera>
|
||||||
|
```
|
||||||
|
|
||||||
|
Validate the live feed before a Windows production release:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
./scripts/validate-desktop-update-feed.ps1 -Url "https://qsfera.kusoft.xyz/desktop/updates/stable.xml"
|
||||||
|
./scripts/validate-production-release.ps1 -VerifyUpdateFeed
|
||||||
|
```
|
||||||
|
|
||||||
|
For a stable signed tag release, `validate-production-release.ps1 -RequireSigning`
|
||||||
|
also verifies this live feed.
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$Url
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
|
||||||
|
function Fail($Message) {
|
||||||
|
Write-Error $Message
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
$parsedUrl = $null
|
||||||
|
if (-not [System.Uri]::TryCreate($Url, [System.UriKind]::Absolute, [ref]$parsedUrl)) {
|
||||||
|
Fail "Desktop update feed URL is not absolute: $Url"
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($parsedUrl.Scheme -ne "https") {
|
||||||
|
Fail "Desktop update feed URL must use HTTPS: $Url"
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$response = Invoke-WebRequest -Uri $Url -UseBasicParsing -TimeoutSec 30
|
||||||
|
} catch {
|
||||||
|
Fail "Desktop update feed request failed for ${Url}: $($_.Exception.Message)"
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($response.StatusCode -ne 200) {
|
||||||
|
Fail "Desktop update feed must return HTTP 200. Actual status: $($response.StatusCode)"
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
[xml]$xml = $response.Content
|
||||||
|
} catch {
|
||||||
|
Fail "Desktop update feed is not valid XML: $($_.Exception.Message)"
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($xml.DocumentElement.Name -ne "QSfera") {
|
||||||
|
Fail "Desktop update feed root element must be <QSfera>."
|
||||||
|
}
|
||||||
|
|
||||||
|
$requiredElements = @("version", "versionstring", "web", "downloadurl")
|
||||||
|
foreach ($name in $requiredElements) {
|
||||||
|
if ($null -eq $xml.QSfera.$name) {
|
||||||
|
Fail "Desktop update feed is missing <$name>."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$version = [string]$xml.QSfera.version
|
||||||
|
$versionString = [string]$xml.QSfera.versionstring
|
||||||
|
$web = [string]$xml.QSfera.web
|
||||||
|
$downloadUrl = [string]$xml.QSfera.downloadurl
|
||||||
|
|
||||||
|
if (-not [string]::IsNullOrWhiteSpace($web)) {
|
||||||
|
$webUri = $null
|
||||||
|
if (-not [System.Uri]::TryCreate($web, [System.UriKind]::Absolute, [ref]$webUri) -or $webUri.Scheme -ne "https") {
|
||||||
|
Fail "Desktop update feed <web> must be an absolute HTTPS URL when present."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ([string]::IsNullOrWhiteSpace($version)) {
|
||||||
|
if (-not [string]::IsNullOrWhiteSpace($downloadUrl)) {
|
||||||
|
Fail "Desktop no-update feed must leave <downloadurl> empty when <version> is empty."
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$parsedVersion = $null
|
||||||
|
if (-not [System.Version]::TryParse($version, [ref]$parsedVersion)) {
|
||||||
|
Fail "Desktop update feed <version> must be parseable as a version number."
|
||||||
|
}
|
||||||
|
|
||||||
|
if ([string]::IsNullOrWhiteSpace($versionString)) {
|
||||||
|
Fail "Desktop update feed must set <versionstring> when <version> is present."
|
||||||
|
}
|
||||||
|
|
||||||
|
$downloadUri = $null
|
||||||
|
if (-not [System.Uri]::TryCreate($downloadUrl, [System.UriKind]::Absolute, [ref]$downloadUri) -or $downloadUri.Scheme -ne "https") {
|
||||||
|
Fail "Desktop update feed <downloadurl> must be an absolute HTTPS URL when an update is advertised."
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not $downloadUri.AbsolutePath.EndsWith(".msi", [System.StringComparison]::OrdinalIgnoreCase)) {
|
||||||
|
Fail "Desktop Windows updater expects an MSI download URL."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "QSfera Desktop update feed validation passed: $Url"
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
param(
|
param(
|
||||||
[switch]$RequireSigning,
|
[switch]$RequireSigning,
|
||||||
|
[switch]$VerifyUpdateFeed,
|
||||||
[string]$Root = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path
|
[string]$Root = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -35,6 +36,10 @@ if ($branding -notmatch 'APPLICATION_DEFAULT_SERVER_URL\s+"https://qsfera\.kusof
|
|||||||
Fail "Desktop default server URL must be https://qsfera.kusoft.xyz."
|
Fail "Desktop default server URL must be https://qsfera.kusoft.xyz."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($branding -notmatch 'APPLICATION_UPDATE_URL\s+"https://qsfera\.kusoft\.xyz/desktop/updates/stable\.xml"') {
|
||||||
|
Fail "Desktop update feed URL must be https://qsfera.kusoft.xyz/desktop/updates/stable.xml."
|
||||||
|
}
|
||||||
|
|
||||||
if ($cmake -notmatch 'option\(WITH_UPDATE_NOTIFICATION\s+"Whether to check for new releases"\s+ON\)') {
|
if ($cmake -notmatch 'option\(WITH_UPDATE_NOTIFICATION\s+"Whether to check for new releases"\s+ON\)') {
|
||||||
Fail "Desktop release builds must keep update notifications enabled by default."
|
Fail "Desktop release builds must keep update notifications enabled by default."
|
||||||
}
|
}
|
||||||
@@ -55,6 +60,17 @@ if ($RequireSigning) {
|
|||||||
if ([string]::IsNullOrWhiteSpace($env:CRAFT_CODESIGN_CERTIFICATE)) {
|
if ([string]::IsNullOrWhiteSpace($env:CRAFT_CODESIGN_CERTIFICATE)) {
|
||||||
Fail "Production Windows tag releases require CRAFT_CODESIGN_CERTIFICATE."
|
Fail "Production Windows tag releases require CRAFT_CODESIGN_CERTIFICATE."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$VerifyUpdateFeed = $true
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($VerifyUpdateFeed) {
|
||||||
|
$feedValidator = Join-Path $PSScriptRoot "validate-desktop-update-feed.ps1"
|
||||||
|
if (-not (Test-Path -LiteralPath $feedValidator)) {
|
||||||
|
Fail "Desktop update feed validator was not found: $feedValidator"
|
||||||
|
}
|
||||||
|
|
||||||
|
& $feedValidator -Url "https://qsfera.kusoft.xyz/desktop/updates/stable.xml"
|
||||||
}
|
}
|
||||||
|
|
||||||
Write-Host "QSfera Desktop production release validation passed."
|
Write-Host "QSfera Desktop production release validation passed."
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ Date: 2026-06-08.
|
|||||||
- Server Docker builds now accept explicit production metadata for `QSFERA_VERSION`, `QSFERA_EDITION`, and `QSFERA_BUILD_DATE`, and the Raspberry Pi 5 compose path defaults to pinned image `qsfera-cloud-server:7.0.0-rpi5-stable`.
|
- Server Docker builds now accept explicit production metadata for `QSFERA_VERSION`, `QSFERA_EDITION`, and `QSFERA_BUILD_DATE`, and the Raspberry Pi 5 compose path defaults to pinned image `qsfera-cloud-server:7.0.0-rpi5-stable`.
|
||||||
- Windows/Desktop update channel defaults now match the build channel: stable builds default to `stable`, beta builds default to `beta`.
|
- Windows/Desktop update channel defaults now match the build channel: stable builds default to `stable`, beta builds default to `beta`.
|
||||||
- Windows/Desktop CI now validates production release invariants before Windows packaging and requires signing variables for stable tag releases.
|
- Windows/Desktop CI now validates production release invariants before Windows packaging and requires signing variables for stable tag releases.
|
||||||
|
- Windows/Desktop now has a configured HTTPS update feed at `https://qsfera.kusoft.xyz/desktop/updates/stable.xml`; the feed returned HTTP `200` on 2026-06-08 and contains the XML elements parsed by `Desktop/src/gui/updater/updateinfo.cpp`.
|
||||||
|
|
||||||
## Raspberry Pi 5 Check
|
## Raspberry Pi 5 Check
|
||||||
|
|
||||||
@@ -65,7 +66,7 @@ Date: 2026-06-08.
|
|||||||
- Fill production domains, ACME email, admin passwords, Keycloak passwords, and storage secrets from a secret manager. Do not commit real values.
|
- Fill production domains, ACME email, admin passwords, Keycloak passwords, and storage secrets from a secret manager. Do not commit real values.
|
||||||
- Run `./validate-production-env.sh .env` and then `docker compose config` for `Server/devtools/deployments/qsfera_full` with a filled production `.env`; Docker CLI is not available in this local environment, so compose rendering is not locally verified.
|
- Run `./validate-production-env.sh .env` and then `docker compose config` for `Server/devtools/deployments/qsfera_full` with a filled production `.env`; Docker CLI is not available in this local environment, so compose rendering is not locally verified.
|
||||||
- Provide real Windows signing secrets in CI: `QSFERA_DESKTOP_SIGN_PACKAGE=True` and `QSFERA_DESKTOP_CODESIGN_CERTIFICATE` with the Craft-compatible signing certificate reference.
|
- Provide real Windows signing secrets in CI: `QSFERA_DESKTOP_SIGN_PACKAGE=True` and `QSFERA_DESKTOP_CODESIGN_CERTIFICATE` with the Craft-compatible signing certificate reference.
|
||||||
- Configure and verify the desktop updater endpoint via `APPLICATION_UPDATE_URL`; `Desktop/scripts/validate-production-release.ps1` confirms update notifications are enabled but cannot prove a live update feed without that URL.
|
- Publish the first signed Windows MSI and update `https://qsfera.kusoft.xyz/desktop/updates/stable.xml` from the current no-update XML to a concrete `<version>`, `<versionstring>`, and HTTPS `.msi` `<downloadurl>`.
|
||||||
|
|
||||||
## Remaining P1
|
## Remaining P1
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user