Skip to content

Commit 3da8dcd

Browse files
gitcommit90claude
andauthored
release: 0.0.39 — Windows runs the Linux build in WSL, no installer to sign (#59)
* feat(windows): WSL keepalive supervisor for the browser-GUI architecture Windows is moving off Electron/Squirrel to run the ordinary Linux build inside WSL 2, with the browser as the GUI. The one thing the Electron app was doing that WSL cannot do for itself is stay alive: WSL tears down an idle distro roughly 15 seconds after its last session closes, killing the server even though 1helm.service is enabled and would auto-start on boot. This adds a per-user Scheduled Task and a two-layer supervisor. On the Windows side keepalive-run.ps1 holds exactly one wsl.exe anchor session, respawns it if it dies, and periodically confirms both that the distro is running and that the HTTP endpoint answers. Inside the distro keepalive-hold.sh exists to pin the distro up and restarts the unit if it stops. It runs as the signed-in user with an interactive token, never as Local System, because WSL state is user-scoped - the same invariant the rest of the Windows support already honours. Crash recovery comes from a one-minute repeating trigger with IgnoreNew rather than RestartOnFailure, and a per-session mutex enforces a single holder. Two Windows-specific traps are handled explicitly. wsl.exe does not strip quotes from its own option values, so a quoted distro name fails with WSL_E_DISTRO_NOT_FOUND; arguments are now quoted only when whitespace requires it. wsl.exe also needs valid standard handles or it exits instantly with no output, so all three anchor streams are redirected to files - which is both the fix and how the quoting bug was found. Every terminate is targeted at one distro and refuses any name matching -runtime$; `wsl --shutdown` is never invoked, as it would kill every distro for every user on the machine. Verified on a live distro: holds indefinitely with no visible window, recovers from killing the holder with zero HTTP downtime, refuses to stack a second supervisor, removes cleanly, and costs 0.01% CPU. Start-at-logon and survive-reboot are NOT yet proven - the acceptance host's autologon credential is invalid and its Task Scheduler cannot create any process, so both are pending a rebuilt VM. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(windows): make keepalive install fail loudly and stop racing cold starts Validation on a healthy Windows 11 host proved both outstanding requirements - start at logon (5/5, isolated from the crash-recovery trigger) and survive a real reboot (4/4, reachable 89-104s after boot) - and surfaced one dangerous defect plus one timing risk. A non-elevated re-install reported success while doing nothing. When the task folder was created by an elevated process, Register-ScheduledTask fails with Access denied (0x80070005), but because the cmdlet is CIM-backed $ErrorActionPreference = 'Stop' does not make it terminating, so execution continued, printed "Registered scheduled task", inferred success from port 8123 answering - which the stale task satisfied - and exited 0. A failed upgrade was indistinguishable from a clean one. Registration now passes -ErrorAction Stop, is confirmed by reading the task back, and explains the elevation mismatch when it is the cause. The supervisor also restarted 1helm.service while systemd was still starting it. It now checks for the "activating" state and waits instead, and the budget before a distro recycle goes from 180s to 360s: measured cold starts were 33-80s on an 8-core box, which leaves too little margin on modest hardware. Defaults move off the spike names to the shipping distro '1helm' and C:\1helm\keepalive. All three scripts re-parsed clean with the PowerShell parser on Windows. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * build(linux): ship a ready-to-run Linux release archive The Linux host installer built the release on the end user's machine: a full `npm ci` (devDependencies included) plus `npm run build`, which is why the install path needed build-essential and npm registry access, and why node-pty compiled from source on every install. Package that work into the artifact instead. `npm run package:linux` now runs the client/sidecar build on the release builder and injects the gitignored outputs (public/bundle.js, public/bundle.css, public/app.css, the stamped public/index.html, public/excalidraw, and the photon sidecar bundle) into the staging tree, the same way the sealed channel image is already injected. Production dependencies are installed once inside docker.io/library/node:22 with --network=host, so the vendored native addons link against Debian bookworm's glibc 2.36 and stay forward compatible with every supported target. Every shipped `build/Release/*.node` is fingerprinted into resources/linux-native-modules.json with its digest and its maximum GLIBC/GLIBCXX symbol, alongside the builder's architecture and Node ABI, so an installer can refuse a release whose native addons cannot load. The archive digest is written to dist/<archive>.sha256 for pinning. All existing release guards are untouched: packaging still refuses to run outside the exact Git checkout whose HEAD version matches package.json, still requires the sealed channel image, and still verifies the staged package version. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(linux): install and update from the ready-to-run release archive The packaging change in 23e852c made the Linux artifact ship its own production node_modules, prebuilt client assets, and a native-addon manifest, but the tracked user-facing scripts still ran `npm ci` and `npm run build` on the end user's machine, so no user benefited. install.sh, update-host.sh, and apply-linux-release.sh now consume that archive instead of building it: - No compiler is probed or installed. build-essential leaves the apt list and make/c++ leave the dependency probe, because nothing on the user's machine compiles 1Helm any more. python3/python3-venv stay. - Every staged or retained release must prove it is runnable before it is promoted: vendored node_modules, the shipped manifest, built client assets, and a dlopen of every listed addon against the installed Node's exact ABI, including a node-pty that still exposes spawn(). A source-only archive is refused with an actionable message rather than silently falling back to a build the host can no longer do. - The verification always runs outside the armed transaction window, so a release that cannot run leaves the host untouched instead of half-promoted; apply-linux-release.sh reports the refusal as an error status rather than leaving the UI on "installing". - Readiness now requires 1helm.service to be `active`, not merely that something answered on 8123, and prints the unit journal on failure. - Installs refuse to start behind a foreign listener on 8123. WSL 2 puts every distribution in one shared network namespace, so a listener in another distribution or on Windows itself would otherwise answer the readiness probe while 1helm.service crash-looped on EADDRINUSE. The probe binds the port the way the service will instead of parsing `ss`, which is not guaranteed to be installed and would skip in silence. Measured cold on WSL2: 3m39.9s versus 8m49s, with no compiler present. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(windows): install 1Helm on Windows with a PowerShell one-liner Windows no longer ships an application. `irm https://1helm.com/install.ps1 | iex` enables WSL 2, imports a digest-pinned Ubuntu root filesystem, runs the ordinary Linux installer inside it, and registers the keepalive. The browser is the interface at http://localhost:8123. Nothing is code-signed because nothing is an executable, so SmartScreen never appears - which matters because 1Helm has no Authenticode identity and was never going to obtain one. Only enabling the Windows optional features and installing Microsoft's WSL package cross the UAC boundary. Importing the distribution, running the Linux installer and registering the keepalive all stay in the signed-in user's session on purpose: WSL state is per-user, so a distribution imported by an elevated session started with over-the-shoulder credentials would belong to that administrator rather than the person at the machine. The restart Windows requires to activate those features is reported as a restart with numbered steps, never as a failure, and every step is idempotent so re-running the same command continues where it stopped. `-Wait` on an elevated ShellExecute is unreliable, so the parent blocks on the real process handle before reading the exit code. Microsoft's WSL package is checked against a pinned digest AND required to carry a valid Microsoft Corporation Authenticode signature; a digest alone only proves we fetched what we expected, not who built it. A port pre-flight refuses to continue when something already holds 8123. Every WSL distribution shares one network namespace with Windows, so a foreign listener would both prevent 1Helm binding and answer its health probe in its place. The keepalive payload moves to site/public/keepalive/ and is fetched from the same origin as the installer, so the two can never be mismatched. Obtaining it is fail-closed: without the keepalive, WSL tears the distribution down when idle and 1Helm silently stops answering, so a missing component aborts the install rather than warning. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(windows): make the Windows installer able to report success The first end-to-end run of install.ps1 on a pristine Windows 11 machine failed at the point where it should have succeeded, and three separate defects were behind it. Invoke-InDistro returned wsl.exe's standard output as well as its exit code. A PowerShell function returns everything it emits, so `$code = Invoke-InDistro ...` collected every line the in-distro command printed and the exit code into one array; `$code -ne 0` is then an array filter that is truthy for any non-empty output. The Linux install completed perfectly and the installer still stopped with the entire apt and install transcript interpolated into "the Linux installer failed (exit ...)". Because the long install step always prints something, this made success unreachable rather than merely unlikely. wsl.exe's output now goes to the host, and only the exit code is returned. install.sh had no local-archive path at all: it always resolved the published release from 1helm.com and downloaded it from GitHub, ignoring the argument install.ps1 was already passing it. -LocalArchive therefore silently installed something other than the archive it was given. It now accepts an archive path, takes the version from that archive's own package.json, and verifies the digest when HELM_RELEASE_SHA256 is pinned (install.ps1 forwards it via the new -LocalArchiveSha256). Resolving the published release stays the default and is unchanged. The Start Menu entry was a .lnk whose TargetPath was set to a URL. WScript.Shell accepts that and saves a shortcut with an empty target, so the entry appeared and did nothing. It is now an Internet Shortcut. Also stop the two readiness loops printing curl's connection errors on every attempt; "connection refused" is the expected state while the unit comes up, and it buried the real outcome under hundreds of lines. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(windows): stop the unelevated installer calling DISM Get-WindowsOptionalFeature -Online requires elevation, and the main pass runs as the signed-in user on purpose. So the very first thing the installer did was something it was not permitted to do: Get-WindowsOptionalFeature : The requested operation requires elevation. At install.ps1:113 The main pass now uses Test-WslReady, which establishes the same fact from signals any user can read: Microsoft's pinned WSL responds, the vmcompute service exists - it does not until VirtualMachinePlatform is active - and Windows has no servicing restart pending. Feature enumeration is left to the elevated host-setup pass, where it is both permitted and authoritative, and Test-RestartPending only attempts it when elevated. This was invisible from an SSH session: Windows OpenSSH grants an administrator a full elevated token, so DISM succeeded during automated testing and failed immediately for a real user in an ordinary PowerShell window. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(site): stop requiring Windows release artifacts that no longer exist Windows publishes no release artifacts now: it installs the Linux archive inside WSL via https://1helm.com/install.ps1. Three places still encoded the old six-file matrix, and each would have failed the day 0.0.39 was published rather than at merge time. latestLinuxRelease() threw unless a Setup executable, full .nupkg and RELEASES were all present with digests. It backs /api/releases/linux/latest, which install.sh resolves to find the archive - so publishing a release without those files would have broken the public Linux AND Windows installers simultaneously, with a "does not contain the complete digest-qualified desktop matrix" error pointing at the wrong thing. It now expects the three artifacts that are actually shipped. RELEASE_FALLBACK advertised the same six files, so an API outage would have handed visitors download links for a Setup executable that does not exist. /download/windows redirected to that executable; it now leads to the install instructions. The Linux installer's port-collision message explained Windows and WSL sharing one network namespace regardless of platform, which reads as nonsense to an operator on a native Debian host. It is now conditional on systemd-detect-virt reporting wsl. test/site.mjs asserted the old contract in two places and is updated to the new one, including explicit assertions that the fallback does NOT advertise a Setup executable, .nupkg or RELEASES. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(windows): add uninstall.ps1 and rewrite Windows documentation Windows no longer ships an application: the ordinary Linux build runs inside a WSL 2 distribution named "1helm" and the browser is the interface at http://localhost:8123. Nothing is code-signed because nothing is shipped, so SmartScreen never appears. The documentation still described a Setup executable, a Squirrel feed, an Authenticode disclosure and %APPDATA% data roots, none of which exist. Add site/public/uninstall.ps1, the only supported way off Windows. In order: stop the keepalive (via its own keepalive-remove.ps1 when present, otherwise unregistering \1Helm\ directly), run /opt/1helm/uninstall-host.sh inside the distribution so 1Helm removes its own ownership-checked containers and units while it still can, then terminate and unregister the distribution, then delete C:\1helm and the Start Menu entry. Because `wsl --unregister` deletes the virtual disk holding every channel's files, the database and the provider credentials, it requires typed confirmation stating exactly what is lost; -Force exists for scripted use. It refuses to run as SYSTEM, whose per-user WSL state is not the user's. It never calls `wsl --shutdown`, which would stop every distribution for every user on the machine, and both the targeted terminate and the unregister sit behind the keepalive's protective pattern plus an exact, case-sensitive match against a currently registered distribution. Every step is idempotent and the run ends by reporting what was removed and what was not. Documentation now matches the verified flow at the altitude a first-time user needs: one command, one permission pop-up, a deliberate "Restart required" stop, the same command again, then the browser. It also names the things that look like failures and are not - Microsoft's own "Welcome to WSL" window, the ~40s the channel-computer runtime needs after install, the ExecutionPolicy wall that only appears when the script is downloaded rather than piped - and records that #main's terminal is now bash inside WSL, not cmd.exe. test/site.mjs asserted the retired contract: that getting-started tells Windows users to download a Setup executable and discloses `NotSigned`. Both now assert the opposite, because SmartScreen and Authenticode cannot apply to a product that ships no .exe and repeating them would send people looking for a file that does not exist. New assertions cover /uninstall.ps1 being served, the Windows guide carrying the whole two-phase flow, and the uninstaller's safety guards. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * build(site): add a reproducible 1helm.com deploy The site has been deployed by hand: git archive a commit into /opt/1helm-site/releases/<sha>, repoint current, restart the unit. That is fragile for something that gates a release - install.ps1 and install.sh are served from here, and install.sh resolves /api/releases/linux/latest to find the Linux archive, so publishing a release without deploying the site leaves Windows with no installer and Linux unable to resolve a version. Refuses any commit that is not an ancestor of origin/main, so the live surface always traces back to reviewed source. Verifies the archive actually contains install.sh, install.ps1 and the keepalive payload before going live, and rolls back to the previous release if the new one fails to answer its health endpoint. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * refactor(windows): retire the Electron/Squirrel Windows lane Windows is now the ordinary Linux build running inside a per-user WSL 2 distribution, installed by the site-served install.ps1 and removed by uninstall.ps1. Nothing Electron, Squirrel, or signed ships for Windows. - delete scripts/package-windows.cjs, scripts/install-wsl-runtime.ps1, scripts/windows-removal.cjs and test/windows-wsl-status.ps1 - drop the package:windows and package:windows:release npm scripts and the electron-winstaller/png-to-ico devDependencies they were the only users of - remove Squirrel event handling and the com.squirrel app user model id from desktop/main.cjs; unpackedPath stays (still used for the asset root) - remove the Windows branch of desktop/updater.cjs; the macOS Developer ID feed, states and quiesce-before-install path are untouched - remove the in-app WSL runtime installer that drove install-wsl-runtime.ps1 (server launcher, status plumbing, runtime/install win32 branch, and the onboarding/settings cards that could only ever paint for it) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(release): three release artifacts, Windows accepted by behaviour The governance documents still encoded a six-artifact desktop matrix with a Windows Setup executable, Squirrel package/manifest and Authenticode disclosure. None of that exists: Windows installs the ordinary Linux build into WSL 2 from the site-served install.ps1. - release-checklist, release-lifecycle, GOVERNANCE and release-notes-template now name exactly 1Helm-<version>-arm64.dmg, 1Helm-<version>-mac-arm64.zip and 1Helm-<version>-linux-node.tgz, and state that Windows publishes nothing - the Windows acceptance section is replaced with behavioural requirements: non-elevated one-liner install with a single UAC prompt, mid-install restart and resume, keepalive surviving a reboot, browser reaching localhost:8123, prior-version update with /var/lib/1helm-oci-v1 retained, uninstall.ps1 - a Linux artifact that fails acceptance now explicitly blocks Windows too, and install.ps1/uninstall.ps1/keepalive are documented as site-served - test/release-governance.mjs asserts the new contract: all four documents must name the three artifacts and say Windows publishes nothing, must carry every behavioural requirement, and must not reintroduce Squirrel, .nupkg, RELEASES, Authenticode, a Setup executable, package:windows or a Windows update feed Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * release: 0.0.39 Version, changelog and the local/1helm-channel-machine:0.0.39 pins. Points the website's offline release fallback at 0.0.39. Its digests are the digests of this commit's own artifacts, so they cannot exist yet; they are filled in at publish and the site is redeployed. Until then the placeholder is deliberately not 64 hex characters, so latestLinuxRelease() rejects it and /api/releases/linux/latest answers 503 - an installer is told no release is available rather than handed a digest that cannot match what it downloads. The macOS download link is unaffected because it resolves by asset name. test/site.mjs now covers both states and refuses a half-filled fallback, which would serve one real digest and two wrong ones. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * chore: restamp index.html for the 0.0.39 bundle Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Joseph Yaksich <gitcommit90@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent f926473 commit 3da8dcd

42 files changed

Lines changed: 2637 additions & 1882 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎CHANGELOG.md‎

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,67 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [0.0.39] - 2026-08-03
11+
12+
### Changed
13+
14+
- **Windows no longer ships an application.** 1Helm on Windows now runs the
15+
ordinary Linux build inside a WSL 2 distribution and serves its interface to
16+
the browser at `http://localhost:8123`. Install it with one command in an
17+
ordinary PowerShell window:
18+
19+
```powershell
20+
irm https://1helm.com/install.ps1 | iex
21+
```
22+
23+
There is no Electron host, no Squirrel installer, no `.exe`, and therefore
24+
nothing to code-sign — so SmartScreen never appears. Windows publishes no
25+
release artifacts; the desktop matrix is now three files (macOS DMG, macOS
26+
updater ZIP, Linux archive), and the Linux archive serves both Linux and
27+
Windows.
28+
- Windows setup asks for administrator approval once, to enable the WSL 2
29+
optional features and install Microsoft's digest- and signature-verified WSL
30+
package. Everything else — importing the distribution, installing 1Helm,
31+
registering the keepalive — runs as the signed-in user, because WSL state is
32+
per-user.
33+
- Windows requires one restart partway through setup, which Windows itself
34+
demands before WSL 2 becomes usable. Setup reports that as a restart with
35+
numbered steps rather than a failure, and re-running the same command
36+
continues from where it stopped.
37+
- `#main`'s terminal on Windows is now bash inside the distribution rather than
38+
`cmd.exe`, matching Linux.
39+
- Removing 1Helm from Windows is `irm https://1helm.com/uninstall.ps1 | iex`.
40+
41+
### Fixed
42+
43+
- Windows file operations are roughly four times faster. Every channel storage
44+
operation previously crossed the Windows-to-WSL boundary through
45+
`wsl.exe`, costing a flat ~208 ms per call — measured at 281 ms versus 73 ms
46+
for the same work without the crossing. Those crossings no longer exist,
47+
because the server now runs inside the distribution.
48+
- The Windows interface can no longer freeze. Those boundary crossings were
49+
synchronous calls on the Electron main thread, which is the thread Windows
50+
requires for its message pump, so a file listing could stall the window past
51+
the five seconds after which Windows reports "not responding". There is no
52+
longer a window to freeze: the browser waits on an HTTP request instead.
53+
- Linux and Windows installs no longer build 1Helm on the target machine. The
54+
release archive now ships production dependencies and prebuilt assets, with
55+
native addons compiled against an older glibc and verified on arrival by
56+
loading each one and checking its Node ABI. A cold install went from 8m49s to
57+
3m40s, and no C/C++ toolchain is installed on the host any more.
58+
- The Linux installer no longer reports success when another process holds port
59+
8123. Its readiness check required only that something answered, which a
60+
foreign listener satisfies; it now also requires the unit to be active, and
61+
refuses to start when the port is already taken.
62+
- A version mismatch between installer and archive failed silently after
63+
several minutes of work. It now names both versions and states that nothing
64+
was installed.
65+
- The website no longer requires a Windows Setup executable, `.nupkg` and
66+
`RELEASES` to exist before it will serve release metadata. That requirement
67+
backed the endpoint the Linux installer resolves, so the first release
68+
without those files would have broken the public Linux and Windows
69+
installers simultaneously.
70+
1071
## [0.0.38] - 2026-08-02
1172

1273
### Fixed
@@ -1038,6 +1099,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
10381099
Application Support, and isolated Apple container machines.
10391100

10401101
[Unreleased]: https://github.com/gitcommit90/1Helm/compare/v0.0.36...HEAD
1102+
[0.0.39]: https://github.com/gitcommit90/1Helm/compare/v0.0.30...v0.0.39
10411103
[0.0.38]: https://github.com/gitcommit90/1Helm/compare/v0.0.30...v0.0.38
10421104
[0.0.37]: https://github.com/gitcommit90/1Helm/compare/v0.0.30...v0.0.37
10431105
[0.0.36]: https://github.com/gitcommit90/1Helm/compare/v0.0.35...v0.0.36

‎README.md‎

Lines changed: 96 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
<p align="center">
1515
<a href="https://1helm.com/download/macos"><strong>Download for Mac</strong></a>
1616
&nbsp;·&nbsp;
17-
<a href="https://1helm.com/download/windows"><strong>for Windows</strong></a>
17+
<a href="https://1helm.com/manual/install-windows"><strong>for Windows</strong></a>
1818
&nbsp;·&nbsp;
1919
<a href="https://1helm.com/manual/install-linux"><strong>for Linux</strong></a>
2020
&nbsp;·&nbsp;
@@ -130,30 +130,74 @@ device displaying the web UI—to download and verify the signed update.
130130

131131
### Windows 11 x64
132132

133-
1. [Download the current Setup executable](https://1helm.com/download/windows).
134-
2. Windows Setup is **not yet Authenticode signed**, so SmartScreen shows
135-
"Windows protected your PC". Choose **More info** → **Run anyway**.
136-
3. Open 1Helm and complete Captain → Providers → Workspace.
137-
4. At the Workspace step, 1Helm builds its WSL 2 runtime. **You do not need WSL
138-
installed beforehand.** A stock Windows 11 ships with WSL and
139-
VirtualMachinePlatform turned off, and 1Helm enables both itself:
140-
- Approve the one administrator (UAC) prompt.
141-
- A PowerShell window opens and reports progress. **Leave it open** until it
142-
finishes.
143-
- Because those Windows features were just turned on, **Windows has to
144-
restart once.** 1Helm says so in plain language. This is the normal path
145-
on a new PC — it is not an error, and nothing is lost.
146-
- Restart, sign back in as the **same** Windows user, then open 1Helm and
147-
continue setup. It picks up where it left off; already-completed steps are
148-
skipped.
149-
150-
Setup downloads Microsoft's pinned WSL 2 package and a pinned Ubuntu 24.04 root
151-
filesystem, both SHA-256 verified, and the WSL package is additionally checked
152-
for a valid Microsoft Authenticode signature. The result is one
153-
installation-scoped WSL 2 runtime hosting one durable OCI container per ordinary
154-
channel, with Windows-drive mounts and process interop disabled. App state lives
155-
in `%APPDATA%\1Helm-OCI-v1`; the shared runtime disk lives in
156-
`%LOCALAPPDATA%\1Helm-Runtime`.
133+
There is nothing to download and no Windows application to install. 1Helm runs
134+
its ordinary **Linux** build inside a WSL 2 distribution named `1helm`, and your
135+
**browser is the interface**, at `http://localhost:8123`. Because no `.exe`
136+
ships, nothing needs code signing and **SmartScreen never appears**.
137+
138+
You need: Windows 11 **x64** (arm64 is not supported), virtualization enabled in
139+
firmware, internet access, and roughly 10 GB of free disk. **You do not need to
140+
install WSL first** — this does it for you.
141+
142+
1. Open **PowerShell**. The normal one — do **not** use "Run as
143+
Administrator".
144+
145+
2. Run exactly this:
146+
147+
```powershell
148+
irm https://1helm.com/install.ps1 | iex
149+
```
150+
151+
3. A Windows permission pop-up appears. Click **Yes**. That one approval covers
152+
only turning on Windows' WSL features and installing Microsoft's own WSL
153+
package; everything after it deliberately runs as you, because WSL state
154+
belongs to the signed-in user.
155+
156+
4. Wait about a minute. The window finishes by printing **"Restart required"**
157+
and a short numbered list. **This is normal — it is not an error and nothing
158+
is lost.**
159+
160+
5. Restart the PC.
161+
162+
6. Sign back in as the **same** Windows user and open PowerShell again.
163+
164+
7. Run the **identical command** a second time:
165+
166+
```powershell
167+
irm https://1helm.com/install.ps1 | iex
168+
```
169+
170+
8. Wait about six and a half minutes. Pages of `apt` output scroll past — that
171+
is normal progress. When it is done it prints the address and opens your
172+
default browser on the onboarding page.
173+
174+
9. Complete Captain → Providers → Workspace in the browser.
175+
176+
The whole thing takes about **9 minutes**, restart included.
177+
178+
Two things you may see along the way:
179+
180+
- Microsoft's own **"Welcome to WSL"** window may open during step 8. It belongs
181+
to Microsoft, it is harmless, and you can close it.
182+
- After 1Helm reports that it is running, the channel-computer runtime needs
183+
roughly another **40 seconds** to finish preparing before your first channel
184+
computer can be created. It has not hung.
185+
186+
If you *download* `install.ps1` instead of piping it, Windows blocks running
187+
`.ps1` files and you must run it as
188+
`powershell -NoProfile -ExecutionPolicy Bypass -File .\install.ps1`. The
189+
`irm | iex` form above is unaffected, because it pipes a string rather than
190+
executing a file.
191+
192+
One behaviour difference from earlier versions: **`#main`'s Terminal is now bash
193+
inside the WSL distribution, not `cmd.exe`.** Windows commands do not work
194+
there. That is deliberate — the host is Linux now.
195+
196+
To remove it later, from the same ordinary PowerShell window:
197+
198+
```powershell
199+
irm https://1helm.com/uninstall.ps1 | iex
200+
```
157201

158202
### Linux (Ubuntu/Debian, systemd)
159203

@@ -178,20 +222,26 @@ machines for safe deletion. Export irreplaceable channel files before you start.
178222

179223
- **macOS** — drag 1Helm to the Trash. `~/Library/Application Support/1Helm-OCI-v1`
180224
is preserved unless you delete it yourself.
181-
- **Windows** — uninstall from Settings → Apps → Installed apps. The uninstaller
182-
removes 1Helm's own containers, unregisters its WSL runtime, and deletes
183-
`%LOCALAPPDATA%\1Helm-Runtime`.
225+
- **Windows** — from an ordinary PowerShell window, run
226+
`irm https://1helm.com/uninstall.ps1 | iex`. It stops the keepalive, runs
227+
1Helm's own Linux uninstaller inside the distribution, then unregisters the
228+
`1helm` distribution and deletes `C:\1helm` and the Start Menu shortcut. It
229+
asks you to type `remove` first, because unregistering the distribution
230+
**destroys every channel's files and the database** — there is no undo and no
231+
copy is kept. Other WSL distributions on the PC are never touched.
184232
- **Linux** — `sudo /opt/1helm/uninstall-host.sh` removes the services, helper,
185233
and 1Helm-owned containers while preserving `/var/lib/1helm-oci-v1` for
186234
recovery.
187235

188236
### Release discipline
189237

190-
Mac, Linux, and Windows use one synchronized desktop release version. A release
191-
is held in full until the signed/notarized Mac DMG and updater ZIP, verified
192-
Linux host archive, and Windows Setup/Squirrel feed have all passed native
193-
install and update acceptance from the same source commit. Windows
194-
Authenticode status is disclosed in every release; v0.0.38 is `NotSigned`.
238+
Mac and Linux use one synchronized desktop release version, and a release is
239+
held in full until all three artifacts — the signed/notarized Mac DMG, the
240+
notarized Mac updater ZIP, and the verified Linux host archive — have passed
241+
native install and update acceptance from the same source commit. Windows ships
242+
no release artifacts at all: it installs the Linux build through
243+
`install.ps1`, served from the site, so there is nothing to sign and no
244+
Authenticode status to disclose.
195245

196246
### Connect from a phone or tablet
197247

@@ -324,9 +374,9 @@ and an audit trail. A prompt saying “use this service” is not a connector.
324374
SHA-256 chain for new operational events.
325375
- Local-first collaboration through an optional workspace domain routed to the
326376
Captain's helm host; workspace state and provider credentials remain there.
327-
- Host-owned updates: a signed native Mac updater, a Windows Squirrel feed with
328-
disclosed Authenticode status, and an atomic digest-verified Linux system
329-
service with health-check rollback.
377+
- Host-owned updates: a signed native Mac updater, and an atomic digest-verified
378+
Linux system service with health-check rollback — which is also how a Windows
379+
host updates, because Windows runs that same Linux service inside WSL.
330380
- Signed, Apple-notarized, stapled Apple Silicon DMG releases.
331381
- Browser access from phones and tablets to an already configured HTTPS 1Helm
332382
host; native mobile gateway source is present but has no v0.0.38 public build.
@@ -337,7 +387,7 @@ and an audit trail. A prompt saying “use this service” is not a connector.
337387
|---|---|
338388
| **Apple Silicon macOS 26** | Native desktop product and real isolated Linux computer per resident (Apple `container machine`, `home-mount=none`). |
339389
| **Linux / CI** | Supported headless systemd host with one durable Podman OCI container per resident, runtime-owned storage, and exact ownership checks; CI may select an explicit test backend. |
340-
| **Windows 11 x64** | Native desktop product with one installation-scoped WSL 2 OCI runtime and one durable container per resident; Windows-drive mounts and interop are disabled. |
390+
| **Windows 11 x64** | The Linux host, installed by `install.ps1` into a WSL 2 distribution named `1helm`, with the browser as the interface at `http://localhost:8123`. One durable Podman OCI container per resident, exactly as on Linux. No Windows application ships. |
341391
| **iPhone, iPad, and Android** | Use the current HTTPS browser interface. Native gateway source exists, but v0.0.38 has no public mobile artifact and the iOS app is not publicly listed. |
342392

343393
Not yet shipped: current public mobile builds, a native Linux desktop shell, a
@@ -365,7 +415,7 @@ A fresh data directory opens first-run setup. The source runtime defaults to
365415
| `PORT` | `8123` | HTTP/WebSocket control-plane port. |
366416
| `CTRL_DATA_DIR` | `./data` | Databases, routing state, uploads, and non-OCI development/Apple workspace mirrors. |
367417
| `HELM_CHANNEL_COMPUTER_BACKEND` | `apple` on macOS, `oci` on Linux and Windows | Host isolation backend; `native` and `mock` are explicit development/test overrides. |
368-
| `HELM_CHANNEL_MACHINE_IMAGE` | `local/1helm-channel-machine:0.0.38` | Versioned channel-machine image contract. |
418+
| `HELM_CHANNEL_MACHINE_IMAGE` | `local/1helm-channel-machine:0.0.39` | Versioned channel-machine image contract. |
369419

370420
### Agent-first JSON CLI
371421

@@ -383,17 +433,18 @@ npm run helm -- audit-verify
383433

384434
## Architecture
385435

386-
1Helm is a compact Node/TypeScript control plane hosted by Electron on macOS
387-
and in the accepted Windows implementation, or by systemd on Linux. It does not
388-
need an external database or a server transpilation step.
436+
1Helm is a compact Node/TypeScript control plane hosted by Electron on macOS, or
437+
by systemd on Linux — including the Linux inside a Windows host's WSL 2
438+
distribution. It does not need an external database or a server transpilation
439+
step.
389440

390441
| Layer | Implementation |
391442
|---|---|
392443
| Runtime | Official Node 22 with native TypeScript stripping. |
393444
| Control plane | `node:http`, WebSocket, additive SQLite migrations. |
394445
| Client | Vanilla TypeScript bundled with esbuild and Tailwind CSS. |
395446
| Model routing | Embedded ReRouted headless engine, private internal gateway, account pools, retries, routes, quotas, and logs. |
396-
| Computers | Defensive argv-only Apple `container machine`; native Podman OCI on Linux; one shared managed WSL 2/Podman runtime on Windows; explicit `native`/`mock` test seams. |
447+
| Computers | Defensive argv-only Apple `container machine`; native Podman OCI on Linux, including inside a Windows host's WSL 2 distribution; explicit `native`/`mock` test seams. |
397448
| Terminal | `node-pty`; ordinary terminals enter their channel VM while Skipper remains native. |
398449
| Memory | Curated records with provenance plus an isolated Mnemosyne SQLite store per identity. |
399450
| Scheduling | Durable obligations, wake reconciliation, lifecycle safety, repair, update, and pressure-aware sizing. |
@@ -430,9 +481,9 @@ the complete `npm test` contract.
430481

431482
## Security boundary
432483

433-
- Residents use separate Linux worlds: Apple machines with no Mac home mount,
434-
or durable OCI containers on Linux and inside Windows' one shared managed
435-
WSL runtime. Windows-drive mounts and interop are disabled.
484+
- Residents use separate Linux worlds: Apple machines with no Mac home mount, or
485+
durable OCI containers on Linux — including the Linux inside a Windows host's
486+
WSL 2 distribution.
436487
- Skipper's host tools require Captain-authorized provenance.
437488
- OCI workspace storage is runtime-owned and authoritative; Files and Cowork
438489
access it directly through a channel-scoped boundary. Apple mirrors remain

‎desktop/main.cjs‎

Lines changed: 5 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -41,34 +41,14 @@ function rememberDesktopMode(mode) {
4141
fs.writeFileSync(desktopModePath(), `${mode}\n`, { mode: 0o600 });
4242
}
4343

44-
// Windows packages ship application code inside app.asar; assets consumed by
45-
// external processes (Python, PowerShell, WSL, plain-Node sidecars) are
46-
// unpacked beside the archive. Translate paths for those consumers. Loose
47-
// packages (macOS, Linux, development) pass through unchanged.
44+
// An asar-packaged build keeps application code inside app.asar while assets
45+
// consumed by external processes (Python, plain-Node sidecars) are unpacked
46+
// beside the archive. Translate paths for those consumers. Loose packages
47+
// (macOS, Linux, development) pass through unchanged.
4848
function unpackedPath(target) {
4949
return String(target).replace(/app\.asar(?=[\\/]|$)/, "app.asar.unpacked");
5050
}
5151

52-
function handleSquirrelEvent() {
53-
if (process.platform !== "win32") return false;
54-
const event = process.argv[1];
55-
if (!["--squirrel-install", "--squirrel-updated", "--squirrel-uninstall", "--squirrel-obsolete"].includes(event)) return false;
56-
const appFolder = path.resolve(process.execPath, "..");
57-
const updateExe = path.resolve(appFolder, "..", "Update.exe");
58-
const exe = path.basename(process.execPath);
59-
if (event === "--squirrel-install" || event === "--squirrel-updated") {
60-
spawnSync(updateExe, ["--createShortcut", exe], { stdio: "ignore", windowsHide: true });
61-
} else if (event === "--squirrel-uninstall") {
62-
const dataRoot = app.getPath("userData");
63-
const wslRoot = path.join(String(process.env.LOCALAPPDATA || ""), "1Helm-Runtime");
64-
const cleanup = unpackedPath(path.resolve(__dirname, "..", "scripts", "windows-removal.cjs"));
65-
spawnSync(process.execPath, [cleanup, dataRoot, wslRoot], { env: { ...process.env, ELECTRON_RUN_AS_NODE: "1" }, stdio: "ignore", windowsHide: true, timeout: 10 * 60_000 });
66-
spawnSync(updateExe, ["--removeShortcut", exe], { stdio: "ignore", windowsHide: true });
67-
}
68-
setTimeout(() => app.quit(), 1000);
69-
return true;
70-
}
71-
7252
function preferredWorkspaceOrigin() {
7353
if (desktopMode() !== "client") return localOrigin;
7454
try {
@@ -310,10 +290,7 @@ function createWindow(showWhenReady = true) {
310290
mainWindow = window;
311291
}
312292

313-
if (handleSquirrelEvent()) {
314-
// Squirrel install/update/uninstall work must exit before the application
315-
// acquires its normal single-instance lock or starts the local server.
316-
} else if (!app.requestSingleInstanceLock()) {
293+
if (!app.requestSingleInstanceLock()) {
317294
app.quit();
318295
} else {
319296
app.on("second-instance", (_event, argv) => {
@@ -325,7 +302,6 @@ if (handleSquirrelEvent()) {
325302
});
326303

327304
app.whenReady().then(async () => {
328-
if (process.platform === "win32") app.setAppUserModelId("com.squirrel.1Helm.1Helm");
329305
session.defaultSession.setPermissionCheckHandler((webContents, permission, _origin, details) => microphonePermissionAllowed(webContents, permission, details));
330306
session.defaultSession.setPermissionRequestHandler(async (webContents, permission, callback, details) => {
331307
if (!microphonePermissionAllowed(webContents, permission, details)) { callback(false); return; }

0 commit comments

Comments
 (0)