Repository navigation
Installer: WiX MSI (blocked on code signing) #77
Description
Skrog installs by unzip-and-run, or by the one-line bootstrap (#359, done). What is missing is a real Windows installation surface: an MSI an admin can deploy, and package-manager entries people expect to find.
Blocked on #358 — the code signing decision — and not on effort. An unsigned MSI that asks for elevation is a worse first impression than a zip: it triggers SmartScreen at the moment the user is being asked to trust it most. Shipping one before a certificate exists would make the problem it is meant to solve worse.
WiX MSI
- installs
skrog.exe/skrogw.exe/skrogtray.exe, adds to PATH, optional Start Menu entries - silent/unattended flags that work Intune-style (
msiexec /qn) - uninstall runs
skrog uninstallsemantics — or clearly documents that engine data removal is a separate, explicit step. An MSI uninstall silently deleting a user's images and volumes would be wrong - upgrade in place preserves the state dir and the autostart registration
Package managers
- winget manifest — submit early, the review queue takes 1–2 weeks
- scoop + chocolatey manifests
- package IDs depend on the naming decision in Name housekeeping: trademark search and package IDs for SKROG (was Hawser) #1
Exit criteria
winget install skrog on a clean machine with no SmartScreen wall, and an MSI that deploys silently via Intune-style flags.
Note that the first of those is not achievable by packaging alone: SmartScreen reputation only begins accruing after signing, so even a signed MSI warns for a while. That is the circularity described in #358.
Split out of this issue
- One-line bootstrap installer: irm | iex, checksum-verified #359 — the one-line
irm | iexbootstrap. Done, anddocs/install.mddocuments the manual path alongside it. - Decide the code signing route: reapply to the SignPath Foundation later, or pay #358 — the signing decision: reapply to the SignPath Foundation once visibility grows, or pay. Was a cross-cutting line here; it is a policy-and-money question rather than an engineering one, and it blocks this issue and long term: reach the wslc root namespace through the WSL Plugin API (WSLCCreateProcess/WSLCProcessGetFd) once skrog binaries are signed #325.
Related
skrog upgrade --apply (self-update) shipped separately in #309/#310.
Activity
docs: index every page from the README, and check the links in CI (#199)
docs: settle the per-workspace engine questions, and say why it is no…
docs: settle the per-workspace engine questions, and say why it is no…
zcsizmadia commentedon Sep 11, 2026
Decision: apply to the SignPath Foundation OSS programme
Free certificates for open-source projects, issued to the project rather than to a person. Chosen over Azure Trusted Signing (whose identity validation has historically wanted an organisation with years of verifiable history — hawserhq is weeks old) and over a commercial OV certificate (real money, annual renewal, and an OV cert still has to earn SmartScreen reputation the slow way).
Why this is the critical path, not a polish item
It gates more than SmartScreen. From #1: winget and Chocolatey package IDs cannot be reserved — the ID is taken when a manifest is merged or a package is pushed. Publishing well needs signing. So the signing application is on the critical path for name protection as well as for distribution.
Next steps
release.ymlwill need a signing step, and the release artifacts stop being reproducible from a local build aloneWhat stays blocked until it lands
The MSI, the winget manifest, the Chocolatey package, and the
hawser upgradeapp-apply half of #191. The one-line bootstrap andhawser upgrade --checkdo not depend on it and could land earlier.feat: hawser upgrade --check — one answer to "am I current?" (#191) (#…
docs: a code signing policy, which SignPath requires us to publish (#77…
docs: record #140's approval, and clear a blocker that has since land…
feat: a one-line installer, served from the docs site (#77) (#223)
zcsizmadia commentedon Sep 11, 2026
The one-line bootstrap is live
#223, merged.
Verified by running that exact published command end to end on a real machine: resolved 0.3.0, downloaded, verified
sha256 dec31978…against the release'sSHA256SUMS, unpacked all three binaries, and the installedhawser.exeruns.Checklist
SHA256SUMS, unpacks, adds to PATH; checksum-verified end to end, and no "latest" semantics beyond resolving the newest published app release explicitlyDecisions embedded in it
It does not provision.
hawser installcreates a WSL2 distro and downloads a rootfs — not something a command someone pipes into their shell should do unasked. The script names it as the next step instead.Hosted on the docs site, not a domain (there is no domain, by decision), and mounted from
scripts/install.ps1rather than copied, so the file people pipe into their shell is the same one reviewed in the repository. Confirmed byte-identical after deploy. The README links to it for anyone who would rather read before running.Release resolution filters to
v<semver>non-draft tags. App and rootfs releases share one tag namespace and/releases/latestcan return either — the same traphawser upgradeandsetup-hawserboth had to avoid.PATH is read and written raw, never expanded — writing back an expanded value is how an installer turns a
%USERPROFILE%in someone's PATH into a literal path that breaks when the profile moves.Tamper detection was tested, not assumed: a matching hash is accepted in both
SHA256SUMSformats, and a tampered hash, a missing entry, and an entry for another architecture are each rejected. Pointed at the live install while the engine was running, it refuses with an actionable message rather than failing on a file lock.Still blocked on code signing
Unchanged, and this does not move them:
The code signing policy SignPath requires is published; the application itself needs your two decisions (the roles table, and accepting that signing moves into CI).
zcsizmadia commentedon Sep 11, 2026
Both decisions confirmed
Roles table: accepted. All three roles are the one maintainer, stated plainly in the policy rather than implying a separation that does not exist. What it still guarantees with one person: no signature without a deliberate act — signing is never automatic on a tag.
CI-only signing: accepted — and I overstated this when I raised it.
I described "reproducible only through the pipeline, not from a local
go build" as a cost of adopting SignPath. It is not a change at all.release.ymlalready signsSHA256SUMSwith cosign keyless:That identity exists nowhere but inside a GitHub Actions run, so CI has always been the only thing able to produce an official release. SignPath adds an Authenticode signature to a pipeline that was already the sole source of signed artifacts. Corrected in #224.
One condition worth knowing before applying
The certificate is issued by the Foundation to the project, and eligibility requires an OSI licence with no commercial dual-licensing for any component. A future paid tier or dual-licence would end eligibility and mean buying a commercial certificate instead. Not a blocker — the org has funding enabled, which is fine; a paid product would not be.
The pipeline already fits their requirements
Checked against SignPath's GitHub Action, which requires the artifact to be a workflow artifact before submission:
release.yml,-trimpath, GitHub-hostedactions/upload-artifactbefore signinghawser-binaries)Why I have not written the signing step
Three of the action's required inputs —
organization-id,project-slug,signing-policy-slug— are assigned by SignPath at onboarding. They do not exist yet, so any step I wrote now would be a guess that cannot be tested. It goes in when the project is approved; the surrounding pipeline needs no rearranging to accept it.What is left for you
SIGNPATH_API_TOKENas a repo secret once approvedThen the MSI, winget and Chocolatey unblock together.
zcsizmadia commentedon Sep 11, 2026
Prepared answers for the SignPath application
Every value checked against the repository. Updated for v0.3.1, which is the release to cite — a reviewer can verify its provenance and cosign signature and both succeed.
Identity
HawserDescription
Short, for their project listing:
Longer, if there is a free-text box. Their terms require functionality to be described:
What needs signing
Windows executables, amd64 and arm64, shipped inside the release zips:
hawser.exehawserw.exehawsertray.exeArtifact shape, if asked: two zips per release (
hawser_<version>_windows_amd64.zip,..._arm64.zip), each containing the three executables plusLICENSEandREADME.md.The engine rootfs is out of scope — a Linux tarball, pinned by SHA-256 and separately signed with cosign.
Why signing is needed
Build and release
.github/workflows/release.yml, triggered by publishing av*releasego build -trimpath, GitHub-hostedwindows-latestrunner, from the tag's sourceactions/upload-artifact(hawser-binaries) — SignPath's structural prerequisite, already in placeSHA256SUMSCode signing policy
https://hawserhq.github.io/hawser/code-signing/
Names the three roles, the manual per-release approval, the CI-only signing model, and the data-handling statement their terms require.
Evidence of a real release
v0.3.1. A reviewer can check it:
Before submitting
The policy page asserts that role holders use MFA on GitHub and SignPath. Confirm that is true —
hawserhqdoes not currently enforce 2FA org-wide, and a claim on the page a reviewer is reading should not be the thing that is wrong.docs: provenance and cosign are live from v0.3.1, not "the next relea…
zcsizmadia commentedon Sep 12, 2026
Refreshed application sheet — every value re-checked against the repo today
The earlier sheet on this ticket predates the rename and is now wrong in every identity field: it names Hawser,
hawserhq/hawser,hawserhq.github.io/hawser, and cites v0.3.1. Submitting it would have a reviewer verifying artifacts under a project and org that no longer exist.This replaces it. Each row was checked against the repository or fetched live, not recalled.
Identity
All four URLs verified to return 200 today.
Description
Short (this is the repo's own description, so the two cannot drift):
Longer, if there is a free-text box:
What gets signed
skrog.exe,skrogw.exe,skrogtray.exe— the Windows executables in a release.Deliberately not the engine rootfs: it is a Linux tarball, pinned by SHA-256 in the compiled-in manifest and separately signed with Sigstore/cosign. Worth stating in the application, because it shows the certificate is scoped to what Authenticode is actually for.
Release to cite
v0.4.0 — published today, and the better exhibit than v0.3.1 in three specific ways a reviewer can check themselves:
--owner wslkit. I confirmed the exit code discriminates: the correct owner exits 0, a wrong one exits 1 with a 404, so a pass is a real pass rather than a silent no-op. (ghprints its summary only to a TTY, so redirected output is empty either way — do not read that as failure.)SHA256SUMSis cosign-signed under the current repository identity.SHA256SUMSshipped with CRLF, sosha256sum -creported "no file was verified" on every release (SHA256SUMS ships with CRLF, so the documentedsha256sum -cverification fails on every release #248). Citing an older release would hand a reviewer a verification step that fails.Programme requirements already satisfied
SHA256SUMSwith cosign keyless, whose identity is a GitHub Actions OIDC token that exists nowhere but inside a workflow run. CI has always been the only thing able to produce an official release.skrog upgrade's query to the GitHub releases API, only when a user runs that command;--offlineskips it.Constraints to accept before submitting
Both are in the policy already, restated so the decision is deliberate:
Why this is still the critical path
Unchanged from the earlier comment and worth repeating: signing gates more than SmartScreen. winget and Chocolatey package IDs cannot be reserved — an ID is taken when a manifest merges or a package is pushed. Publishing badly to claim a name is worse than waiting. So this application blocks the MSI, all three package managers, and name protection at once, while the one-line bootstrap (already shipped) carries distribution in the meantime.
Nothing else in this ticket is blocked on engineering.
2 remaining items
feat: skrog upgrade --apply replaces the app too (#309)
feat: skrog upgrade --apply replaces the app too (#309) (#310)
feat(wslc): doctor, command guards, and a real install guide (#335) (#…
zcsizmadia commentedon Sep 15, 2026
Docs caught up with the current reality in 0.5.0, which changes what this issue has to carry.
docs/install.mdis now a full step-by-step guide for the unzip-and-run path: which zip for which architecture, verifying it againstSHA256SUMS, where to unpack, what each file in the zip is for, thenskrog install→skrog start→ verify. It also states plainly that the three stages are separate, and that Skrog does not install adockerCLI by default — which was the single most confusing thing about a first install and is now answered in the docs and byskrog installitself, which prints the hint when it finds nodockeron PATH.So the one-line bootstrap row of this issue is not just done, it is documented. What remains is the MSI and the package managers, both still gated on the Authenticode certificate: shipping an unsigned MSI would make the SmartScreen warning worse rather than better, because an installer that asks for elevation and is unsigned is a harder thing to talk someone through than a zip. That reasoning is now written into the README and the install guide rather than living only here.
Suggest this issue narrows to "MSI + winget/scoop/choco, once signed", with the bootstrap row checked off.
docs: SignPath declined for now — record both remaining routes honest…
zcsizmadia commentedon Sep 16, 2026
Signing route changed, and this issue is now the decision rather than a wait.
The SignPath Foundation application — the free OSS programme — was declined for now. It is for projects with an established user base, and Skrog does not have one yet. They invited a reapplication as visibility grows.
So this is no longer "waiting for a certificate". It is a choice between two open routes:
The circularity, stated plainly
The free route needs adoption. The fastest driver of adoption is winget, which is in this issue. An unsigned installer asking for elevation is a worse first impression than a zip, so winget effectively wants a signed binary. And SmartScreen reputation only begins accruing after signing.
Free → needs adoption → needs winget → wants signing → needs a certificate. Paying is the only thing that breaks that loop on our own initiative. That does not make it the right answer; it makes it the answer that does not depend on anyone else.
The non-obvious part of the trade
The Foundation issues the certificate to the project and requires an OSI-approved licence with no commercial dual-licensing for any component — a future paid tier would end eligibility — and can pause or revoke, including retroactively.
A purchased certificate costs money, but nobody can withdraw it over a licence change or a disagreement.
So the free route keeps a door open that the paid route closes, and vice versa. Worth deciding on that basis rather than on price alone. Written up in
docs/code-signing.md.What this issue should now be
Suggest splitting:
irm | iex) — done and documented indocs/install.md; close that part out.Docs across the repo were corrected in #357; they had all been claiming signing was applied for and imminent.
[-]Installer & distribution: WiX MSI, winget/scoop/choco, one-line bootstrap[/-][+]Installer & distribution: WiX MSI, winget/scoop/choco[/+]zcsizmadia commentedon Sep 16, 2026
Split done, per the suggestion above:
docs/install.mddocuments the by-hand path beside it.This issue is now MSI + package managers only, retitled to match, and explicitly blocked on #358 rather than on anyone finding time.
Worth restating the reason it is blocked, because it is easy to read as over-caution: an unsigned MSI is not a neutral step toward a signed one. It triggers SmartScreen at exactly the moment the user is being asked to grant elevation, which is worse than the zip it would replace. container-desktop's Defender-blocked installer is the cautionary tale already cited in PLAN.
zcsizmadia commentedon Sep 16, 2026
Correction: winget is not blocked on signing. I asserted that above and it is wrong.
winget requires a signed installer only for installers that elevate — an MSI or a setup EXE. It also supports a portable type, which needs no signature, no elevation and no MSI. Verified on this machine:
Both are unsigned GitHub-release zips, shipping on winget today. Skrog already publishes exactly that artifact shape, checksum and all.
So this issue splits again, by blocker
The manifest needs no new build output — the release zip is the package:
winget extracts to `...\WinGet\Packages<id>.
Two real prerequisites, neither of them signing
os.Executable()returns the shim rather than the target, so sibling lookups land in the Links directory. Measured, not assumed. Autostart is the visible casualty: it would fail to findskrogw.exe. Fixing that before submitting matters, because "installed from winget, autostart does not work" would be a bad first impression from the exact channel meant to win users.Also worth deciding:
skrog upgrade --applyoverwrites the binary in place, which is wrong for a winget-managed install. It should detect that and defer towinget upgrade.Why this matters beyond packaging
It removes the circularity I described in #358 — free signing needs visibility, visibility needs winget, winget was thought to need signing. That loop was never closed. winget is reachable now, which makes it the cheapest path to the visibility the SignPath reapplication wants.
fix: resolve the executable path through symlinks before deriving sib…
docs: winget was never blocked by code signing (#362)
zcsizmadia commentedon Sep 18, 2026
Review: the blocker banner is too broad — winget is not blocked, by this project'"'"'s own docs
The issue says "Blocked on #358 — the code signing decision" for the whole ticket. That is right for the MSI and wrong for winget, and the repo says so in two places:
A
portable-type winget manifest points at the existing release zip, installs per-user, needs no elevation and therefore no Authenticode signature to avoid the elevation-prompt problem this issue correctly identifies for the MSI.So the ticket is currently telling a reader that the one distribution channel available today is unavailable.
Suggestion: split it.
Worth noting the ordering argument in #358 too: winget submission takes queue time, and SmartScreen reputation only begins accruing once something ships. Starting the winget half now costs nothing and shortens the path either way the signing decision goes.
[-]Installer & distribution: WiX MSI, winget/scoop/choco[/-][+]Installer: WiX MSI (blocked on code signing)[/+]zcsizmadia commentedon Sep 18, 2026
Split: the package-manager half is now #412 and is not blocked on #358, per this repo's own docs (winget's \portable\ type unpacks per-user, so there is no elevation prompt for an unsigned binary to make frightening). scoop went with it; chocolatey stayed here, since its install model wants an installer and puts it back behind the same blocker as the MSI.
Retitled so the blocker in the banner matches what is actually blocked. Everything left here — the WiX MSI, silent/unattended flags, uninstall semantics, in-place upgrade — is correctly gated on #358.
Add the skrog manifest (0.8.0) (#2)