diff --git a/.gitea/workflows/windows-client.yml b/.gitea/workflows/windows-client.yml index 9745490e..ca42f8c5 100644 --- a/.gitea/workflows/windows-client.yml +++ b/.gitea/workflows/windows-client.yml @@ -70,12 +70,18 @@ # latest stable tag via scripts/ci/pf-version.ps1, run number climbs monotonically). # Both arches share the version; artifacts are arch-suffixed (..._x64.msix / ..._arm64.msix). # -# Signing (clients/windows/packaging/pack-msix.ps1): if the MSIX_CERT_PFX_B64 / MSIX_CERT_PASSWORD -# Actions secrets are set (a real or shared code-signing .pfx whose subject DN == Publisher), the -# package is signed with them. Otherwise an ephemeral self-signed cert is generated and its public -# .cer is published next to the .msix (users import it to Trusted People before install). +# Signing (clients/windows/packaging/pack-msix.ps1), first match wins: +# 1. Azure Artifact Signing — what this workflow always takes, since the AZURE_CODESIGNING_* +# endpoint/account/profile are literals below and only the AZURE_TENANT_ID / AZURE_CLIENT_ID / +# AZURE_CLIENT_SECRET secrets are needed. Publicly trusted, so NO .cer is emitted or published +# and users import nothing. NOTE the Publisher DN is the Azure profile's verified subject, and +# MSIX identity is name + publisher: moving to it changed the package identity, so installs +# predating it need an uninstall, not an upgrade. +# 2. MSIX_CERT_PFX_B64 / MSIX_CERT_PASSWORD — the older self-signed .pfx, kept as a fallback. +# 3. an ephemeral self-signed cert. Modes 2 and 3 DO emit a .cer next to the .msix, which users +# would have to import into Trusted People before Windows will install the package. # -# That fallback is for canary/CI ONLY. On a v* tag the pack script FAILS CLOSED — a missing secret +# Modes 2 and 3 are for canary/CI ONLY. On a v* tag the pack script FAILS CLOSED — a missing secret # aborts the build instead of quietly shipping a release signed by a per-build throwaway cert that # no one can pin. Nothing to opt into here: the script reads GITHUB_REF itself. name: windows-client diff --git a/clients/windows/packaging/README.md b/clients/windows/packaging/README.md index 6c22343c..f9aa1d38 100644 --- a/clients/windows/packaging/README.md +++ b/clients/windows/packaging/README.md @@ -12,7 +12,8 @@ x64 Windows runner — `x86_64-pc-windows-msvc` builds natively, `aarch64-pc-win cross-compiled (the x64 MSVC toolset ships the ARM64 cross compiler; since M10 nothing in the package links FFmpeg, so neither arch needs a per-arch `FFMPEG_DIR` tree staged on the runner — one less thing the ARM64 leg can be missing). Artifacts are arch-suffixed -(`..._x64.msix` / `..._arm64.msix`, each with its matching `.cer`); `pack-msix.ps1 -Arch x64|arm64` +(`..._x64.msix` / `..._arm64.msix`, plus a matching `.cer` only in the fallback signing modes 2 and 3 +— Azure signing emits none); `pack-msix.ps1 -Arch x64|arm64` stamps the manifest `ProcessorArchitecture` and names the output. See [`windows-client.yml`](../../../.gitea/workflows/windows-client.yml) for the cross-build rationale. @@ -52,7 +53,8 @@ low-level input hooks, WASAPI and SDL3. MSIX requires a strictly 4-part numeric version. The workflow computes: - `vX.Y.Z` tag → `X.Y.Z.0` (THE release; any `-rc`/`+meta` suffix is dropped for MSIX). Published to the stable `latest/` alias and attached to the unified Gitea Release. -- `main` push / `workflow_dispatch` → `0.3..0` (canary, climbs by run number; `canary/` alias). +- `main` push / `workflow_dispatch` → `X...0` (canary — the minor *after* the latest + `v*` tag, per `scripts/ci/pf-version.ps1`, climbing by run number; `canary/` alias). ## Signing & install diff --git a/docs-site/content/docs/install-client.md b/docs-site/content/docs/install-client.md index 3da99035..8bb023b1 100644 --- a/docs-site/content/docs/install-client.md +++ b/docs-site/content/docs/install-client.md @@ -103,29 +103,24 @@ See [packaging/flatpak](https://git.unom.io/unom/punktfunk/src/branch/main/packa ## Windows -The Windows client ships as a **signed MSIX** in the package registry. Builds use a self-signed -certificate, so you import that certificate once before Windows will install the package. +The Windows client ships as a **signed MSIX** in the package registry, signed with a publicly +trusted certificate — so there is nothing to import and nothing to trust by hand. Download, install. -1. Download the package and its certificate. Each channel keeps one fixed URL, so these two lines - always fetch the current build — in PowerShell: +1. Download the package. Each channel keeps one fixed URL, so this line always fetches the current + build — in PowerShell: ```powershell curl.exe -LO https://git.unom.io/api/packages/unom/generic/punktfunk-client-windows/latest/punktfunk-client-windows_x64.msix - curl.exe -LO https://git.unom.io/api/packages/unom/generic/punktfunk-client-windows/latest/punktfunk-client-windows_x64.cer ``` Swap `_x64` for `_arm64` on an Arm device, and `latest` for `canary` to track `main`. The same - two files are attached to every [release](https://git.unom.io/unom/punktfunk/releases), and every + file is attached to every [release](https://git.unom.io/unom/punktfunk/releases), and every build is also kept under its own version on the [packages page](https://git.unom.io/unom/-/packages) (generic group, `punktfunk-client-windows`). -2. **Trust the publisher certificate**, then install. The MSIX won't install until the certificate is - trusted — but it's the **same certificate for every release**, so this is genuinely one-time and - later updates need nothing. In an **admin** PowerShell: +2. Install it: ```powershell - # use the _arm64 files instead on an Arm device - Import-Certificate -FilePath .\punktfunk-client-windows_x64.cer ` - -CertStoreLocation Cert:\LocalMachine\TrustedPeople + # use the _arm64 file instead on an Arm device Add-AppxPackage .\punktfunk-client-windows_x64.msix ``` @@ -133,6 +128,10 @@ certificate, so you import that certificate once before Windows will install the [Windows App Runtime 2.x](https://learn.microsoft.com/windows/apps/windows-app-sdk/downloads) (the MSIX depends on `Microsoft.WindowsAppRuntime.2`), then re-run `Add-AppxPackage`. + Install from a signed-in desktop session. Over a remote, non-interactive session (SSH, an RMM + tool) `Add-AppxPackage` can fail with `0x80070005` when the Windows App Runtime it depends on is + in use and Windows can't restart the apps holding it. + 3. Launch **Punktfunk** from the Start menu and pick your host. The package also adds a second entry, **Punktfunk Console** — the same client as a controller-driven fullscreen interface for a TV or HTPC — and the headless `punktfunk` command on your PATH. @@ -223,7 +222,7 @@ but keeping them close is the least surprising. (Updating the **host** is its ow | **Linux Flatpak** | `flatpak update --user io.unom.Punktfunk` — **without `sudo`** (see the [Flatpak section](#linux-desktop-flatpak)) | | **Linux apt / dnf / pacman** | your normal `sudo apt upgrade` / `sudo dnf upgrade` / `sudo pacman -Syu`, or the app's own updater below | | **Fedora Atomic (layered)** | `rpm-ostree upgrade` on its own is not enough — see the note below the table | -| **Windows MSIX** | no self-update — download the newer `.msix` as in [Windows](#windows) and re-run `Add-AppxPackage`. The certificate is the same every release, so you don't import it again | +| **Windows MSIX** | no self-update — download the newer `.msix` as in [Windows](#windows) and re-run `Add-AppxPackage`. Coming from **0.28.1 or earlier**, see the note below the table | | **macOS `.dmg`** | download the newer `Punktfunk-.dmg` and drag it over the copy in Applications | | **iOS / iPadOS / tvOS** | TestFlight updates it | | **Android** | Google Play updates it; if you sideloaded, download the APK again and install over it | @@ -244,6 +243,20 @@ systemctl reboot The client's own updater below runs exactly that dance for you, if you'd rather not remember it. (A layered **host** has the same trap — [Updating](/docs/updating) covers it.) +**Windows, coming from 0.28.1 or earlier — uninstall first.** Those builds were signed with our own +self-signed certificate. The move to a publicly trusted one changes the package's *publisher*, and an +MSIX's identity is its name **plus** its publisher — so Windows treats the new package as a different +app rather than an update, and installing it leaves you with two **Punktfunk** entries. Remove the +old one first, then install the new `.msix` as above: + +```powershell +Get-AppxPackage *Punktfunk* | Remove-AppxPackage +``` + +This is one-time; releases after that upgrade in place. Note that a packaged app's settings live +*inside* its package, so removing the old one also removes this client's identity and its paired +hosts — expect to [pair](/docs/pairing) again once. Nothing on the host side is affected. + ### The Linux client can update itself The native Linux client checks its own channel and can apply the update in place, so you don't have diff --git a/docs-site/content/docs/install.md b/docs-site/content/docs/install.md index 88879ab6..8190df91 100644 --- a/docs-site/content/docs/install.md +++ b/docs-site/content/docs/install.md @@ -70,22 +70,22 @@ everything the installer puts on the machine, its optional tasks, the console pa This is also the path for **canary** builds, which winget doesn't carry — see [Release Channels](/docs/channels) for that download. -> **About the Unknown Publisher prompt.** The installer is signed with a self-signed certificate, so -> Windows warns before it runs — accepting the prompt is enough, nothing else is required. The winget -> route is no different: it downloads and runs that same installer. If you'd -> rather silence it, the matching **`punktfunk-host-windows_.cer`** is published next to the -> installer, and it's the **same certificate for every release**, so this is one-time. A self-signed -> certificate is its own root, so it has to go in both stores. In an **admin** PowerShell: +> **About signing.** The installer is signed with a publicly trusted certificate, so Windows shows +> the publisher by name at the UAC prompt — there is no Unknown Publisher warning and nothing to +> import. The winget route is no different: it downloads and runs that same installer. > -> ```powershell -> Import-Certificate -FilePath .\punktfunk-host-windows_.cer ` -> -CertStoreLocation Cert:\LocalMachine\Root -> Import-Certificate -FilePath .\punktfunk-host-windows_.cer ` -> -CertStoreLocation Cert:\LocalMachine\TrustedPublisher -> ``` +> SmartScreen is a separate mechanism that builds reputation per publisher, so shortly after a new +> signing certificate starts being used it can still show *"Windows protected your PC"* on the first +> downloads — **More info → Run anyway**. It settles as installs accumulate. > -> This is a different certificate from the one the bundled **drivers** are signed with — the -> installer imports that one for you. +> The bundled **drivers** are a separate matter — they carry their own certificate, and the installer +> imports that one for you. [Windows Host](/docs/windows-host#about-the-signatures) has the detail. +> +> Releases **0.28.1 and earlier** were signed with our own self-signed certificate, and older docs +> told you to import it. Nothing needs it any more: if you imported +> `punktfunk-host-windows_.cer` back then, you can remove it from `Cert:\LocalMachine\Root` and +> `Cert:\LocalMachine\TrustedPublisher` (look for the certificate issued to **unom**, thumbprint +> `CD1EFDEEEC9743AFC38F56C5AF30C5A3009BE941`). ## NixOS diff --git a/docs-site/content/docs/uninstall.md b/docs-site/content/docs/uninstall.md index 4413c09f..87ea033a 100644 --- a/docs-site/content/docs/uninstall.md +++ b/docs-site/content/docs/uninstall.md @@ -227,9 +227,12 @@ Three things are left on purpose: component other apps may be using, so the Punktfunk uninstaller never touches it. Remove it with its own uninstaller — `VBCABLE_Setup_x64.exe -u -h` — or the **VB-Audio Virtual Cable** entry in Installed apps. -- **The publisher certificate**, if you imported it by hand to silence the Unknown Publisher prompt. - Remove it in `certlm.msc` under **Trusted Publishers** and **Trusted Root Certification - Authorities**. (This is *not* the driver certificate above, which the uninstaller does remove.) +- **The old publisher certificate**, if you imported it by hand to silence the Unknown Publisher + prompt on 0.28.1 or earlier. Releases since then are signed by a publicly trusted CA and never + needed it, so it is safe to drop: remove the certificate issued to **unom** (thumbprint + `CD1EFDEEEC9743AFC38F56C5AF30C5A3009BE941`) in `certlm.msc` under **Trusted Publishers** and + **Trusted Root Certification Authorities**. (This is *not* the driver certificate above, which the + uninstaller does remove.) If you registered the winget source, drop it too — in an **admin** PowerShell, the same as registering it: diff --git a/docs-site/content/docs/windows-host.md b/docs-site/content/docs/windows-host.md index 730884a9..f7dc9b7c 100644 --- a/docs-site/content/docs/windows-host.md +++ b/docs-site/content/docs/windows-host.md @@ -94,12 +94,14 @@ are `winget upgrade unom.PunktfunkHost` and removal is `winget uninstall unom.Pu ### About the signatures -Punktfunk signs with its own certificates rather than a publicly trusted one, so Windows warns about -an unknown publisher before setup runs. Accepting the prompt is enough. To silence it for good, the -matching **`punktfunk-host-windows_.cer`** is published next to the installer and it is the -same certificate for every release — the one-time import is in -[Install → Windows](/docs/install#windows). This applies to the winget path too: winget downloads -and runs that same installer. +Setup, and the Punktfunk executables it installs, are signed with a **publicly trusted** certificate, +so UAC names the publisher rather than warning about an unknown one, and there is nothing for you to +import. This applies to the winget path too: winget downloads and runs that same installer. (The +bundled third-party pieces keep whatever their own vendors shipped.) + +Releases **0.28.1 and earlier** were signed with our own self-signed certificate, and the docs then +asked you to import `punktfunk-host-windows_.cer`. Current builds no longer produce or need it — +[Install → Windows](/docs/install#windows) shows how to remove it if you imported one. The bundled drivers carry a **second, separate** self-signed certificate (`CN=punktfunk-driver`, SHA-1 thumbprint `4B8493E7CD565758D335F8F4F05C5A7261A13E02`). The installer adds it to the machine's @@ -114,12 +116,22 @@ punktfunk-host-setup-.exe /VERYSILENT /SUPPRESSMSGBOXES /NORESTART /SP- Keep `/SUPPRESSMSGBOXES`: without it an unattended run can stop on a message box nobody can see. -A silent run takes the same defaults as the wizard, so add or drop individual options with Inno's -`/MERGETASKS` — a bare name adds a task, a `!` prefix removes one. `/MERGETASKS="gamestream"` turns -on Moonlight compatibility; `/MERGETASKS="!trayicon"` skips the status icon. The task names are -`installdriver`, `installgamepad`, `installaudiocable`, `installhdrlayer`, `gamestream`, +A silent **first** install takes the same defaults as the wizard, so add or drop individual options +with Inno's `/MERGETASKS` — a bare name adds a task, a `!` prefix removes one. +`/MERGETASKS="gamestream"` turns on Moonlight compatibility; `/MERGETASKS="!trayicon"` skips the +status icon. The task names are `installdriver`, `installgamepad`, `installhdrlayer`, `gamestream`, `allowpublicfw`, `startservice` and `trayicon`. +An **upgrade** is different: it reuses the choices the previous install recorded, not the defaults +above. That is usually what you want, but it means anything you once declined stays declined — and +if that includes `installgamepad`, the gamepad drivers are never updated alongside the host, which +surfaces later as a virtual controller games stop seeing. To force the full set on an upgrade, name +them (`/MERGETASKS` merges with the remembered set rather than replacing it): + +```powershell +punktfunk-host-setup-.exe /VERYSILENT /SUPPRESSMSGBOXES /NORESTART /SP- /MERGETASKS="installdriver,installgamepad,installhdrlayer,startservice" +``` + Two things to know before you script it: - Setup **aborts with a non-zero exit code** if another Moonlight-compatible host — Sunshine, Apollo, diff --git a/packaging/windows/README.md b/packaging/windows/README.md index 6088c978..c8605f08 100644 --- a/packaging/windows/README.md +++ b/packaging/windows/README.md @@ -303,7 +303,8 @@ pwsh -File packaging\windows\pack-host-installer.ps1 -Version 0.0.0-dev -TargetD Push a `vX.Y.Z` tag — one tag releases every platform (see [Release Channels](https://punktfunk.unom.io/docs/channels)). The workflow builds, signs, and -publishes `punktfunk-host-setup-X.Y.Z.exe` + the public `.cer`, refreshes the stable `latest/` +publishes `punktfunk-host-setup-X.Y.Z.exe` (no `.cer` — Azure signing is publicly trusted, and mode 2 +or 3 would be needed to emit one), refreshes the stable `latest/` alias, and attaches the installer to the unified Gitea Release. Main pushes publish rolling `.` **canary** builds (base derived from the latest stable tag by `scripts/ci/pf-version.ps1`) to the `canary/` alias.