Skip to content

Commit e8bb0bf

Browse files
committed
Fix fresh Linux connector and Windows reboot setup
1 parent e5796ec commit e8bb0bf

13 files changed

Lines changed: 111 additions & 21 deletions

CHANGELOG.md

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

88
## [Unreleased]
99

10+
## [0.0.34] - 2026-08-01
11+
12+
### Fixed
13+
14+
- Fixed fresh Linux collaboration so the systemd service resolves the
15+
architecture-specific Cloudflare connector already shipped inside the
16+
verified host archive.
17+
- Fixed fresh Windows setup so enabling WSL 2 features always stops at the
18+
required reboot boundary, and mapped the Windows VM-compute-not-ready import
19+
response to that same actionable restart state.
20+
- Made Windows retries safely recover an app-owned partial shared-runtime
21+
import left behind when Windows required the feature-activation reboot.
22+
1023
## [0.0.33] - 2026-08-01
1124

1225
### Fixed
@@ -955,7 +968,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
955968
notarization, stapled tickets, Gatekeeper verification, persistent
956969
Application Support, and isolated Apple container machines.
957970

958-
[Unreleased]: https://github.com/gitcommit90/1Helm/compare/v0.0.33...HEAD
971+
[Unreleased]: https://github.com/gitcommit90/1Helm/compare/v0.0.34...HEAD
972+
[0.0.34]: https://github.com/gitcommit90/1Helm/compare/v0.0.33...v0.0.34
959973
[0.0.33]: https://github.com/gitcommit90/1Helm/compare/v0.0.32...v0.0.33
960974
[0.0.32]: https://github.com/gitcommit90/1Helm/compare/v0.0.31...v0.0.32
961975
[0.0.31]: https://github.com/gitcommit90/1Helm/compare/v0.0.30...v0.0.31

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -313,7 +313,7 @@ A fresh data directory opens first-run setup. The source runtime defaults to
313313
| `PORT` | `8123` | HTTP/WebSocket control-plane port. |
314314
| `CTRL_DATA_DIR` | `./data` | Databases, routing state, uploads, and non-OCI development/Apple workspace mirrors. |
315315
| `HELM_CHANNEL_COMPUTER_BACKEND` | `apple` on macOS, `oci` on Linux and Windows | Host isolation backend; `native` and `mock` are explicit development/test overrides. |
316-
| `HELM_CHANNEL_MACHINE_IMAGE` | `local/1helm-channel-machine:0.0.33` | Versioned channel-machine image contract. |
316+
| `HELM_CHANNEL_MACHINE_IMAGE` | `local/1helm-channel-machine:0.0.34` | Versioned channel-machine image contract. |
317317

318318
### Agent-first JSON CLI
319319

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "1helm",
33
"productName": "1Helm",
4-
"version": "0.0.33",
4+
"version": "0.0.34",
55
"private": true,
66
"type": "module",
77
"license": "AGPL-3.0-only",

scripts/install-wsl-runtime.ps1

Lines changed: 46 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,18 @@ function Test-RestartRequired {
6666
return $value -eq "Required" -or $value -eq "1" -or $value -eq "True"
6767
}
6868

69+
function Test-WslRestartFailure {
70+
param([string]$Text)
71+
return $Text -match 'HCS_E_SERVICE_NOT_AVAILABLE|required feature is not installed'
72+
}
73+
74+
function Require-WindowsRestart {
75+
$message = "WSL 2 features are enabled. Restart Windows once, then retry 1Helm computer setup."
76+
Write-SetupStatus -Status "restart_required" -Step $message -Progress 20 -ErrorMessage "Windows restart required to finish enabling WSL 2."
77+
Write-Host $message
78+
exit 10
79+
}
80+
6981
function Get-WslDistributionNames {
7082
$result = Get-WslText -ArgumentList @("--list", "--quiet")
7183
if ($result.ExitCode -ne 0) { return @() }
@@ -123,11 +135,16 @@ if ($HostSetup) {
123135
Write-SetupStatus -Status "running" -Step "Enabling Windows WSL features..." -Progress 8
124136
$wslFeature = Get-WindowsOptionalFeature -Online -FeatureName Microsoft-Windows-Subsystem-Linux
125137
$vmFeature = Get-WindowsOptionalFeature -Online -FeatureName VirtualMachinePlatform
126-
if ($wslFeature.State -ne "Enabled") { Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Windows-Subsystem-Linux -All -NoRestart | Out-Null }
127-
if ($vmFeature.State -ne "Enabled") { Enable-WindowsOptionalFeature -Online -FeatureName VirtualMachinePlatform -All -NoRestart | Out-Null }
138+
$enabledWslFeatureNow = $wslFeature.State -ne "Enabled"
139+
$enabledVmFeatureNow = $vmFeature.State -ne "Enabled"
140+
if ($enabledWslFeatureNow) { Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Windows-Subsystem-Linux -All -NoRestart | Out-Null }
141+
if ($enabledVmFeatureNow) { Enable-WindowsOptionalFeature -Online -FeatureName VirtualMachinePlatform -All -NoRestart | Out-Null }
128142
$wslFeature = Get-WindowsOptionalFeature -Online -FeatureName Microsoft-Windows-Subsystem-Linux
129143
$vmFeature = Get-WindowsOptionalFeature -Online -FeatureName VirtualMachinePlatform
130-
$restartRequired = (Test-RestartRequired $wslFeature) -or (Test-RestartRequired $vmFeature)
144+
# DISM's RestartRequired enum can stringify as "Possible" even though its
145+
# numeric value is 1. Enabling either feature in this invocation is itself
146+
# authoritative evidence that Windows must reboot before a WSL 2 VM import.
147+
$restartRequired = $enabledWslFeatureNow -or $enabledVmFeatureNow -or (Test-RestartRequired $wslFeature) -or (Test-RestartRequired $vmFeature)
131148
$hostTemporary = Join-Path ([System.IO.Path]::GetTempPath()) ("1helm-wsl-host-" + [Guid]::NewGuid().ToString("N"))
132149
New-Item -ItemType Directory -Path $hostTemporary | Out-Null
133150
try {
@@ -160,14 +177,20 @@ if ($HostSetup) {
160177
} else {
161178
Write-SetupStatus -Status "running" -Step "Microsoft WSL $wslVersion is already installed." -Progress 18
162179
}
180+
# A feature may already report Enabled before the reboot has registered
181+
# WSL's VM compute service. This is the concrete pre-reboot state that
182+
# otherwise lets setup continue into HCS_E_SERVICE_NOT_AVAILABLE.
183+
if ($null -eq (Get-Service -Name vmcompute -ErrorAction SilentlyContinue)) { $restartRequired = $true }
163184
if ($restartRequired) {
164-
Write-SetupStatus -Status "restart_required" -Step "WSL 2 features are enabled. Restart Windows once, then retry 1Helm computer setup." -Progress 20
165-
exit 10
185+
Require-WindowsRestart
166186
}
167187
if (-not (Test-PinnedWslRuntime)) { Fail-Setup "Microsoft WSL $wslVersion was installed but could not be verified." }
168188
Write-SetupStatus -Status "running" -Step "Setting WSL 2 as the default..." -Progress 22
169189
$defaultVersion = Get-WslText -ArgumentList @("--set-default-version", "2")
170-
if ($defaultVersion.ExitCode -ne 0) { Fail-Setup "WSL could not set version 2 as the default. $($defaultVersion.Text)" }
190+
if ($defaultVersion.ExitCode -ne 0) {
191+
if (Test-WslRestartFailure $defaultVersion.Text) { Require-WindowsRestart }
192+
Fail-Setup "WSL could not set version 2 as the default. $($defaultVersion.Text)"
193+
}
171194
} finally {
172195
if (Test-Path -LiteralPath $hostTemporary) { Remove-Item -LiteralPath $hostTemporary -Recurse -Force }
173196
}
@@ -242,11 +265,22 @@ try {
242265
$names = @(Get-WslDistributionNames)
243266
$runtimeRoot = Join-Path $env:LOCALAPPDATA "1Helm-Runtime"
244267
$installDirectory = Join-Path $runtimeRoot $RuntimeName
268+
$partialMarker = "$installDirectory.1helm-partial-import"
245269
if ($names -notcontains $RuntimeName) {
246270
if (Test-Path -LiteralPath $installDirectory) {
247-
Fail-Setup "The shared runtime disk directory already exists without a registered runtime. Remove `"$installDirectory`" or unregister the partial distro, then retry."
271+
$entries = @(Get-ChildItem -LiteralPath $installDirectory -Force -ErrorAction SilentlyContinue)
272+
$ownedPartial = (Test-Path -LiteralPath $partialMarker -PathType Leaf) -and ((Get-Content -LiteralPath $partialMarker -Raw).Trim() -eq $RuntimeName)
273+
# v0.0.33 could leave an empty app-owned directory when Windows rejected
274+
# the import before creating its VM. New attempts carry an ownership
275+
# marker so an interrupted partial VHD can also be retried safely.
276+
if ($entries.Count -eq 0 -or $ownedPartial) {
277+
Remove-Item -LiteralPath $installDirectory -Recurse -Force
278+
} else {
279+
Fail-Setup "The shared runtime disk directory already exists without a registered runtime. Remove `"$installDirectory`" or unregister the partial distro, then retry."
280+
}
248281
}
249282
New-Item -ItemType Directory -Path $installDirectory -Force | Out-Null
283+
[System.IO.File]::WriteAllText($partialMarker, $RuntimeName, [System.Text.UTF8Encoding]::new($false))
250284
$rootfs = Join-Path $temporary "ubuntu-noble-wsl.rootfs.tar.gz"
251285
Write-SetupStatus -Status "running" -Step "Downloading shared Linux runtime base..." -Progress 35
252286
Fetch-File -Url $rootfsUrl -Destination $rootfs
@@ -255,7 +289,11 @@ try {
255289
}
256290
Write-SetupStatus -Status "running" -Step "Importing shared Linux runtime..." -Progress 48
257291
$imported = Get-WslText -ArgumentList @("--import", $RuntimeName, $installDirectory, $rootfs, "--version", "2")
258-
if ($imported.ExitCode -ne 0) { Fail-Setup "The shared 1Helm WSL runtime could not be imported. $($imported.Text)" }
292+
if ($imported.ExitCode -ne 0) {
293+
if (Test-WslRestartFailure $imported.Text) { Require-WindowsRestart }
294+
Fail-Setup "The shared 1Helm WSL runtime could not be imported. $($imported.Text)"
295+
}
296+
Remove-Item -LiteralPath $partialMarker -Force
259297
}
260298

261299
Write-SetupStatus -Status "running" -Step "Installing shared runtime packages (podman, crun, ...)..." -Progress 58

site/public/install-linux-units.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ Environment=NODE_ENV=production
6565
Environment=PORT=8123
6666
Environment=HELM_HOST=0.0.0.0
6767
Environment=CTRL_DATA_DIR=$STATE_ROOT
68+
Environment=HELM_APP_ROOT=$INSTALL_ROOT/current
6869
Environment=HELM_CHANNEL_COMPUTER_BACKEND=oci
6970
Environment=HELM_OCI_HELPER=/usr/libexec/1helm-oci-runtime
7071
Environment=HELM_INSTALL_KIND=linux-systemd

src/server/channel-computers.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ const APPLE_RUNTIME_VERSION = "1.1.0";
6868
export const APPLE_RUNTIME_PACKAGE = `container-${APPLE_RUNTIME_VERSION}-installer-signed.pkg`;
6969
export const APPLE_RUNTIME_URL = `https://github.com/apple/container/releases/download/${APPLE_RUNTIME_VERSION}/${APPLE_RUNTIME_PACKAGE}`;
7070
export const APPLE_RUNTIME_SHA256 = "0ca1c42a2269c2557efb1d82b1b38ac553e6a3a3da1b1179c439bcee1e7d6714";
71-
export const DEFAULT_CHANNEL_IMAGE = process.env.HELM_CHANNEL_MACHINE_IMAGE || "local/1helm-channel-machine:0.0.33";
71+
export const DEFAULT_CHANNEL_IMAGE = process.env.HELM_CHANNEL_MACHINE_IMAGE || "local/1helm-channel-machine:0.0.34";
7272
const CONTAINER_CANDIDATES = [process.env.HELM_CONTAINER_CLI, "/usr/local/bin/container", "/opt/homebrew/bin/container", "container"].filter(Boolean) as string[];
7373
const OCI_RUNTIME_VERSION = "1helm-oci-runtime-v1";
7474
const OCI_HELPER_CANDIDATES = [

src/server/connectors.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@ let shuttingDown = false;
1616

1717
function connectorBinary(): string {
1818
const resources = process.env.HELM_RESOURCES_PATH || "";
19-
const appRoot = process.env.HELM_APP_ROOT || "";
19+
// Linux systemd releases run with /opt/1helm/current as their working
20+
// directory. Keep that installed-root contract usable even if an older unit
21+
// omitted HELM_APP_ROOT, while preferring the explicit packaged root.
22+
const appRoots = [...new Set([process.env.HELM_APP_ROOT || "", process.cwd()].filter(Boolean))];
2023
const pathSep = process.platform === "win32" ? ";" : ":";
2124
const pathNames = process.platform === "win32" ? ["cloudflared.exe", "cloudflared"] : ["cloudflared"];
2225
const linuxConnector = process.platform === "linux" && (process.arch === "x64" || process.arch === "arm64")
@@ -28,9 +31,11 @@ function connectorBinary(): string {
2831
// Packaged desktop apps (macOS Resources/cloudflared, Windows resources/cloudflared.exe).
2932
resources ? join(resources, "cloudflared.exe") : "",
3033
resources ? join(resources, "cloudflared") : "",
31-
appRoot ? join(appRoot, "cloudflared.exe") : "",
32-
appRoot ? join(appRoot, "cloudflared") : "",
33-
appRoot && linuxConnector ? join(appRoot, "resources", linuxConnector) : "",
34+
...appRoots.flatMap((appRoot) => [
35+
join(appRoot, "cloudflared.exe"),
36+
join(appRoot, "cloudflared"),
37+
linuxConnector ? join(appRoot, "resources", linuxConnector) : "",
38+
]),
3439
"/opt/homebrew/bin/cloudflared",
3540
"/usr/local/bin/cloudflared",
3641
"/usr/bin/cloudflared",

src/server/db.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -939,7 +939,7 @@ export function migrate(): void {
939939
const platformBackend = process.platform === "darwin" ? "apple" : "oci";
940940
const configuredBackend = String(process.env.HELM_CHANNEL_COMPUTER_BACKEND || platformBackend);
941941
const backend = ["apple", "oci", "native", "mock"].includes(configuredBackend) ? configuredBackend : platformBackend;
942-
const image = String(process.env.HELM_CHANNEL_MACHINE_IMAGE || "local/1helm-channel-machine:0.0.33");
942+
const image = String(process.env.HELM_CHANNEL_MACHINE_IMAGE || "local/1helm-channel-machine:0.0.34");
943943
for (const channel of q(`SELECT c.id FROM channels c JOIN agent_channels ac ON ac.channel_id=c.id
944944
WHERE c.kind='channel' AND c.status<>'deleted'`)) {
945945
const channelId = Number(channel.id);

test/channel-computers.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ test("Apple channel-computer contract preserves isolation, files, wakes, archive
181181
test("runtime digest and packaged image recipe stay pinned", async () => {
182182
assert.equal(computers.APPLE_RUNTIME_SHA256, "0ca1c42a2269c2557efb1d82b1b38ac553e6a3a3da1b1179c439bcee1e7d6714");
183183
assert.match(computers.APPLE_RUNTIME_URL, /\/1\.1\.0\/container-1\.1\.0-installer-signed\.pkg$/);
184-
assert.equal(computers.DEFAULT_CHANNEL_IMAGE, "local/1helm-channel-machine:0.0.33");
184+
assert.equal(computers.DEFAULT_CHANNEL_IMAGE, "local/1helm-channel-machine:0.0.34");
185185
const packaging = await readFile(join(root, "scripts", "package-mac-dmg.cjs"), "utf8");
186186
assert.match(packaging, /container\(\?:\$\|\\\/\)/, "release packaging includes container/ image assets");
187187
const image = await readFile(join(root, "container", "Containerfile"), "utf8");

0 commit comments

Comments
 (0)