Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,67 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.0.39] - 2026-08-03

### Changed

- **Windows no longer ships an application.** 1Helm on Windows now runs the
ordinary Linux build inside a WSL 2 distribution and serves its interface to
the browser at `http://localhost:8123`. Install it with one command in an
ordinary PowerShell window:

```powershell
irm https://1helm.com/install.ps1 | iex
```

There is no Electron host, no Squirrel installer, no `.exe`, and therefore
nothing to code-sign — so SmartScreen never appears. Windows publishes no
release artifacts; the desktop matrix is now three files (macOS DMG, macOS
updater ZIP, Linux archive), and the Linux archive serves both Linux and
Windows.
- Windows setup asks for administrator approval once, to enable the WSL 2
optional features and install Microsoft's digest- and signature-verified WSL
package. Everything else — importing the distribution, installing 1Helm,
registering the keepalive — runs as the signed-in user, because WSL state is
per-user.
- Windows requires one restart partway through setup, which Windows itself
demands before WSL 2 becomes usable. Setup reports that as a restart with
numbered steps rather than a failure, and re-running the same command
continues from where it stopped.
- `#main`'s terminal on Windows is now bash inside the distribution rather than
`cmd.exe`, matching Linux.
- Removing 1Helm from Windows is `irm https://1helm.com/uninstall.ps1 | iex`.

### Fixed

- Windows file operations are roughly four times faster. Every channel storage
operation previously crossed the Windows-to-WSL boundary through
`wsl.exe`, costing a flat ~208 ms per call — measured at 281 ms versus 73 ms
for the same work without the crossing. Those crossings no longer exist,
because the server now runs inside the distribution.
- The Windows interface can no longer freeze. Those boundary crossings were
synchronous calls on the Electron main thread, which is the thread Windows
requires for its message pump, so a file listing could stall the window past
the five seconds after which Windows reports "not responding". There is no
longer a window to freeze: the browser waits on an HTTP request instead.
- Linux and Windows installs no longer build 1Helm on the target machine. The
release archive now ships production dependencies and prebuilt assets, with
native addons compiled against an older glibc and verified on arrival by
loading each one and checking its Node ABI. A cold install went from 8m49s to
3m40s, and no C/C++ toolchain is installed on the host any more.
- The Linux installer no longer reports success when another process holds port
8123. Its readiness check required only that something answered, which a
foreign listener satisfies; it now also requires the unit to be active, and
refuses to start when the port is already taken.
- A version mismatch between installer and archive failed silently after
several minutes of work. It now names both versions and states that nothing
was installed.
- The website no longer requires a Windows Setup executable, `.nupkg` and
`RELEASES` to exist before it will serve release metadata. That requirement
backed the endpoint the Linux installer resolves, so the first release
without those files would have broken the public Linux and Windows
installers simultaneously.

## [0.0.38] - 2026-08-02

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

[Unreleased]: https://github.com/gitcommit90/1Helm/compare/v0.0.36...HEAD
[0.0.39]: https://github.com/gitcommit90/1Helm/compare/v0.0.30...v0.0.39

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Link the release to the immediate predecessor.

Line 1102 compares v0.0.30 through v0.0.39. This includes the changes already represented by versions 0.0.31 through 0.0.38. Change the left tag to v0.0.38.

Proposed fix
-[0.0.39]: https://github.com/gitcommit90/1Helm/compare/v0.0.30...v0.0.39
+[0.0.39]: https://github.com/gitcommit90/1Helm/compare/v0.0.38...v0.0.39
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
[0.0.39]: https://github.com/gitcommit90/1Helm/compare/v0.0.30...v0.0.39
[0.0.39]: https://github.com/gitcommit90/1Helm/compare/v0.0.38...v0.0.39
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@CHANGELOG.md` at line 1102, Update the [0.0.39] changelog comparison link to
use v0.0.38 as the left-hand predecessor, while keeping v0.0.39 as the
right-hand tag.

[0.0.38]: https://github.com/gitcommit90/1Helm/compare/v0.0.30...v0.0.38
[0.0.37]: https://github.com/gitcommit90/1Helm/compare/v0.0.30...v0.0.37
[0.0.36]: https://github.com/gitcommit90/1Helm/compare/v0.0.35...v0.0.36
Expand Down
141 changes: 96 additions & 45 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
<p align="center">
<a href="https://1helm.com/download/macos"><strong>Download for Mac</strong></a>
&nbsp;·&nbsp;
<a href="https://1helm.com/download/windows"><strong>for Windows</strong></a>
<a href="https://1helm.com/manual/install-windows"><strong>for Windows</strong></a>
&nbsp;·&nbsp;
<a href="https://1helm.com/manual/install-linux"><strong>for Linux</strong></a>
&nbsp;·&nbsp;
Expand Down Expand Up @@ -130,30 +130,74 @@ device displaying the web UI—to download and verify the signed update.

### Windows 11 x64

1. [Download the current Setup executable](https://1helm.com/download/windows).
2. Windows Setup is **not yet Authenticode signed**, so SmartScreen shows
"Windows protected your PC". Choose **More info** → **Run anyway**.
3. Open 1Helm and complete Captain → Providers → Workspace.
4. At the Workspace step, 1Helm builds its WSL 2 runtime. **You do not need WSL
installed beforehand.** A stock Windows 11 ships with WSL and
VirtualMachinePlatform turned off, and 1Helm enables both itself:
- Approve the one administrator (UAC) prompt.
- A PowerShell window opens and reports progress. **Leave it open** until it
finishes.
- Because those Windows features were just turned on, **Windows has to
restart once.** 1Helm says so in plain language. This is the normal path
on a new PC — it is not an error, and nothing is lost.
- Restart, sign back in as the **same** Windows user, then open 1Helm and
continue setup. It picks up where it left off; already-completed steps are
skipped.

Setup downloads Microsoft's pinned WSL 2 package and a pinned Ubuntu 24.04 root
filesystem, both SHA-256 verified, and the WSL package is additionally checked
for a valid Microsoft Authenticode signature. The result is one
installation-scoped WSL 2 runtime hosting one durable OCI container per ordinary
channel, with Windows-drive mounts and process interop disabled. App state lives
in `%APPDATA%\1Helm-OCI-v1`; the shared runtime disk lives in
`%LOCALAPPDATA%\1Helm-Runtime`.
There is nothing to download and no Windows application to install. 1Helm runs
its ordinary **Linux** build inside a WSL 2 distribution named `1helm`, and your
**browser is the interface**, at `http://localhost:8123`. Because no `.exe`
ships, nothing needs code signing and **SmartScreen never appears**.

You need: Windows 11 **x64** (arm64 is not supported), virtualization enabled in
firmware, internet access, and roughly 10 GB of free disk. **You do not need to
install WSL first** — this does it for you.

1. Open **PowerShell**. The normal one — do **not** use "Run as
Administrator".

2. Run exactly this:

```powershell
irm https://1helm.com/install.ps1 | iex
```

3. A Windows permission pop-up appears. Click **Yes**. That one approval covers
only turning on Windows' WSL features and installing Microsoft's own WSL
package; everything after it deliberately runs as you, because WSL state
belongs to the signed-in user.

4. Wait about a minute. The window finishes by printing **"Restart required"**
and a short numbered list. **This is normal — it is not an error and nothing
is lost.**

5. Restart the PC.

6. Sign back in as the **same** Windows user and open PowerShell again.

7. Run the **identical command** a second time:

```powershell
irm https://1helm.com/install.ps1 | iex
```

8. Wait about six and a half minutes. Pages of `apt` output scroll past — that
is normal progress. When it is done it prints the address and opens your
default browser on the onboarding page.

9. Complete Captain → Providers → Workspace in the browser.

The whole thing takes about **9 minutes**, restart included.

Two things you may see along the way:

- Microsoft's own **"Welcome to WSL"** window may open during step 8. It belongs
to Microsoft, it is harmless, and you can close it.
- After 1Helm reports that it is running, the channel-computer runtime needs
roughly another **40 seconds** to finish preparing before your first channel
computer can be created. It has not hung.

If you *download* `install.ps1` instead of piping it, Windows blocks running
`.ps1` files and you must run it as
`powershell -NoProfile -ExecutionPolicy Bypass -File .\install.ps1`. The
`irm | iex` form above is unaffected, because it pipes a string rather than
executing a file.

One behaviour difference from earlier versions: **`#main`'s Terminal is now bash
inside the WSL distribution, not `cmd.exe`.** Windows commands do not work
there. That is deliberate — the host is Linux now.

To remove it later, from the same ordinary PowerShell window:

```powershell
irm https://1helm.com/uninstall.ps1 | iex
```

### Linux (Ubuntu/Debian, systemd)

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

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

### Release discipline

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

### Connect from a phone or tablet

Expand Down Expand Up @@ -324,9 +374,9 @@ and an audit trail. A prompt saying “use this service” is not a connector.
SHA-256 chain for new operational events.
- Local-first collaboration through an optional workspace domain routed to the
Captain's helm host; workspace state and provider credentials remain there.
- Host-owned updates: a signed native Mac updater, a Windows Squirrel feed with
disclosed Authenticode status, and an atomic digest-verified Linux system
service with health-check rollback.
- Host-owned updates: a signed native Mac updater, and an atomic digest-verified
Linux system service with health-check rollback — which is also how a Windows
host updates, because Windows runs that same Linux service inside WSL.
- Signed, Apple-notarized, stapled Apple Silicon DMG releases.
- Browser access from phones and tablets to an already configured HTTPS 1Helm
host; native mobile gateway source is present but has no v0.0.38 public build.
Expand All @@ -337,7 +387,7 @@ and an audit trail. A prompt saying “use this service” is not a connector.
|---|---|
| **Apple Silicon macOS 26** | Native desktop product and real isolated Linux computer per resident (Apple `container machine`, `home-mount=none`). |
| **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. |
| **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. |
| **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. |
| **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. |

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

### Agent-first JSON CLI

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

## Architecture

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

| Layer | Implementation |
|---|---|
| Runtime | Official Node 22 with native TypeScript stripping. |
| Control plane | `node:http`, WebSocket, additive SQLite migrations. |
| Client | Vanilla TypeScript bundled with esbuild and Tailwind CSS. |
| Model routing | Embedded ReRouted headless engine, private internal gateway, account pools, retries, routes, quotas, and logs. |
| 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. |
| 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. |
| Terminal | `node-pty`; ordinary terminals enter their channel VM while Skipper remains native. |
| Memory | Curated records with provenance plus an isolated Mnemosyne SQLite store per identity. |
| Scheduling | Durable obligations, wake reconciliation, lifecycle safety, repair, update, and pressure-aware sizing. |
Expand Down Expand Up @@ -430,9 +481,9 @@ the complete `npm test` contract.

## Security boundary

- Residents use separate Linux worlds: Apple machines with no Mac home mount,
or durable OCI containers on Linux and inside Windows' one shared managed
WSL runtime. Windows-drive mounts and interop are disabled.
- Residents use separate Linux worlds: Apple machines with no Mac home mount, or
durable OCI containers on Linux — including the Linux inside a Windows host's
WSL 2 distribution.
- Skipper's host tools require Captain-authorized provenance.
- OCI workspace storage is runtime-owned and authoritative; Files and Cowork
access it directly through a channel-scoped boundary. Apple mirrors remain
Expand Down
34 changes: 5 additions & 29 deletions desktop/main.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -41,34 +41,14 @@ function rememberDesktopMode(mode) {
fs.writeFileSync(desktopModePath(), `${mode}\n`, { mode: 0o600 });
}

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

function handleSquirrelEvent() {
if (process.platform !== "win32") return false;
const event = process.argv[1];
if (!["--squirrel-install", "--squirrel-updated", "--squirrel-uninstall", "--squirrel-obsolete"].includes(event)) return false;
const appFolder = path.resolve(process.execPath, "..");
const updateExe = path.resolve(appFolder, "..", "Update.exe");
const exe = path.basename(process.execPath);
if (event === "--squirrel-install" || event === "--squirrel-updated") {
spawnSync(updateExe, ["--createShortcut", exe], { stdio: "ignore", windowsHide: true });
} else if (event === "--squirrel-uninstall") {
const dataRoot = app.getPath("userData");
const wslRoot = path.join(String(process.env.LOCALAPPDATA || ""), "1Helm-Runtime");
const cleanup = unpackedPath(path.resolve(__dirname, "..", "scripts", "windows-removal.cjs"));
spawnSync(process.execPath, [cleanup, dataRoot, wslRoot], { env: { ...process.env, ELECTRON_RUN_AS_NODE: "1" }, stdio: "ignore", windowsHide: true, timeout: 10 * 60_000 });
spawnSync(updateExe, ["--removeShortcut", exe], { stdio: "ignore", windowsHide: true });
}
setTimeout(() => app.quit(), 1000);
return true;
}

function preferredWorkspaceOrigin() {
if (desktopMode() !== "client") return localOrigin;
try {
Expand Down Expand Up @@ -310,10 +290,7 @@ function createWindow(showWhenReady = true) {
mainWindow = window;
}

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

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