botmux 3.18.4 → 3.18.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/dist/.runtime-build-id +1 -1
  2. package/dist/cli.d.ts.map +1 -1
  3. package/dist/cli.js +130 -21
  4. package/dist/cli.js.map +1 -1
  5. package/dist/core/binary-install-shape.d.ts +77 -0
  6. package/dist/core/binary-install-shape.d.ts.map +1 -0
  7. package/dist/core/binary-install-shape.js +117 -0
  8. package/dist/core/binary-install-shape.js.map +1 -0
  9. package/dist/core/binary-self-update.d.ts +82 -0
  10. package/dist/core/binary-self-update.d.ts.map +1 -0
  11. package/dist/core/binary-self-update.js +239 -0
  12. package/dist/core/binary-self-update.js.map +1 -0
  13. package/dist/core/maintenance.d.ts +88 -1
  14. package/dist/core/maintenance.d.ts.map +1 -1
  15. package/dist/core/maintenance.js +149 -8
  16. package/dist/core/maintenance.js.map +1 -1
  17. package/dist/core/release-download.d.ts +13 -0
  18. package/dist/core/release-download.d.ts.map +1 -0
  19. package/dist/core/release-download.js +130 -0
  20. package/dist/core/release-download.js.map +1 -0
  21. package/dist/dashboard/hd2d-assets.d.ts +2 -3
  22. package/dist/dashboard/hd2d-assets.d.ts.map +1 -1
  23. package/dist/dashboard/hd2d-assets.js +7 -109
  24. package/dist/dashboard/hd2d-assets.js.map +1 -1
  25. package/dist/dashboard/web/app.d.ts.map +1 -1
  26. package/dist/dashboard/web/app.js +7 -1
  27. package/dist/dashboard/web/app.js.map +1 -1
  28. package/dist/dashboard/web/settings-page.d.ts.map +1 -1
  29. package/dist/dashboard/web/settings-page.js.map +1 -1
  30. package/dist/dashboard-web/app.js +1 -1
  31. package/dist/dashboard.js +120 -13
  32. package/dist/dashboard.js.map +1 -1
  33. package/dist/setup/open-platform-automation.d.ts.map +1 -1
  34. package/dist/setup/open-platform-automation.js +9 -12
  35. package/dist/setup/open-platform-automation.js.map +1 -1
  36. package/dist/utils/file-lock.d.ts +25 -0
  37. package/dist/utils/file-lock.d.ts.map +1 -1
  38. package/dist/utils/file-lock.js +34 -4
  39. package/dist/utils/file-lock.js.map +1 -1
  40. package/dist/utils/global-install.d.ts +19 -0
  41. package/dist/utils/global-install.d.ts.map +1 -1
  42. package/dist/utils/global-install.js +28 -1
  43. package/dist/utils/global-install.js.map +1 -1
  44. package/dist/utils/install-info.d.ts +37 -1
  45. package/dist/utils/install-info.d.ts.map +1 -1
  46. package/dist/utils/install-info.js +45 -3
  47. package/dist/utils/install-info.js.map +1 -1
  48. package/package.json +7 -7
@@ -0,0 +1,77 @@
1
+ /**
2
+ * How the running compiled binary was installed, and therefore who owns updating it.
3
+ *
4
+ * · `npm-binary` — inside an npm/pnpm/Bun platform subpackage
5
+ * (`botmux-<plat>-<arch>[-musl]`). The manager owns the file;
6
+ * update by re-running it.
7
+ * · `curl-binary` — the standalone location install.sh writes. We own the file;
8
+ * update by replacing it.
9
+ * · `unknown` — anywhere else (a hand-copied binary, a distro package, a dev
10
+ * build run straight out of dist-bin/), or not a compiled
11
+ * binary at all. Fail closed.
12
+ */
13
+ export type BinaryInstallShape = 'npm-binary' | 'curl-binary' | 'unknown';
14
+ /**
15
+ * Classify a binary path. Pure — the caller passes the path and the relevant
16
+ * environment, so every shape is unit-testable without a real install.
17
+ *
18
+ * @param execPath the running executable (`process.execPath`)
19
+ * @param env consulted for `BOTMUX_INSTALL_DIR` (install.sh honours it)
20
+ * @param home the home directory, for the default install dir
21
+ */
22
+ export declare function classifyBinaryInstall(execPath: string, env?: NodeJS.ProcessEnv, home?: string): BinaryInstallShape;
23
+ /** Classify the running process. `unknown` for a non-compiled (Node) run, whose
24
+ * updates go through the package-manager path instead. */
25
+ export declare function currentBinaryInstallShape(): BinaryInstallShape;
26
+ /**
27
+ * Map a platform-subpackage binary path to the MAIN `botmux` package root beside
28
+ * it (`…/node_modules/botmux-linux-x64/botmux` → `…/node_modules/botmux`).
29
+ *
30
+ * WHY GO THROUGH THE MAIN PACKAGE instead of computing an npm `--prefix` here:
31
+ * `resolveGlobalInstallPlan` already classifies that path shape into the right
32
+ * manager and builds the right command — including pnpm's `--global-dir`, Bun's
33
+ * env pinning, and the POSIX-vs-Windows prefix difference — and all of it is
34
+ * unit-tested. Re-deriving a prefix here would be a second, diverging
35
+ * implementation of the same rules. It also means `pnpm i -g` / `bun add -g`
36
+ * installs keep resolving to THEIR manager rather than being forced onto npm.
37
+ *
38
+ * Returns null when the shape does not match, so callers fail closed.
39
+ */
40
+ export declare function mainPackageRootForSubpackageBinary(execPath: string): string | null;
41
+ /**
42
+ * How the running process should update itself.
43
+ *
44
+ * · `package-manager` — hand off to npm/pnpm/Bun for `packageRoot`. Covers both a
45
+ * plain Node install AND a compiled binary living inside a package manager's
46
+ * tree (the manager owns that file).
47
+ * · `self-replace` — download the release asset and swap the binary.
48
+ * · `unsupported` — could not identify the install; callers keep their
49
+ * existing "unsupported install" behaviour.
50
+ */
51
+ export type UpdateStrategy = {
52
+ kind: 'package-manager';
53
+ packageRoot: string;
54
+ } | {
55
+ kind: 'self-replace';
56
+ target: string;
57
+ } | {
58
+ kind: 'unsupported';
59
+ reason: 'unknown-binary-location';
60
+ };
61
+ /**
62
+ * Decide the update strategy. Pure over its inputs so every branch is testable
63
+ * without a compiled binary.
64
+ *
65
+ * The Node path is deliberately left EXACTLY as it was: when this is not a
66
+ * standalone binary we return the running install root and callers resolve it
67
+ * through `resolveGlobalInstallPlan` as before — no behaviour change for npm
68
+ * `dist/cli.js` deployments or source checkouts.
69
+ *
70
+ * @param standalone is this a compiled single-file executable?
71
+ * @param execPath the running executable
72
+ * @param installRoot `botmuxInstallRoot()` — only meaningful when NOT standalone
73
+ */
74
+ export declare function resolveUpdateStrategy(standalone: boolean, execPath: string, installRoot: string, env?: NodeJS.ProcessEnv, home?: string): UpdateStrategy;
75
+ /** Production wiring for {@link resolveUpdateStrategy}. */
76
+ export declare function currentUpdateStrategy(installRoot: string): UpdateStrategy;
77
+ //# sourceMappingURL=binary-install-shape.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"binary-install-shape.d.ts","sourceRoot":"","sources":["../../src/core/binary-install-shape.ts"],"names":[],"mappings":"AAmBA;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,kBAAkB,GAAG,YAAY,GAAG,aAAa,GAAG,SAAS,CAAC;AAO1E;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,MAAM,EAChB,GAAG,GAAE,MAAM,CAAC,UAAwB,EACpC,IAAI,GAAE,MAAkB,GACvB,kBAAkB,CAyBpB;AAED;2DAC2D;AAC3D,wBAAgB,yBAAyB,IAAI,kBAAkB,CAG9D;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,kCAAkC,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAIlF;AAED;;;;;;;;;GASG;AACH,MAAM,MAAM,cAAc,GACtB;IAAE,IAAI,EAAE,iBAAiB,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GAChD;IAAE,IAAI,EAAE,cAAc,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACxC;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,MAAM,EAAE,yBAAyB,CAAA;CAAE,CAAC;AAE/D;;;;;;;;;;;;GAYG;AACH,wBAAgB,qBAAqB,CACnC,UAAU,EAAE,OAAO,EACnB,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,GAAG,GAAE,MAAM,CAAC,UAAwB,EACpC,IAAI,GAAE,MAAkB,GACvB,cAAc,CAYhB;AAED,2DAA2D;AAC3D,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,MAAM,GAAG,cAAc,CAEzE"}
@@ -0,0 +1,117 @@
1
+ /**
2
+ * WHERE the running compiled binary lives — the pure, dependency-free half of the
3
+ * self-update story.
4
+ *
5
+ * Split out of `binary-self-update.ts` so it can be imported from
6
+ * `utils/global-install.ts` without closing an import cycle: the self-update
7
+ * module needs `restart-report` (for the repo slug) which reaches
8
+ * `utils/install-info.ts`, and `global-install.ts` is itself imported by
9
+ * `install-diagnostics.ts`. This file imports nothing but `node:os`/`node:path`
10
+ * plus the standalone check, so it is safe to depend on from anywhere.
11
+ *
12
+ * See `binary-self-update.ts` for the full rationale — in short: both installers
13
+ * ship the SAME compiled binary, so the module graph cannot tell them apart (both
14
+ * report an install root of `/`), and `process.execPath` can.
15
+ */
16
+ import { homedir } from 'node:os';
17
+ import { join } from 'node:path';
18
+ import { isStandaloneBinary } from './self-spawn.js';
19
+ /** Default standalone install dir, matching install.sh's own default. */
20
+ function defaultInstallDir(home) {
21
+ return join(home, '.botmux', 'bin');
22
+ }
23
+ /**
24
+ * Classify a binary path. Pure — the caller passes the path and the relevant
25
+ * environment, so every shape is unit-testable without a real install.
26
+ *
27
+ * @param execPath the running executable (`process.execPath`)
28
+ * @param env consulted for `BOTMUX_INSTALL_DIR` (install.sh honours it)
29
+ * @param home the home directory, for the default install dir
30
+ */
31
+ export function classifyBinaryInstall(execPath, env = process.env, home = homedir()) {
32
+ const path = execPath.replace(/\\/g, '/').replace(/\/+$/, '');
33
+ if (!path)
34
+ return 'unknown';
35
+ // Platform subpackage: <anything>/node_modules/botmux-<plat>-<arch>[-musl]/botmux
36
+ // Anchored on `/node_modules/` so a user directory that merely happens to be
37
+ // named `botmux-linux-x64` is not mistaken for a package-manager-owned tree.
38
+ if (/\/node_modules\/botmux-(?:linux|darwin)-(?:x64|arm64)(?:-musl)?\/botmux$/.test(path)) {
39
+ return 'npm-binary';
40
+ }
41
+ // The standalone install location. `BOTMUX_INSTALL_DIR` is what install.sh
42
+ // honours, so an install that used it is recognised WHEN THAT VARIABLE IS STILL
43
+ // EXPORTED at runtime. ⚠️ It usually is not: `BOTMUX_INSTALL_DIR=/opt/bm sh
44
+ // install.sh` sets it for the installer only, so a later `botmux update` sees a
45
+ // bare environment and this falls through to `unknown` — fail-closed, so nothing
46
+ // is damaged, but self-update is unavailable for that install. Do not describe
47
+ // custom dirs as unconditionally covered; making them work without the variable
48
+ // would need a persisted install record, which is out of scope here.
49
+ for (const raw of [env.BOTMUX_INSTALL_DIR, defaultInstallDir(home)]) {
50
+ if (!raw)
51
+ continue;
52
+ const dir = raw.replace(/\\/g, '/').replace(/\/+$/, '');
53
+ if (dir && path === `${dir}/botmux`)
54
+ return 'curl-binary';
55
+ }
56
+ return 'unknown';
57
+ }
58
+ /** Classify the running process. `unknown` for a non-compiled (Node) run, whose
59
+ * updates go through the package-manager path instead. */
60
+ export function currentBinaryInstallShape() {
61
+ if (!isStandaloneBinary())
62
+ return 'unknown';
63
+ return classifyBinaryInstall(process.execPath);
64
+ }
65
+ /**
66
+ * Map a platform-subpackage binary path to the MAIN `botmux` package root beside
67
+ * it (`…/node_modules/botmux-linux-x64/botmux` → `…/node_modules/botmux`).
68
+ *
69
+ * WHY GO THROUGH THE MAIN PACKAGE instead of computing an npm `--prefix` here:
70
+ * `resolveGlobalInstallPlan` already classifies that path shape into the right
71
+ * manager and builds the right command — including pnpm's `--global-dir`, Bun's
72
+ * env pinning, and the POSIX-vs-Windows prefix difference — and all of it is
73
+ * unit-tested. Re-deriving a prefix here would be a second, diverging
74
+ * implementation of the same rules. It also means `pnpm i -g` / `bun add -g`
75
+ * installs keep resolving to THEIR manager rather than being forced onto npm.
76
+ *
77
+ * Returns null when the shape does not match, so callers fail closed.
78
+ */
79
+ export function mainPackageRootForSubpackageBinary(execPath) {
80
+ const path = execPath.replace(/\\/g, '/').replace(/\/+$/, '');
81
+ const m = /^(.*\/node_modules)\/botmux-(?:linux|darwin)-(?:x64|arm64)(?:-musl)?\/botmux$/.exec(path);
82
+ return m ? `${m[1]}/botmux` : null;
83
+ }
84
+ /**
85
+ * Decide the update strategy. Pure over its inputs so every branch is testable
86
+ * without a compiled binary.
87
+ *
88
+ * The Node path is deliberately left EXACTLY as it was: when this is not a
89
+ * standalone binary we return the running install root and callers resolve it
90
+ * through `resolveGlobalInstallPlan` as before — no behaviour change for npm
91
+ * `dist/cli.js` deployments or source checkouts.
92
+ *
93
+ * @param standalone is this a compiled single-file executable?
94
+ * @param execPath the running executable
95
+ * @param installRoot `botmuxInstallRoot()` — only meaningful when NOT standalone
96
+ */
97
+ export function resolveUpdateStrategy(standalone, execPath, installRoot, env = process.env, home = homedir()) {
98
+ if (!standalone)
99
+ return { kind: 'package-manager', packageRoot: installRoot };
100
+ const shape = classifyBinaryInstall(execPath, env, home);
101
+ if (shape === 'npm-binary') {
102
+ const root = mainPackageRootForSubpackageBinary(execPath);
103
+ // The regex that produced `npm-binary` is the same one used here, so `root`
104
+ // cannot be null in practice; keep the guard so a future loosening of one
105
+ // pattern degrades to "unsupported" instead of dereferencing null.
106
+ if (root)
107
+ return { kind: 'package-manager', packageRoot: root };
108
+ }
109
+ if (shape === 'curl-binary')
110
+ return { kind: 'self-replace', target: execPath };
111
+ return { kind: 'unsupported', reason: 'unknown-binary-location' };
112
+ }
113
+ /** Production wiring for {@link resolveUpdateStrategy}. */
114
+ export function currentUpdateStrategy(installRoot) {
115
+ return resolveUpdateStrategy(isStandaloneBinary(), process.execPath, installRoot);
116
+ }
117
+ //# sourceMappingURL=binary-install-shape.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"binary-install-shape.js","sourceRoot":"","sources":["../../src/core/binary-install-shape.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Where the running compiled binary lives, and therefore who owns updating it.
3
+ * The pure location classifier lives in `binary-install-shape.ts` so
4
+ * `utils/global-install.ts` can import it without closing an import cycle through
5
+ * this module (which reaches install-info via restart-report). Re-exported here so
6
+ * callers have one obvious import site for the whole self-update surface.
7
+ */
8
+ export { classifyBinaryInstall, currentBinaryInstallShape, mainPackageRootForSubpackageBinary, resolveUpdateStrategy, currentUpdateStrategy, type BinaryInstallShape, type UpdateStrategy, } from './binary-install-shape.js';
9
+ /**
10
+ * Is this a musl libc host (Alpine and most slim Docker images)?
11
+ *
12
+ * WHY IT MATTERS: a glibc-linked binary does not run on musl at all — it dies in
13
+ * the loader with a message naming no cause. Downloading the wrong one turns a
14
+ * working install into a binary that cannot start, which is strictly worse than
15
+ * refusing to update.
16
+ *
17
+ * Mirrors the probe order in `scripts/postinstall-bin.mjs` and install.sh, and is
18
+ * deliberately conservative: only claim musl when positively observed, so a glibc
19
+ * box is never pushed onto the musl asset. Node's `process.report` header is a
20
+ * NEGATIVE signal only — measured on node:22-alpine it carries neither
21
+ * `glibcVersionRuntime` nor any musl key, so it can confirm glibc but never musl.
22
+ */
23
+ export declare function isMuslHost(platform?: NodeJS.Platform, probes?: {
24
+ glibcRuntime?: () => string | undefined;
25
+ listDir?: (dir: string) => string[];
26
+ exists?: (path: string) => boolean;
27
+ }): boolean;
28
+ /**
29
+ * The release asset name for a platform, matching the names release.yml uploads
30
+ * (`botmux-<os>-<arch>[-musl]`) and install.sh downloads. Returns null for a
31
+ * platform/arch with no published build rather than inventing a name that 404s.
32
+ */
33
+ export declare function releaseAssetName(platform?: NodeJS.Platform, arch?: string, musl?: boolean): string | null;
34
+ /** Download base for a release tag, mirroring install.sh's URL construction. */
35
+ export declare function releaseAssetBaseUrl(version: string): string;
36
+ export interface BinarySelfUpdateResult {
37
+ /** The asset that was installed. */
38
+ asset: string;
39
+ /** Absolute path that was replaced. */
40
+ target: string;
41
+ /** Bytes written. */
42
+ bytes: number;
43
+ }
44
+ export interface BinarySelfUpdateDeps {
45
+ /** Fetch a URL as a readable stream (injected for tests). */
46
+ fetchStream?: (url: string) => Promise<NodeJS.ReadableStream>;
47
+ /** Read the published checksum for `asset`, or null when none is published. */
48
+ fetchChecksum?: (url: string) => Promise<string | null>;
49
+ }
50
+ /**
51
+ * Replace the running standalone binary with `version`'s published asset.
52
+ *
53
+ * ── WHY A RENAME AND NOT A WRITE (do not "simplify" this) ─────────────────────
54
+ * MEASURED on Linux with a real ELF:
55
+ *
56
+ * open(target,'r+b').write(…) → OSError 26 ETXTBSY "Text file busy"
57
+ * rename(new, target) → succeeds; the running process keeps executing
58
+ * the old inode and finishes normally
59
+ *
60
+ * So the update MUST land as a rename over the path, never an in-place write:
61
+ * the kernel refuses to let us scribble on the executable of a live process, and
62
+ * a rename is also what makes the swap atomic for anything about to `exec` it.
63
+ * The temp file is created in the SAME directory as the target so the rename is
64
+ * within one filesystem (a cross-device rename fails with EXDEV, and copying
65
+ * would reintroduce the torn-file window this avoids).
66
+ *
67
+ * Checksum verification happens BEFORE the rename, so a truncated or tampered
68
+ * download is discarded while the working binary is still in place. A release
69
+ * that publishes no `.sha256` is a warning, not a failure — matching install.sh,
70
+ * which has always tolerated that.
71
+ *
72
+ * ── WHY NO `realpathSync(target)` ─────────────────────────────────────────────
73
+ * Renaming over a SYMLINK replaces the link itself and orphans its target
74
+ * (measured). That would matter if `target` could be a symlink — but the default
75
+ * target is `process.execPath`, which the OS has already resolved: MEASURED with a
76
+ * compiled binary invoked through a symlink, `process.execPath` reports the real
77
+ * file, never the link. So the symlink case is unreachable on the production path,
78
+ * and resolving again would only add a failure mode of its own (an unreadable
79
+ * parent dir). install.sh's plain `mv` has the same semantics.
80
+ */
81
+ export declare function replaceStandaloneBinary(version: string, target?: string, deps?: BinarySelfUpdateDeps): Promise<BinarySelfUpdateResult>;
82
+ //# sourceMappingURL=binary-self-update.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"binary-self-update.d.ts","sourceRoot":"","sources":["../../src/core/binary-self-update.ts"],"names":[],"mappings":"AA0EA;;;;;;GAMG;AACH,OAAO,EACL,qBAAqB,EACrB,yBAAyB,EACzB,kCAAkC,EAClC,qBAAqB,EACrB,qBAAqB,EACrB,KAAK,kBAAkB,EACvB,KAAK,cAAc,GACpB,MAAM,2BAA2B,CAAC;AAInC;;;;;;;;;;;;;GAaG;AACH,wBAAgB,UAAU,CACxB,QAAQ,GAAE,MAAM,CAAC,QAA2B,EAC5C,MAAM,GAAE;IACN,YAAY,CAAC,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;IACxC,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,EAAE,CAAC;IACpC,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;CAC/B,GACL,OAAO,CAoBT;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,GAAE,MAAM,CAAC,QAA2B,EAC5C,IAAI,GAAE,MAAqB,EAC3B,IAAI,GAAE,OAA8B,GACnC,MAAM,GAAG,IAAI,CAMf;AAED,gFAAgF;AAChF,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAE3D;AAYD,MAAM,WAAW,sBAAsB;IACrC,oCAAoC;IACpC,KAAK,EAAE,MAAM,CAAC;IACd,uCAAuC;IACvC,MAAM,EAAE,MAAM,CAAC;IACf,qBAAqB;IACrB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,oBAAoB;IACnC,6DAA6D;IAC7D,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;IAC9D,+EAA+E;IAC/E,aAAa,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CACzD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,MAAM,EACf,MAAM,GAAE,MAAyB,EACjC,IAAI,GAAE,oBAAyB,GAC9B,OAAO,CAAC,sBAAsB,CAAC,CAoDjC"}
@@ -0,0 +1,239 @@
1
+ /**
2
+ * How the RUNNING compiled binary was installed, and how to update it.
3
+ *
4
+ * ── WHY THIS EXISTS ────────────────────────────────────────────────────────────
5
+ * Every pre-existing update path (`botmux update`, the dashboard's
6
+ * `/api/update/run`, the scheduled maintenance auto-update) asks
7
+ * `resolveGlobalInstallPlan(botmuxInstallRoot())` which package manager owns the
8
+ * install. That question is answered by looking at the install ROOT — the
9
+ * directory holding `package.json` — and classifying its path shape
10
+ * (`…/lib/node_modules/botmux` ⇒ npm, `…/.pnpm/…` ⇒ pnpm, …).
11
+ *
12
+ * A compiled single-file executable has NO package.json on disk: the module graph
13
+ * lives in the virtual read-only `/$bunfs/`, so `packageRoot()` walks up to `/`
14
+ * and stops. MEASURED on the real published v3.18.4 binary and on a locally
15
+ * compiled probe — identical in both:
16
+ *
17
+ * botmuxInstallRoot() → "/"
18
+ * detectGlobalInstallManager("/") → "unknown"
19
+ * resolveGlobalInstallPlan("/") → throws UnsupportedGlobalInstallError
20
+ * botmuxCliEntry() → "/dist/cli.js" (does not exist)
21
+ *
22
+ * So on the real shipped artifact `botmux update` printed
23
+ * “无法安全识别当前安装方式(unknown)” and exited, the dashboard button greyed out
24
+ * with “supports npm/pnpm/Bun global installs only”, and the scheduled
25
+ * auto-update failed every single day (fail-safe — it never restarts onto a stale
26
+ * version — but it marks the day done, so it does not even retry).
27
+ *
28
+ * ── THE KEY INSIGHT: BOTH INSTALLERS SHIP THE SAME BINARY ─────────────────────
29
+ * It is tempting to think “npm users update with npm, curl users update with
30
+ * curl”, i.e. that the two installers produce different artifacts. They do not.
31
+ * `npm i -g botmux` has NO `bin` field any more (removed in #1047): it downloads
32
+ * a platform subpackage and its postinstall writes `~/.botmux/bin/botmux` as a
33
+ * launcher that `exec`s that subpackage's compiled binary. `install.sh` downloads
34
+ * the very same compiled binary from the GitHub Release. Same bytes, different
35
+ * location — which is exactly why classifying by MODULE GRAPH cannot work (both
36
+ * report `/`) and classifying by LOCATION can.
37
+ *
38
+ * `process.execPath` is the location of the running executable, and it is the one
39
+ * thing that still differs. MEASURED, compiled binaries invoked through each
40
+ * installer's own launcher:
41
+ *
42
+ * npm → /usr/lib/node_modules/botmux-linux-x64/botmux (subpackage dir)
43
+ * curl → ~/.botmux/bin/botmux (BOTMUX_INSTALL_DIR)
44
+ *
45
+ * Note it is the REAL binary path in both cases, not the launcher's: the launcher
46
+ * `exec`s the binary, so the process's execPath is the target. Verified by
47
+ * running a compiled probe through a launcher of exactly the shape postinstall
48
+ * writes.
49
+ *
50
+ * ── WHAT EACH SHAPE DOES TO UPDATE ─────────────────────────────────────────────
51
+ * · npm → hand back to npm (`npm i -g botmux@latest`). npm replaces the
52
+ * subpackage and re-runs postinstall, which re-points the launcher. We
53
+ * must NOT rewrite the binary ourselves: npm owns that tree, and a
54
+ * hand-written file there would be clobbered (or worse, left behind at
55
+ * a version npm's metadata disagrees with).
56
+ * · curl → replace the binary in place, the same way install.sh does: download
57
+ * the matching asset, verify its published SHA-256, then atomically
58
+ * rename over the target. Nothing else owns that path.
59
+ *
60
+ * A shape we cannot positively identify returns null, and every caller keeps its
61
+ * existing "unsupported install" behaviour. Fail closed: guessing wrong here
62
+ * means either writing into a tree npm owns, or downloading a binary for the
63
+ * wrong libc.
64
+ */
65
+ import { createHash, randomBytes } from 'node:crypto';
66
+ import { createReadStream, createWriteStream, existsSync, mkdirSync, readdirSync, renameSync, rmSync, statSync, chmodSync, } from 'node:fs';
67
+ import { dirname, join } from 'node:path';
68
+ import { pipeline } from 'node:stream/promises';
69
+ import { GITHUB_REPO } from './restart-report.js';
70
+ import { getReleaseStream, resolveHttpProxy } from './release-download.js';
71
+ /**
72
+ * Where the running compiled binary lives, and therefore who owns updating it.
73
+ * The pure location classifier lives in `binary-install-shape.ts` so
74
+ * `utils/global-install.ts` can import it without closing an import cycle through
75
+ * this module (which reaches install-info via restart-report). Re-exported here so
76
+ * callers have one obvious import site for the whole self-update surface.
77
+ */
78
+ export { classifyBinaryInstall, currentBinaryInstallShape, mainPackageRootForSubpackageBinary, resolveUpdateStrategy, currentUpdateStrategy, } from './binary-install-shape.js';
79
+ // ── Release asset selection ────────────────────────────────────────────────────
80
+ /**
81
+ * Is this a musl libc host (Alpine and most slim Docker images)?
82
+ *
83
+ * WHY IT MATTERS: a glibc-linked binary does not run on musl at all — it dies in
84
+ * the loader with a message naming no cause. Downloading the wrong one turns a
85
+ * working install into a binary that cannot start, which is strictly worse than
86
+ * refusing to update.
87
+ *
88
+ * Mirrors the probe order in `scripts/postinstall-bin.mjs` and install.sh, and is
89
+ * deliberately conservative: only claim musl when positively observed, so a glibc
90
+ * box is never pushed onto the musl asset. Node's `process.report` header is a
91
+ * NEGATIVE signal only — measured on node:22-alpine it carries neither
92
+ * `glibcVersionRuntime` nor any musl key, so it can confirm glibc but never musl.
93
+ */
94
+ export function isMuslHost(platform = process.platform, probes = {}) {
95
+ if (platform !== 'linux')
96
+ return false;
97
+ const glibcRuntime = probes.glibcRuntime ?? (() => {
98
+ try {
99
+ return process.report?.getReport?.()
100
+ ?.header?.glibcVersionRuntime;
101
+ }
102
+ catch {
103
+ return undefined;
104
+ }
105
+ });
106
+ const listDir = probes.listDir ?? ((dir) => {
107
+ try {
108
+ return readdirSync(dir);
109
+ }
110
+ catch {
111
+ return [];
112
+ }
113
+ });
114
+ const exists = probes.exists ?? ((p) => {
115
+ try {
116
+ return existsSync(p);
117
+ }
118
+ catch {
119
+ return false;
120
+ }
121
+ });
122
+ if (glibcRuntime())
123
+ return false; // a reported glibc runtime settles it
124
+ for (const dir of ['/lib', '/usr/lib']) {
125
+ if (listDir(dir).some(f => f.startsWith('ld-musl-')))
126
+ return true;
127
+ }
128
+ return exists('/etc/alpine-release');
129
+ }
130
+ /**
131
+ * The release asset name for a platform, matching the names release.yml uploads
132
+ * (`botmux-<os>-<arch>[-musl]`) and install.sh downloads. Returns null for a
133
+ * platform/arch with no published build rather than inventing a name that 404s.
134
+ */
135
+ export function releaseAssetName(platform = process.platform, arch = process.arch, musl = isMuslHost(platform)) {
136
+ const os = platform === 'linux' ? 'linux' : platform === 'darwin' ? 'darwin' : null;
137
+ const cpu = arch === 'x64' ? 'x64' : arch === 'arm64' ? 'arm64' : null;
138
+ if (!os || !cpu)
139
+ return null;
140
+ // Only linux ships musl variants; darwin has no such split.
141
+ return `botmux-${os}-${cpu}${os === 'linux' && musl ? '-musl' : ''}`;
142
+ }
143
+ /** Download base for a release tag, mirroring install.sh's URL construction. */
144
+ export function releaseAssetBaseUrl(version) {
145
+ return `https://github.com/${GITHUB_REPO}/releases/download/v${version.replace(/^v/i, '')}`;
146
+ }
147
+ // ── The self-update itself ─────────────────────────────────────────────────────
148
+ const SELF_UPDATE_UA = 'botmux-self-update';
149
+ async function sha256File(path) {
150
+ const hash = createHash('sha256');
151
+ await pipeline(createReadStream(path), hash);
152
+ return hash.digest('hex');
153
+ }
154
+ /**
155
+ * Replace the running standalone binary with `version`'s published asset.
156
+ *
157
+ * ── WHY A RENAME AND NOT A WRITE (do not "simplify" this) ─────────────────────
158
+ * MEASURED on Linux with a real ELF:
159
+ *
160
+ * open(target,'r+b').write(…) → OSError 26 ETXTBSY "Text file busy"
161
+ * rename(new, target) → succeeds; the running process keeps executing
162
+ * the old inode and finishes normally
163
+ *
164
+ * So the update MUST land as a rename over the path, never an in-place write:
165
+ * the kernel refuses to let us scribble on the executable of a live process, and
166
+ * a rename is also what makes the swap atomic for anything about to `exec` it.
167
+ * The temp file is created in the SAME directory as the target so the rename is
168
+ * within one filesystem (a cross-device rename fails with EXDEV, and copying
169
+ * would reintroduce the torn-file window this avoids).
170
+ *
171
+ * Checksum verification happens BEFORE the rename, so a truncated or tampered
172
+ * download is discarded while the working binary is still in place. A release
173
+ * that publishes no `.sha256` is a warning, not a failure — matching install.sh,
174
+ * which has always tolerated that.
175
+ *
176
+ * ── WHY NO `realpathSync(target)` ─────────────────────────────────────────────
177
+ * Renaming over a SYMLINK replaces the link itself and orphans its target
178
+ * (measured). That would matter if `target` could be a symlink — but the default
179
+ * target is `process.execPath`, which the OS has already resolved: MEASURED with a
180
+ * compiled binary invoked through a symlink, `process.execPath` reports the real
181
+ * file, never the link. So the symlink case is unreachable on the production path,
182
+ * and resolving again would only add a failure mode of its own (an unreadable
183
+ * parent dir). install.sh's plain `mv` has the same semantics.
184
+ */
185
+ export async function replaceStandaloneBinary(version, target = process.execPath, deps = {}) {
186
+ const asset = releaseAssetName();
187
+ if (!asset) {
188
+ throw new Error(`当前平台没有发布二进制(${process.platform}-${process.arch})`);
189
+ }
190
+ const base = releaseAssetBaseUrl(version);
191
+ const proxy = resolveHttpProxy();
192
+ const fetchStream = deps.fetchStream
193
+ ?? (async (url) => await getReleaseStream(url, proxy, SELF_UPDATE_UA));
194
+ const fetchChecksum = deps.fetchChecksum ?? (async (url) => {
195
+ try {
196
+ const res = await getReleaseStream(url, proxy, SELF_UPDATE_UA);
197
+ const chunks = [];
198
+ for await (const chunk of res)
199
+ chunks.push(Buffer.from(chunk));
200
+ // The file is "<sha256> <filename>"; take the first field.
201
+ const first = Buffer.concat(chunks).toString('utf-8').trim().split(/\s+/)[0] ?? '';
202
+ return /^[0-9a-f]{64}$/i.test(first) ? first.toLowerCase() : null;
203
+ }
204
+ catch {
205
+ return null; // no checksum published (or unreachable) → warn, don't fail
206
+ }
207
+ });
208
+ // Same directory as the target: keeps the rename intra-filesystem (EXDEV).
209
+ const dir = dirname(target);
210
+ mkdirSync(dir, { recursive: true });
211
+ const tmp = join(dir, `.botmux-update.${process.pid}.${randomBytes(4).toString('hex')}.tmp`);
212
+ try {
213
+ const res = await fetchStream(`${base}/${asset}`);
214
+ await pipeline(res, createWriteStream(tmp));
215
+ const expected = await fetchChecksum(`${base}/${asset}.sha256`);
216
+ if (expected) {
217
+ const actual = await sha256File(tmp);
218
+ if (actual !== expected) {
219
+ throw new Error(`${asset} SHA-256 校验不通过(期望 ${expected},实际 ${actual})`);
220
+ }
221
+ }
222
+ const bytes = statSync(tmp).size;
223
+ // A truncated download that still passed (no checksum published) would leave
224
+ // an unrunnable binary in place of a working one. The real assets are ~100MB+;
225
+ // anything under a megabyte is a GitHub error page, not an executable.
226
+ if (bytes < 1_000_000) {
227
+ throw new Error(`${asset} 下载内容异常(仅 ${bytes} 字节,疑似错误页而非二进制)`);
228
+ }
229
+ chmodSync(tmp, 0o755);
230
+ // Atomic swap. NOT a write to `target` — that is ETXTBSY (see header).
231
+ renameSync(tmp, target);
232
+ return { asset, target, bytes };
233
+ }
234
+ catch (error) {
235
+ rmSync(tmp, { force: true });
236
+ throw error;
237
+ }
238
+ }
239
+ //# sourceMappingURL=binary-self-update.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"binary-self-update.js","sourceRoot":"","sources":["../../src/core/binary-self-update.ts"],"names":[],"mappings":"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"}