@ivan-murzak/runner-manager 0.0.0-bootstrap.1 → 0.1.2
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.
- package/LICENSE +21 -0
- package/README.md +99 -6
- package/bin/runner-manager.cjs +138 -0
- package/package.json +12 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ivan Murzak
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,9 +1,102 @@
|
|
|
1
|
-
# @ivan-murzak/runner-manager
|
|
1
|
+
# @ivan-murzak/runner-manager (npm wrapper)
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
Local-first autoscaling manager for ephemeral GitHub Actions self-hosted
|
|
4
|
+
runners, with a CLI and a Ratatui TUI.
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
> **Read this before you install.** Using this tool means installing the
|
|
7
|
+
> project's published GitHub App, which declares **Repository → Administration:
|
|
8
|
+
> Read and write** — a permission that also allows deleting, renaming and
|
|
9
|
+
> transferring the repository, and adding or removing collaborators. It applies
|
|
10
|
+
> even if you only ever use `runner-manager` as a read-only dashboard. The full
|
|
11
|
+
> permission set and the narrower organization-scoped alternative are in
|
|
12
|
+
> [What you are granting](#what-you-are-granting) below.
|
|
8
13
|
|
|
9
|
-
|
|
14
|
+
```sh
|
|
15
|
+
npm i -g @ivan-murzak/runner-manager
|
|
16
|
+
runner-manager --version
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
This package is a thin wrapper. The binary itself lives in one of five
|
|
20
|
+
per-platform packages, declared here as `optionalDependencies`, so npm installs
|
|
21
|
+
only the one that matches your OS and CPU:
|
|
22
|
+
|
|
23
|
+
| Platform package | os / cpu | Rust target |
|
|
24
|
+
|---|---|---|
|
|
25
|
+
| `@ivan-murzak/runner-manager-win32-x64` | `win32` / `x64` | `x86_64-pc-windows-msvc` |
|
|
26
|
+
| `@ivan-murzak/runner-manager-darwin-arm64` | `darwin` / `arm64` | `aarch64-apple-darwin` |
|
|
27
|
+
| `@ivan-murzak/runner-manager-darwin-x64` | `darwin` / `x64` | `x86_64-apple-darwin` |
|
|
28
|
+
| `@ivan-murzak/runner-manager-linux-x64` | `linux` / `x64` | `x86_64-unknown-linux-gnu` |
|
|
29
|
+
| `@ivan-murzak/runner-manager-linux-arm64` | `linux` / `arm64` | `aarch64-unknown-linux-gnu` |
|
|
30
|
+
|
|
31
|
+
Each platform package records, in its `package.json`, the release archive its
|
|
32
|
+
binary was taken from and that archive's published SHA-256 — the same digest
|
|
33
|
+
listed in the release's `SHA256SUMS`.
|
|
34
|
+
|
|
35
|
+
## Read this before `service install`
|
|
36
|
+
|
|
37
|
+
**An `npm i -g` binary does not have a fixed home, and this product records an
|
|
38
|
+
absolute path.**
|
|
39
|
+
|
|
40
|
+
`npm i -g` installs into the *active* Node installation's global prefix. If you
|
|
41
|
+
manage Node with `nvm`, `fnm`, `volta`, `asdf`, Homebrew, or a Windows
|
|
42
|
+
installer upgrade, that prefix is different for every Node version — something
|
|
43
|
+
like:
|
|
44
|
+
|
|
45
|
+
```text
|
|
46
|
+
~/.nvm/versions/node/v20.11.0/bin/runner-manager
|
|
47
|
+
~/.nvm/versions/node/v22.3.0/bin/runner-manager <- after `nvm install 22`
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
`runner-manager service install` resolves and records the **absolute** path of
|
|
51
|
+
the binary at the moment you run it. So switching Node versions after
|
|
52
|
+
installing the service leaves the service pointing at a path that no longer
|
|
53
|
+
exists, and nothing tells you until the next unattended boot, when the agent
|
|
54
|
+
does not come up.
|
|
55
|
+
|
|
56
|
+
`runner-manager service status` detects this and reports the recorded path as
|
|
57
|
+
**stale** rather than reporting the service as healthy. If you see that:
|
|
58
|
+
|
|
59
|
+
```sh
|
|
60
|
+
npm i -g @ivan-murzak/runner-manager # into the Node version you are now using
|
|
61
|
+
runner-manager service install # re-records the new absolute path
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
**If you want a boot-start service, prefer the install script.** It installs to
|
|
65
|
+
`~/.local/bin` (macOS, Linux) or `%LOCALAPPDATA%\Programs\runner-manager`
|
|
66
|
+
(Windows), neither of which moves when a toolchain moves:
|
|
67
|
+
|
|
68
|
+
```sh
|
|
69
|
+
curl -fsSL https://github.com/IvanMurzak/GitHub-Runner-Scaler-UI/releases/latest/download/install.sh | sh
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
```powershell
|
|
73
|
+
irm https://github.com/IvanMurzak/GitHub-Runner-Scaler-UI/releases/latest/download/install.ps1 | iex
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
This package is the right choice when you already manage tooling with npm and
|
|
77
|
+
you run `runner-manager` interactively or from a script, rather than as a
|
|
78
|
+
boot-start service.
|
|
79
|
+
|
|
80
|
+
## Windows on ARM
|
|
81
|
+
|
|
82
|
+
npm will not install an `"cpu": ["x64"]` package onto an arm64 host, and no
|
|
83
|
+
arm64 Windows build is published, so this package cannot serve Windows on ARM.
|
|
84
|
+
The install script can: it uses the x64 build through the built-in emulation
|
|
85
|
+
layer.
|
|
86
|
+
|
|
87
|
+
## What you are granting
|
|
88
|
+
|
|
89
|
+
Using this tool means installing the project's published GitHub App, and that
|
|
90
|
+
App declares **Repository → Administration: Read and write**. That permission
|
|
91
|
+
also allows deleting, renaming and transferring the repository, and adding or
|
|
92
|
+
removing collaborators. It applies even if you only ever use `runner-manager`
|
|
93
|
+
as a read-only dashboard.
|
|
94
|
+
|
|
95
|
+
The full permission table, why the grant is unavoidable at repository scope,
|
|
96
|
+
and why organization scope is materially narrower are in the
|
|
97
|
+
[repository README](https://github.com/IvanMurzak/GitHub-Runner-Scaler-UI#what-you-are-granting).
|
|
98
|
+
Read it before you run `auth login`.
|
|
99
|
+
|
|
100
|
+
## Licence
|
|
101
|
+
|
|
102
|
+
MIT. Source: <https://github.com/IvanMurzak/GitHub-Runner-Scaler-UI>
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
//
|
|
3
|
+
// The npm wrapper's entry point (task a3, D11).
|
|
4
|
+
//
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
// WHY A SHIM AND NOT A postinstall DOWNLOAD.
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
// The other common shape for shipping a binary through npm is a `postinstall`
|
|
9
|
+
// script that downloads it. That shape needs network access at install time,
|
|
10
|
+
// breaks behind a proxy and inside an offline CI cache, and -- the part that
|
|
11
|
+
// matters here -- it fetches an artifact that npm's own integrity hashes never
|
|
12
|
+
// covered. The esbuild shape used instead puts each binary inside a real npm
|
|
13
|
+
// package, so npm's registry integrity hash covers the exact bytes that end up
|
|
14
|
+
// on disk, and `--ignore-scripts` (increasingly a default in locked-down CI)
|
|
15
|
+
// does not silently produce an installation with no binary in it.
|
|
16
|
+
//
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
// WHY THE PLATFORM PACKAGES ARE `optionalDependencies`.
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
// A package whose `os`/`cpu` do not match the host is SKIPPED when it is
|
|
21
|
+
// optional and is a hard install failure when it is not. Five platform
|
|
22
|
+
// packages on a normal `dependencies` line would therefore fail the install
|
|
23
|
+
// for every user on four of the five platforms. The cost is that a genuinely
|
|
24
|
+
// missing package is indistinguishable from a skipped one at install time,
|
|
25
|
+
// which is what the diagnostics below exist to untangle at run time.
|
|
26
|
+
//
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
// THIS FILE IS COMMITTED; THE package.json FILES BESIDE IT ARE GENERATED.
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
// `.github/scripts/channels.sh npm-manifests` writes the manifests at release
|
|
31
|
+
// time so every version and every published digest comes from the release that
|
|
32
|
+
// is happening. This file has no version in it and never needs one.
|
|
33
|
+
|
|
34
|
+
"use strict";
|
|
35
|
+
|
|
36
|
+
const path = require("node:path");
|
|
37
|
+
const { spawnSync } = require("node:child_process");
|
|
38
|
+
|
|
39
|
+
// Must agree with `PUBLISHED_TARGETS` in `.github/scripts/channels.sh`.
|
|
40
|
+
// `crates/app/tests/release_channels.rs` compares the two, because a package
|
|
41
|
+
// renamed on one side and not the other produces a wrapper that installs
|
|
42
|
+
// cleanly and then cannot find its own binary.
|
|
43
|
+
const PLATFORMS = {
|
|
44
|
+
"darwin arm64": ["@ivan-murzak/runner-manager-darwin-arm64", "runner-manager"],
|
|
45
|
+
"darwin x64": ["@ivan-murzak/runner-manager-darwin-x64", "runner-manager"],
|
|
46
|
+
"linux arm64": ["@ivan-murzak/runner-manager-linux-arm64", "runner-manager"],
|
|
47
|
+
"linux x64": ["@ivan-murzak/runner-manager-linux-x64", "runner-manager"],
|
|
48
|
+
"win32 x64": ["@ivan-murzak/runner-manager-win32-x64", "runner-manager.exe"],
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
function fail(lines) {
|
|
52
|
+
for (const line of lines) {
|
|
53
|
+
process.stderr.write(line + "\n");
|
|
54
|
+
}
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const key = process.platform + " " + process.arch;
|
|
59
|
+
const entry = PLATFORMS[key];
|
|
60
|
+
|
|
61
|
+
if (!entry) {
|
|
62
|
+
const extra = [];
|
|
63
|
+
if (process.platform === "win32") {
|
|
64
|
+
// Windows on ARM runs the x64 build through the built-in emulation layer,
|
|
65
|
+
// but npm will not install an `"cpu": ["x64"]` package onto an arm64 host,
|
|
66
|
+
// so this is the one platform npm cannot serve and the install script can.
|
|
67
|
+
extra.push(
|
|
68
|
+
"",
|
|
69
|
+
"Windows on ARM is supported by the install script, which uses the x64",
|
|
70
|
+
"build through the built-in emulation layer:",
|
|
71
|
+
"",
|
|
72
|
+
" irm https://github.com/IvanMurzak/GitHub-Runner-Scaler-UI/releases/latest/download/install.ps1 | iex",
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
fail([
|
|
76
|
+
`runner-manager: no published binary for ${process.platform} ${process.arch}.`,
|
|
77
|
+
"",
|
|
78
|
+
"Published platforms: " + Object.keys(PLATFORMS).join(", ") + ".",
|
|
79
|
+
"Build from source instead: cargo install runner-manager",
|
|
80
|
+
...extra,
|
|
81
|
+
]);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const [packageName, binaryName] = entry;
|
|
85
|
+
|
|
86
|
+
let binary;
|
|
87
|
+
try {
|
|
88
|
+
// Resolved through `package.json` rather than through the package's main
|
|
89
|
+
// entry point: these packages have no JavaScript in them at all, so there is
|
|
90
|
+
// no main to resolve.
|
|
91
|
+
binary = path.join(
|
|
92
|
+
path.dirname(require.resolve(packageName + "/package.json")),
|
|
93
|
+
"bin",
|
|
94
|
+
binaryName,
|
|
95
|
+
);
|
|
96
|
+
} catch (error) {
|
|
97
|
+
fail([
|
|
98
|
+
`runner-manager: the platform package ${packageName} is not installed.`,
|
|
99
|
+
"",
|
|
100
|
+
"It is an optionalDependency, so npm skips it silently in three cases:",
|
|
101
|
+
"",
|
|
102
|
+
" * the install ran with --no-optional or --omit=optional",
|
|
103
|
+
" * the install ran on a different platform (a lockfile or a Docker",
|
|
104
|
+
" image built on one OS and used on another)",
|
|
105
|
+
" * the registry was unreachable when the optional dependency was fetched",
|
|
106
|
+
"",
|
|
107
|
+
"Reinstall with optional dependencies enabled:",
|
|
108
|
+
"",
|
|
109
|
+
" npm install -g @ivan-murzak/runner-manager",
|
|
110
|
+
"",
|
|
111
|
+
`(resolution error: ${error && error.message ? error.message : error})`,
|
|
112
|
+
]);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// `stdio: "inherit"` because this is a TUI as well as a CLI: the child needs
|
|
116
|
+
// the real terminal, not a pipe, or Ratatui has no size to draw into and no
|
|
117
|
+
// key events to read.
|
|
118
|
+
//
|
|
119
|
+
// The child's exit code is propagated. A wrapper that always exits 0 makes
|
|
120
|
+
// every `runner-manager` invocation look successful to a shell script, to a
|
|
121
|
+
// CI step, and to the service manager that restarts it on failure.
|
|
122
|
+
const result = spawnSync(binary, process.argv.slice(2), { stdio: "inherit" });
|
|
123
|
+
|
|
124
|
+
if (result.error) {
|
|
125
|
+
fail([
|
|
126
|
+
`runner-manager: could not execute ${binary}`,
|
|
127
|
+
"",
|
|
128
|
+
String(result.error.message || result.error),
|
|
129
|
+
]);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Killed by a signal: report it as a shell does, rather than as exit 0.
|
|
133
|
+
if (result.signal) {
|
|
134
|
+
process.stderr.write(`runner-manager: terminated by signal ${result.signal}\n`);
|
|
135
|
+
process.exit(1);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
process.exit(result.status === null ? 1 : result.status);
|
package/package.json
CHANGED
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ivan-murzak/runner-manager",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"description": "Local-first autoscaling manager for ephemeral GitHub Actions self-hosted runners, with a CLI and a Ratatui TUI.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Ivan Murzak (https://github.com/IvanMurzak)",
|
|
7
7
|
"homepage": "https://github.com/IvanMurzak/GitHub-Runner-Scaler-UI",
|
|
8
8
|
"repository": { "type": "git", "url": "git+https://github.com/IvanMurzak/GitHub-Runner-Scaler-UI.git" },
|
|
9
|
-
"
|
|
9
|
+
"engines": { "node": ">=18" },
|
|
10
|
+
"bin": { "runner-manager": "bin/runner-manager.cjs" },
|
|
11
|
+
"files": [ "bin/", "README.md" ],
|
|
12
|
+
"optionalDependencies": {
|
|
13
|
+
"@ivan-murzak/runner-manager-win32-x64": "0.1.2",
|
|
14
|
+
"@ivan-murzak/runner-manager-darwin-arm64": "0.1.2",
|
|
15
|
+
"@ivan-murzak/runner-manager-darwin-x64": "0.1.2",
|
|
16
|
+
"@ivan-murzak/runner-manager-linux-x64": "0.1.2",
|
|
17
|
+
"@ivan-murzak/runner-manager-linux-arm64": "0.1.2"
|
|
18
|
+
}
|
|
10
19
|
}
|