Files
punktfunk/packaging/winget
enricobuehlerandClaude Opus 5 e726542f96
ci / rust-arm64 (push) Successful in 1m23s
ci / web (push) Successful in 1m42s
ci / rust (push) Successful in 4m45s
ci / docs-site (push) Successful in 1m30s
docker / builders (--build-arg FEDORA_VERSION=44, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm, -f44) (push) Successful in 13s
docker / builders (ci/android-ci.Dockerfile, punktfunk-android-ci) (push) Successful in 9s
docker / builders (ci/arch-ci.Dockerfile, punktfunk-arch-ci) (push) Successful in 6s
docker / builders (ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 5s
docker / builders (ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 5s
docker / builders (ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 5s
docker / apps (., web/Dockerfile, punktfunk-web) (push) Successful in 9s
docker / apps (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 8s
docker / builders-arm64cross (push) Successful in 6s
docker / deploy-docs (push) Successful in 28s
docs(winget): the vhost belongs in unom/infra, not on the box
Step 2 told you to add the Caddy vhost by hand on unom-1. That instruction is
what broke the source: ~/caddy/Caddyfile is a copy that deploy-all.sh rsyncs
over from unom/infra, with no .git there to warn you, so the hand-added block
survived until the 2026-07-31 hardening commit rewrote the file from the repo's
own copy and deleted it.

Point step 2 at unom/infra and record how the failure presents, since it does
not look like an ingress problem from the client side: no vhost means no
certificate for that SNI, so Caddy answers with TLS internal_error (alert 80)
before sending one, and winget surfaces that as
WINHTTP_CALLBACK_STATUS_FLAG_SECURITY_CHANNEL_ERROR / 0x8a15003b.

Also note that port 80 is useless for diagnosing it — Caddy 308s every Host to
https including names it has never heard of — and give the SNI probe that does
work.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 10:43:39 +02:00
..

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):

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:

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

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/. 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.

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.