diff --git a/CHANGELOG.md b/CHANGELOG.md index 5561ee7..46e9af9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.0.41] - 2026-08-03 + +### Fixed + +- Linux host updates failed and rolled back. `install-linux-units.sh` wrote the + systemd unit files with `install /dev/stdin`, which works from an operator + shell (every fresh install) but fails with ENOENT under `systemd-run` — the + only context the update path uses — so the transaction aborted and correctly + restored the prior release. Each unit file is now written via a temp file. A + fresh install was never affected; the fix is required for updates to succeed. + + ## [0.0.40] - 2026-08-03 ### Fixed @@ -1131,6 +1143,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.41]: https://github.com/gitcommit90/1Helm/compare/v0.0.40...v0.0.41 [0.0.40]: https://github.com/gitcommit90/1Helm/compare/v0.0.39...v0.0.40 [0.0.39]: https://github.com/gitcommit90/1Helm/compare/v0.0.30...v0.0.39 [0.0.38]: https://github.com/gitcommit90/1Helm/compare/v0.0.30...v0.0.38 diff --git a/README.md b/README.md index ea10846..1192f3d 100644 --- a/README.md +++ b/README.md @@ -415,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.40` | Versioned channel-machine image contract. | +| `HELM_CHANNEL_MACHINE_IMAGE` | `local/1helm-channel-machine:0.0.41` | Versioned channel-machine image contract. | ### Agent-first JSON CLI diff --git a/package-lock.json b/package-lock.json index cd06b25..2b3b99f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "1helm", - "version": "0.0.40", + "version": "0.0.41", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "1helm", - "version": "0.0.40", + "version": "0.0.41", "hasInstallScript": true, "license": "AGPL-3.0-only", "dependencies": { diff --git a/package.json b/package.json index 962e634..2fa1d20 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "1helm", "productName": "1Helm", - "version": "0.0.40", + "version": "0.0.41", "private": true, "type": "module", "license": "AGPL-3.0-only", diff --git a/site/server.mjs b/site/server.mjs index 5e689d8..0d69a6a 100644 --- a/site/server.mjs +++ b/site/server.mjs @@ -40,15 +40,16 @@ const RELEASE_CACHE_MS = 10 * 60_000; // 64 hex characters, so latestLinuxRelease() rejects it and // /api/releases/linux/latest answers 503 - failing closed rather than handing // an installer a digest that cannot match what it downloads. -const RELEASE_FALLBACK_TAG = "v0.0.40"; +const PENDING_DIGEST = "pending-release-digest"; +const RELEASE_FALLBACK_TAG = "v0.0.41"; const RELEASE_FALLBACK = { tag_name: RELEASE_FALLBACK_TAG, draft: false, prerelease: false, assets: [ - ["1Helm-0.0.40-arm64.dmg", "d96868530bbeb3708e82d027bd079c129bf064e16e3d1e7482a999dd5a1e439a"], - ["1Helm-0.0.40-mac-arm64.zip", "a18f7b3e6e4c1c5f8e060d47a2fd1cba6461e1aa8ccafbbb5a24d6e43051ccfd"], - ["1Helm-0.0.40-linux-node.tgz", "95267b23753628a843cec0de54380355a04a6c9826ae60c37ce0da41b41d95e7"], + ["1Helm-0.0.41-arm64.dmg", PENDING_DIGEST], + ["1Helm-0.0.41-mac-arm64.zip", PENDING_DIGEST], + ["1Helm-0.0.41-linux-node.tgz", PENDING_DIGEST], ].map(([name, digest]) => ({ name, digest: `sha256:${digest}`, diff --git a/src/server/channel-computers.ts b/src/server/channel-computers.ts index 97d1e52..458b55c 100644 --- a/src/server/channel-computers.ts +++ b/src/server/channel-computers.ts @@ -68,7 +68,7 @@ const APPLE_RUNTIME_VERSION = "1.1.0"; export const APPLE_RUNTIME_PACKAGE = `container-${APPLE_RUNTIME_VERSION}-installer-signed.pkg`; export const APPLE_RUNTIME_URL = `https://github.com/apple/container/releases/download/${APPLE_RUNTIME_VERSION}/${APPLE_RUNTIME_PACKAGE}`; export const APPLE_RUNTIME_SHA256 = "0ca1c42a2269c2557efb1d82b1b38ac553e6a3a3da1b1179c439bcee1e7d6714"; -export const DEFAULT_CHANNEL_IMAGE = process.env.HELM_CHANNEL_MACHINE_IMAGE || "local/1helm-channel-machine:0.0.40"; +export const DEFAULT_CHANNEL_IMAGE = process.env.HELM_CHANNEL_MACHINE_IMAGE || "local/1helm-channel-machine:0.0.41"; const CONTAINER_CANDIDATES = [process.env.HELM_CONTAINER_CLI, "/usr/local/bin/container", "/opt/homebrew/bin/container", "container"].filter(Boolean) as string[]; const OCI_RUNTIME_VERSION = "1helm-oci-runtime-v1"; const OCI_HELPER_CANDIDATES = [ diff --git a/src/server/db.ts b/src/server/db.ts index 8fdc0dc..9807c3b 100644 --- a/src/server/db.ts +++ b/src/server/db.ts @@ -939,7 +939,7 @@ export function migrate(): void { const platformBackend = process.platform === "darwin" ? "apple" : "oci"; const configuredBackend = String(process.env.HELM_CHANNEL_COMPUTER_BACKEND || platformBackend); const backend = ["apple", "oci", "native", "mock"].includes(configuredBackend) ? configuredBackend : platformBackend; - const image = String(process.env.HELM_CHANNEL_MACHINE_IMAGE || "local/1helm-channel-machine:0.0.40"); + const image = String(process.env.HELM_CHANNEL_MACHINE_IMAGE || "local/1helm-channel-machine:0.0.41"); for (const channel of q(`SELECT c.id FROM channels c JOIN agent_channels ac ON ac.channel_id=c.id WHERE c.kind='channel' AND c.status<>'deleted'`)) { const channelId = Number(channel.id); diff --git a/test/channel-computers.mjs b/test/channel-computers.mjs index d9b3d09..8c0295a 100644 --- a/test/channel-computers.mjs +++ b/test/channel-computers.mjs @@ -181,7 +181,7 @@ test("Apple channel-computer contract preserves isolation, files, wakes, archive test("runtime digest and packaged image recipe stay pinned", async () => { assert.equal(computers.APPLE_RUNTIME_SHA256, "0ca1c42a2269c2557efb1d82b1b38ac553e6a3a3da1b1179c439bcee1e7d6714"); assert.match(computers.APPLE_RUNTIME_URL, /\/1\.1\.0\/container-1\.1\.0-installer-signed\.pkg$/); - assert.equal(computers.DEFAULT_CHANNEL_IMAGE, "local/1helm-channel-machine:0.0.40"); + assert.equal(computers.DEFAULT_CHANNEL_IMAGE, "local/1helm-channel-machine:0.0.41"); const packaging = await readFile(join(root, "scripts", "package-mac-dmg.cjs"), "utf8"); assert.match(packaging, /container\(\?:\$\|\\\/\)/, "release packaging includes container/ image assets"); const image = await readFile(join(root, "container", "Containerfile"), "utf8");