feat(packaging/winget): install the Windows host with winget, from our own source
docker / build-push-arm64cross (push) Successful in 10s
docker / deploy-docs (push) Successful in 31s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 26m54s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 26m56s
apple / screenshots (push) Canceled after 11m30s
windows-host / package (push) Failing after 12s
windows-host / winget-source (push) Skipped
apple / swift (push) Successful in 6m2s
ci / web (push) Successful in 1m53s
ci / docs-site (push) Successful in 1m29s
ci / rust-arm64 (push) Successful in 9m56s
android / android (push) Successful in 12m33s
ci / bench (push) Successful in 7m17s
decky / build-publish (push) Successful in 29s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 12s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 12s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 11s
docker / build-push (ci, ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 11s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 19s
arch / build-publish (push) Successful in 18m18s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 16s
deb / build-publish (push) Successful in 9m2s
ci / rust (push) Failing after 19m38s
deb / build-publish-client-arm64 (push) Successful in 7m21s
deb / build-publish-host (push) Successful in 9m43s
docker / build-push-arm64cross (push) Successful in 10s
docker / deploy-docs (push) Successful in 31s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 26m54s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 26m56s
apple / screenshots (push) Canceled after 11m30s
windows-host / package (push) Failing after 12s
windows-host / winget-source (push) Skipped
apple / swift (push) Successful in 6m2s
ci / web (push) Successful in 1m53s
ci / docs-site (push) Successful in 1m29s
ci / rust-arm64 (push) Successful in 9m56s
android / android (push) Successful in 12m33s
ci / bench (push) Successful in 7m17s
decky / build-publish (push) Successful in 29s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 12s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 12s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 11s
docker / build-push (ci, ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 11s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 19s
arch / build-publish (push) Successful in 18m18s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 16s
deb / build-publish (push) Successful in 9m2s
ci / rust (push) Failing after 19m38s
deb / build-publish-client-arm64 (push) Successful in 7m21s
deb / build-publish-host (push) Successful in 9m43s
`winget install unom.PunktfunkHost` after adding the source, and `winget upgrade`
from then on. Windows had no update path at all before this — no self-update, no
package manager — so keeping a host current meant noticing a release and
re-running an installer by hand.
Ships the manifest trio in winget-pkgs' own format (so submitting upstream later
is a copy, not a rewrite) plus a release-time generator that substitutes only
version, URL, hash and release-notes link. Everything reviewable — the silent
switches, the agreements, the installation notes — stays in the checked-in
templates rather than buried in a generator.
Silent installs deliberately take the SAME task defaults the wizard shows: a
per-channel default is a support trap. The disclosures the wizard puts on screen
travel as manifest Agreements instead, shown before install and requiring
acceptance — VB-Audio's bundling grant wants the user to see VB-CABLE's origin
and donationware status, and a silent install shows them nothing otherwise. The
console password can only be pointed at, never printed: it is generated per
install and the notes are fixed at publish time.
Self-hosted rather than the community repo, which gates on Defender/SmartScreen
validation the self-signed installer cert would not clear today. The source is
three endpoints — /information, /manifestSearch, /packageManifests/{id}. The
reference implementation's other twenty are its admin API for mutating a
CosmosDB; a catalogue generated at release time has nothing to mutate.
It runs on unom-1 as a stock bun image with the two .mjs files bind-mounted, the
same shape as the flatpak server, so there is no image to build or pull. The
catalogue is derived from the RELEASES rather than local files: winget resolves
--version and upgrade against the version list, so a source that only knew the
newest release could neither pin an older one nor show an upgrade path from it.
That also leaves no state to drift — re-running the build reproduces it exactly.
NormalizedPackageNameAndPublisher is declared unsupported on purpose. winget
derives it client-side with its own normalization, and a near-miss silently
mis-correlates an installed host; ProductCode is exact and Inno gives us one.
28 checks drive the handler directly, and CI gates on them before anything
reaches the box — a wrong response shape does not fail loudly, it just makes
winget report "no package found".
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
# winget manifests — Windows host
|
||||
|
||||
The reviewed source of truth for the `unom.PunktfunkHost` winget package. Everything except
|
||||
`PackageVersion` / `InstallerUrl` / `InstallerSha256` / `ReleaseNotesUrl` is edited **here**;
|
||||
`scripts/ci/winget-manifest.ps1` only substitutes those four per release, so the switches,
|
||||
agreements and installation notes stay under normal code review.
|
||||
|
||||
| File | Purpose |
|
||||
| --- | --- |
|
||||
| `unom.PunktfunkHost.yaml` | Version manifest — ties the other two together. |
|
||||
| `unom.PunktfunkHost.installer.yaml` | Installer type, scope, silent switches, `ProductCode`, URL + hash. |
|
||||
| `unom.PunktfunkHost.locale.en-US.yaml` | User-facing metadata, `Agreements`, `InstallationNotes`. |
|
||||
|
||||
## Why these choices
|
||||
|
||||
- **`InstallerType: inno`, `Scope: machine`, `ElevationRequirement: elevatesSelf`.** The host
|
||||
registers a SYSTEM service, installs drivers and opens firewall ports; `PrivilegesRequired=admin`
|
||||
in the `.iss` means Setup raises its own UAC prompt. There is no per-user scope.
|
||||
- **`ProductCode: {7C9E6A52-…}_is1`** — Inno's ARP key is `<AppId>_is1`. This is what correlates an
|
||||
installed host with the package for `winget list` / `winget upgrade`. **It must track `AppId` in
|
||||
`packaging/windows/punktfunk-host.iss`** — if that GUID ever changes, change it here too or
|
||||
upgrades silently stop being detected.
|
||||
- **`interactive` is in `InstallModes`.** `winget install unom.PunktfunkHost --interactive` runs the
|
||||
full existing wizard: every task checkbox, the web-console password page, the VB-CABLE notice.
|
||||
Nothing about the installer changes to support it.
|
||||
- **No `/MERGETASKS` in the silent switches.** A silent install deliberately takes the *same* task
|
||||
defaults the wizard shows, so the product does not differ by install channel — a per-channel
|
||||
default is a support trap ("it works when I install it by hand"). The disclosures the wizard puts
|
||||
on screen are carried by `Agreements` instead, which winget shows *before* install and requires
|
||||
the user to accept.
|
||||
- **`UpgradeBehavior: install`** — Inno upgrades in place (`UsePreviousAppDir=yes`). Uninstalling
|
||||
first would run the `[UninstallRun]` service + driver teardown between versions.
|
||||
|
||||
## Opting out of individual tasks
|
||||
|
||||
Inno's `/MERGETASKS` takes `!` prefixes to deselect a default-checked task. Use `--override`
|
||||
(replaces winget's switches) rather than `--custom` (appends — you would end up with two
|
||||
`/MERGETASKS` on one command line):
|
||||
|
||||
```powershell
|
||||
winget install unom.PunktfunkHost --override "/VERYSILENT /SUPPRESSMSGBOXES /NORESTART /SP- /MERGETASKS=!gamestream"
|
||||
```
|
||||
|
||||
Task names: `installdriver`, `installgamepad`, `installaudiocable`, `installhdrlayer`,
|
||||
`gamestream`, `allowpublicfw`, `startservice`, `trayicon`.
|
||||
|
||||
## Two installer behaviours that exist for this path
|
||||
|
||||
Both are in `packaging/windows/punktfunk-host.iss` and both also fix pre-existing bugs on the
|
||||
plain double-click upgrade path:
|
||||
|
||||
- **`InitializeSetup` uses `SuppressibleMsgBox`, not `MsgBox`.** A plain `MsgBox` ignores
|
||||
`/SUPPRESSMSGBOXES` and displays even under `/VERYSILENT` — an unattended install on a box that
|
||||
already runs Sunshine/Apollo would block on an invisible modal dialog. Suppressed it returns
|
||||
`IDNO`, so that install aborts (Setup exits non-zero) rather than proceeding into the unsupported
|
||||
dual-host state.
|
||||
- **`GamestreamParam` is fresh-install-only.** On an upgrade the flag is omitted entirely, which
|
||||
`service install` reads as "keep host.env as-is". Passing an explicit on/off would rewrite
|
||||
`PUNKTFUNK_HOST_CMD` whenever it still holds either canonical value — so a silent upgrade, where
|
||||
no wizard carries the old choice forward, would flip a user's GameStream setting with nothing on
|
||||
screen.
|
||||
- **`PublicFwParam` is fresh-install-only too**, and `--allow-public-network` is now tri-state
|
||||
(`=on` / `=off` / absent → keep the recorded choice, resolved from the `fw-allow-public` marker in
|
||||
`windows/service.rs`). This task is default-*unchecked*, so without the change a silent upgrade
|
||||
would have silently **revoked** a Public-network opt-in the user made once. The bare
|
||||
`--allow-public-network` form still means `on` for existing scripts; a malformed value is a hard
|
||||
error rather than a fall-through, since a typo'd opt-*out* must never resolve to "keep Public
|
||||
open".
|
||||
|
||||
## Release flow
|
||||
|
||||
`.gitea/workflows/windows-host.yml` runs on stable `v*` tags only, **after** the installer is
|
||||
attached to the Gitea release — winget validates the URL and hash, so a manifest must never be
|
||||
published ahead of its artifact:
|
||||
|
||||
```powershell
|
||||
scripts/ci/winget-manifest.ps1 -Version 0.19.2 `
|
||||
-InstallerPath C:\t\out\punktfunk-host-setup-0.19.2.exe -OutDir C:\t\out\winget
|
||||
```
|
||||
|
||||
The generated trio is attached to the same release. Canary builds are excluded: winget pins one
|
||||
immutable artifact per version, so the rolling `canary/` alias has nothing it could point at.
|
||||
|
||||
## Validating a change
|
||||
|
||||
```powershell
|
||||
winget validate --manifest packaging\winget
|
||||
winget install --manifest packaging\winget # local install from the manifest
|
||||
```
|
||||
|
||||
For a throwaway check, `winget-pkgs`' `Tools\SandboxTest.ps1` runs a manifest in Windows Sandbox.
|
||||
Note the host needs a real GPU and installs drivers, so a Sandbox run exercises the *manifest*
|
||||
(download, hash, switches, ARP correlation) rather than a working stream.
|
||||
|
||||
## Publishing
|
||||
|
||||
Through **our own REST source** on unom-1 — see [`server/`](server/README.md). It sits alongside the
|
||||
docs (3220) and flatpak (3230) services, behind the same edge Caddy; `windows-host.yml` rebuilds and
|
||||
ships its catalogue on every stable tag, so releasing is one pipeline with no manual step.
|
||||
|
||||
```powershell
|
||||
winget source add -n punktfunk https://winget.punktfunk.unom.io -t Microsoft.Rest # elevated, once
|
||||
winget install unom.PunktfunkHost
|
||||
```
|
||||
|
||||
These manifests stay in winget-pkgs' own format rather than a bespoke one, so submitting upstream
|
||||
later is a copy, not a rewrite. Two things would need attention on that path: the signing note
|
||||
below, and `Agreements` being verified-developers-only in the community repo.
|
||||
|
||||
> **Signing.** The installer is currently signed with a self-signed cert (`CN=unom`, subject ==
|
||||
> issuer) and ships a `.cer` users import manually. winget does not sign anything; it downloads and
|
||||
> runs the same binary, so SmartScreen behaves exactly as it does for a browser download. That is a
|
||||
> pre-existing condition rather than something winget introduces — but the community repo
|
||||
> (`microsoft/winget-pkgs`) gates on it via its `Binary-Validation-Error` /
|
||||
> `Validation-Defender-Error` checks, so a submission there needs a publicly-trusted cert (Azure
|
||||
> Trusted Signing is the cheap path). A self-hosted source has no such gate.
|
||||
Reference in New Issue
Block a user