Skip to content

feat: add a windows install script - #28

Merged
bsg62 merged 1 commit into
mainfrom
feat/windows-install-script
Aug 3, 2026
Merged

feat: add a windows install script#28
bsg62 merged 1 commit into
mainfrom
feat/windows-install-script

Conversation

@bsg62

@bsg62 bsg62 commented Aug 3, 2026

Copy link
Copy Markdown
Member

install.ps1 is the Windows counterpart to install.sh, served at https://jwtd.sh/install.ps1 and published by the same Pages step:

irm https://jwtd.sh/install.ps1 | iex

Same contract as install.sh

Verify before writing anything, checksum always, a present cosign never advisory, the same certificate identity and OIDC issuer, no elevation — user directory plus HKCU only.

Where Windows forced a different design

  • Consumes the windows .zip, not the .tar.gzExpand-Archive ships with PowerShell 5.1, tar does not. The zips already exist for WinGet and are covered by the signed checksums.txt, so no new release artifact.
  • Errors throw, never exit — under irm | iex, exit would close the user's shell. $ErrorActionPreference/$ProgressPreference are set inside Install-Jwtd for the same reason: preference variables are dynamically scoped, so setting them at script scope would leave them applied to the caller's session.
  • It edits the user PATH itself, which install.sh can only hint at. The HKCU:\Environment value is read with DoNotExpandEnvironmentNames and written back as ExpandString[Environment]::SetEnvironmentVariable expands %USERPROFILE%-style entries and writes the expanded text back, corrupting parts of PATH the installer never touched. WM_SETTINGCHANGE is broadcast (best-effort) so a new terminal sees it without signing out. -NoModifyPath/JWTD_NO_MODIFY_PATH opts out.
  • Upgrades rename the old jwtd.exe aside, move the new one in, then delete best-effort — Windows won't overwrite a running exe but will rename one. The analogue of install.sh's stage-then-rename(2) handling of ETXTBSY.
  • Options are environment variables (JWTD_VERSION, JWTD_INSTALL_DIR, JWTD_NO_MODIFY_PATH) since Invoke-Expression cannot forward arguments; the param() block serves & ([scriptblock]::Create((irm …))) -Version ….
  • Architecture detection corrects for x64-on-ARM64 emulation — the Rosetta check's counterpart.

Verification

install_test.go gains 8 tests mirroring the sh ones: archive naming pinned to .goreleaser.yaml, verify-before-write ordering, the shared Cosign trust root, no exit, no elevation, the registry handling, and the publication path. Comment lines are stripped before the "must not call" assertions, so a comment explaining why the script avoids an API cannot satisfy the check for it.

The behaviour needs a real Windows host, so a windows-installer job in test.yml installs the latest published release the way a user would, reinstalls over it to exercise the upgrade path, checks that an unavailable release writes nothing, and asserts the PATH edit adds the directory while leaving a seeded %USERPROFILE%\seeded entry verbatim and the value still REG_EXPAND_SZ.

Locally, the helper functions were exercised under pwsh: checksum match / mismatch / missing entry / exact-name-not-substring, architecture mapping, the non-Windows rejection, and that the embedded P/Invoke C# compiles.

Site change beyond the installer

WebCodr.jwtd is not in winget-pkgs yet (moderation pending), so the site's Windows hero offered winget install WebCodr.jwtd — a command that fails today. It now offers the install script, and the Windows panel lists script → Scoop → WinGet. The WinGet instructions stay in place for when it merges.

🤖 Generated with Claude Code

install.ps1 is the Windows counterpart to install.sh, served at
https://jwtd.sh/install.ps1 and published by the same Pages step. It keeps
the Unix installer's contract - verify before writing anything, checksum
always, a present cosign never advisory, the same certificate identity and
issuer, no elevation - and differs only where Windows forces it to:

- It consumes the windows .zip rather than the .tar.gz, because
  Expand-Archive ships with PowerShell 5.1 and tar does not. The zips
  already exist for WinGet and are covered by the signed checksums.txt.
- Errors throw instead of exiting: under `irm | iex` an exit would close
  the user's shell. Preference variables are set inside Install-Jwtd for
  the same reason, so they do not leak into the caller's session.
- It edits the user PATH itself, which install.sh can only hint at. The
  HKCU value is read unexpanded and written back as ExpandString, since
  [Environment]::SetEnvironmentVariable would rewrite %USERPROFILE%-style
  entries the installer never touched.
- An upgrade renames the installed binary aside before moving the new one
  into place, because Windows cannot overwrite a running .exe.
- Options come from environment variables, as Invoke-Expression cannot
  forward arguments.
- Architecture detection corrects for x64-on-ARM64 emulation.

install_test.go asserts the script's shape; the behaviour needs a real
Windows host, so a windows-installer job installs the latest release for
real, reinstalls over it, checks that an unavailable release writes
nothing, and verifies the PATH edit leaves a seeded %USERPROFILE% entry
verbatim.

WebCodr.jwtd is not in winget-pkgs yet, so the site's Windows hero pointed
at a command that fails today. It now offers the install script, and the
Windows panel lists script, Scoop, then WinGet.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@bsg62
bsg62 merged commit a6b1eb0 into main Aug 3, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant