# Tooling: one canonical install bundle per target, for online and offline alike

Each Release now attaches exactly one artifact per target: `penguin-{linux,darwin}-{x64,arm64}.tar.gz`, `penguin-universal.tar.gz` and `penguin-win32-x64.zip` are flat installer bundles, each sealing the native installer, the program payload (`payload.tar.gz` / `payload.zip`) and the payload's SHA256 checksum. Raw program archives and the five `*-offline` wrappers introduced in 0.1.5 are no longer published; `SHA256SUMS` covers exactly the six bundles.

Both installation modes consume the same file. Online, `install.sh` / `install.ps1` download the bundle, verify it against its published `.sha256` — checksums are now always mandatory; the online warn-and-skip fallback is gone — then open the flat outer layer and verify the payload checksum sealed inside before staging. Offline, the user transfers that one file, extracts it once and runs the bundled `./install.sh` (or double-clicks `install.cmd`); the installer finds the sibling payload by itself and verifies the same sealed checksum with no network access, so no separate checksum file needs to be carried.

Archive shape is probed by content, not filename: a top-level `payload.*` means bundle, anything else is treated as a program archive with a top-level `penguin/`. That single probe replaces the previous online/offline split and keeps pinned pre-0.1.6 versions and legacy local archives installable (see the batch's [backward-compatibility notes](2026-07-31-backward-compatibility.md)). On Windows, the flat outer layer means the initial extraction never creates deep paths — PowerShell 5.1's 260-character limit stops being reachable from the download directory — and the zip shape probe reads the entry list without extracting; only the installer expands the payload, straight into its short staging directory.

The canonical installers travel byte-identical inside the bundles — the generated POSIX wrapper entry (`penguin-installer.sh` + stub `install.sh`) is gone, replaced by sibling-payload detection in the one real installer. That detection is hardened to keep 0.1.5's "never trust archives beside a temporary script" property: it engages only for a real file actually named `install.sh`, and the penguin.ooo forwarder now stages its download in a private `mktemp -d` directory rather than a bare file in shared `/tmp`. The Windows forwarder already runs the installer as an in-memory script block and keeps its sibling detection offline-only.

In-place upgrades stop failing on filesystems that pin in-use directories. `penguin update` re-runs the installer while the CLI process is still executing out of `lib/`; on overlayfs under Docker the plain `mv lib .old.<pid>/lib` failed with `Device or resource busy` and rolled back. Directory moves now degrade through three strategies — whole-directory rename, per-entry renames into a fresh or existing destination, then per-entry copy with the source cleared (POSIX keeps an unlinked-but-open file valid for the process using it) — with a pinned, empty directory husk reused by the incoming move, rollback following the same rules, and a failure message that says to stop running penguin processes. A hermetic test stubs `mv`/`rmdir` to refuse directory renames touching the installed `lib/` and verifies the upgrade completes cleanly.

Two Windows papercuts are fixed at the root. The payload no longer ships a `penguin.ps1` launcher: PowerShell prefers `.ps1` over `.cmd` on PATH and client Windows defaults to the Restricted execution policy, so a fresh install's plain `penguin` command failed with "running scripts is disabled"; batch files are policy-exempt, both PowerShell and cmd.exe resolve `penguin.cmd`, and since upgrades swap `bin\` wholesale, re-running the installer also removes the old shim (see the batch's [backward-compatibility notes](2026-07-31-backward-compatibility.md)). And after appending to the user Path registry value, the installer now broadcasts `WM_SETTINGCHANGE` — a raw registry write alone left Explorer, and every terminal launched from it, on the stale Path until the next logon, which is why a brand-new PowerShell window still could not find `penguin`; the completion note now says to open a new terminal window rather than a new tab.

`scripts/package-release-bundles.sh` replaces `package-offline-bundles.sh`, and the release workflow now builds payloads as intermediates, packages the six bundles, and validates the real outputs before upload: outer checksum, exact flat member set, byte-identical installer and payload, and a passing sealed checksum. `scripts/test-installer.sh` and `scripts/test-installer.ps1` replace the offline-only test scripts and hermetically cover bundle layout, offline installs with network access stubbed to fail, corrupted-payload rejection, upgrade rollback, both checksum layers, no-fallback download failures and pinned legacy versions, wired into the existing Linux and Windows CI jobs. README, the docs Installation page and the landing install copy describe the single-artifact flow.
