@andrian.yablonskyy/thub-coordinator 1.0.2 → 1.0.4

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/README.md CHANGED
@@ -7,14 +7,14 @@ See the [main TestHub repo](https://github.com/andrianyablonskyy/thub) for the f
7
7
  ## Install
8
8
 
9
9
  ```bash
10
- npm install @andrian.yablonskyy/thub-coordinator
10
+ npm install -g @andrian.yablonskyy/thub-coordinator
11
11
  ```
12
12
 
13
- Or clone this repo directly and run it from source (see below).
13
+ This gives you two global commands: `thub-coordinator` (the server itself) and `thub-admin` (the local operator CLI, below). Or clone this repo directly and run it from source (see below).
14
14
 
15
15
  ## Configuration
16
16
 
17
- The Coordinator loads a plain **JSON** config file (no YAML support). Resolution order, first match wins: `THUB_COORDINATOR_CONFIG` env var path → the bundled `config.json` default. There is no other automatic fallback path — a real deployment always sets `THUB_COORDINATOR_CONFIG` explicitly.
17
+ The Coordinator loads a plain **JSON** config file (no YAML support). Resolution order, first match wins: `THUB_COORDINATOR_CONFIG` env var path → `~/.config/thub/coordinator.json` → the bundled `config.json` default.
18
18
 
19
19
  ```json
20
20
  {
@@ -33,6 +33,8 @@ The Coordinator loads a plain **JSON** config file (no YAML support). Resolution
33
33
 
34
34
  `sessionSecret` signs the dashboard's session cookie and the HMAC on artifact download links; `clientJoinKey` is the shared secret Clients self-register with — omit or leave `null` to disable auto-registration entirely. Individual `THUB_LISTEN` / `THUB_PUBLIC_URL` / `THUB_DATA_DIR` / `THUB_SESSION_SECRET` / `THUB_CLIENT_JOIN_KEY` env vars override whatever the file set.
35
35
 
36
+ `npm install -g` creates `~/.config/thub/coordinator.json` for you if it doesn't already exist, with a home-anchored `dataDir` and a freshly generated random `sessionSecret` (not the placeholder above) — a re-install never overwrites it or regenerates the secret. Set `publicUrl` and `clientJoinKey` yourself before relying on auto-registration.
37
+
36
38
  ## Running it
37
39
 
38
40
  ```bash
@@ -40,46 +42,46 @@ The Coordinator loads a plain **JSON** config file (no YAML support). Resolution
40
42
  # password (creates user "admin") or use thub-admin afterwards.
41
43
  THUB_COORDINATOR_CONFIG=/etc/thub/coordinator.json \
42
44
  THUB_BOOTSTRAP_ADMIN_PASSWORD=correct-horse-battery-staple \
43
- node src/server.js
45
+ thub-coordinator
44
46
  # -> Reset password for admin user "admin" from THUB_BOOTSTRAP_ADMIN_PASSWORD
45
47
  # -> TestHub Coordinator listening on http://127.0.0.1:8080
46
48
  ```
47
49
 
48
- `THUB_BOOTSTRAP_ADMIN_PASSWORD` is more than a first-run convenience — it's an **exceptional password reset**, checked on every startup, not only when `admin_users` is empty. Whenever it's set, the named account (`THUB_BOOTSTRAP_ADMIN_USER`, default `admin`) has its password forced to it — creating that user as `admin` if it doesn't exist yet, or just resetting the password (never the role) if it does. With it unset, login uses whatever's already in the DB, as normal — unset it again once you're back in, or every subsequent restart re-applies it.
50
+ `thub-coordinator` is the global command from `npm install -g`; from a local checkout of this repo it's `node src/server.js` or `npm start`/`npm run dev` (auto-restart on change) — all equivalent, all reading config the same way (above).
49
51
 
50
- Or just `npm start` / `npm run dev` (auto-restart on change) against the bundled `config.json` default for local development.
52
+ `THUB_BOOTSTRAP_ADMIN_PASSWORD` is more than a first-run convenience — it's an **exceptional password reset**, checked on every startup, not only when `admin_users` is empty. Whenever it's set, the named account (`THUB_BOOTSTRAP_ADMIN_USER`, default `admin`) has its password forced to it — creating that user as `admin` if it doesn't exist yet, or just resetting the password (never the role) if it does. With it unset, login uses whatever's already in the DB, as normal — unset it again once you're back in, or every subsequent restart re-applies it.
51
53
 
52
54
  ## `thub-admin` — the local operator CLI
53
55
 
54
- Talks to the Coordinator's SQLite database directly — no running server required, and no HTTP auth of its own, so it's meant to be run on the Coordinator host itself:
56
+ Talks to the Coordinator's SQLite database directly — no running server required, and no HTTP auth of its own, so it's meant to be run on the Coordinator host itself. `thub-admin` is the global command from `npm install -g`; from a local checkout it's `node bin/thub-admin.js`:
55
57
 
56
58
  ```bash
57
59
  # Create additional dashboard users (first one can also come from
58
60
  # THUB_BOOTSTRAP_ADMIN_PASSWORD above).
59
- node bin/thub-admin.js create-admin alice s3cret --role admin
61
+ thub-admin create-admin alice s3cret --role admin
60
62
 
61
63
  # Register a CI or developer identity — the only credential still issued
62
64
  # by an admin; the token is shown once.
63
- node bin/thub-admin.js agent add ci-firmware --kind ci
65
+ thub-admin agent add ci-firmware --kind ci
64
66
  # -> Agent agt_... created. Token (shown once): agt_...
65
67
 
66
68
  # Mint the shared secret Clients use to self-register, and put it in both
67
69
  # the Coordinator's clientJoinKey and every Client's joinKey.
68
- node bin/thub-admin.js join-key generate
70
+ thub-admin join-key generate
69
71
 
70
72
  # Take a resource out of rotation without an active job (or bring it back).
71
- node bin/thub-admin.js resource maintenance res_abc123 --on
73
+ thub-admin resource maintenance res_abc123 --on
72
74
 
73
75
  # Cancel every queued/assigned/preparing/running job.
74
- node bin/thub-admin.js jobs reset --yes
76
+ thub-admin jobs reset --yes
75
77
 
76
78
  # Permanently delete every finished job, plus its logs and artifacts on disk.
77
- node bin/thub-admin.js jobs clean --yes
79
+ thub-admin jobs clean --yes
78
80
 
79
81
  # Resource groups: constrain which resources a job can schedule onto.
80
- node bin/thub-admin.js group add ci-nightly --comment "shared CI pool"
81
- node bin/thub-admin.js group list
82
- node bin/thub-admin.js group remove <groupId>
82
+ thub-admin group add ci-nightly --comment "shared CI pool"
83
+ thub-admin group list
84
+ thub-admin group remove <groupId>
83
85
  ```
84
86
 
85
87
  Both `jobs reset` and `jobs clean` refuse to run without `--yes` — there's no undo for either, especially `clean`.
package/package.json CHANGED
@@ -1,15 +1,17 @@
1
1
  {
2
2
  "name": "@andrian.yablonskyy/thub-coordinator",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "TestHub Coordinator — job queue, resource registry, scheduler, heartbeat monitor, log/artifact store and web dashboard",
5
5
  "main": "src/server.js",
6
6
  "bin": {
7
- "thub-admin": "./bin/thub-admin.js"
7
+ "thub-admin": "./bin/thub-admin.js",
8
+ "thub-coordinator": "./src/server.js"
8
9
  },
9
10
  "scripts": {
10
11
  "start": "node src/server.js",
11
12
  "dev": "node --watch src/server.js",
12
13
  "test": "node --test test/*.test.js",
14
+ "postinstall": "node scripts/install-default-config.js",
13
15
  "lint": "eslint .",
14
16
  "lint:fix": "eslint . --fix"
15
17
  },
@@ -0,0 +1,74 @@
1
+ /**
2
+ * @file scripts/install-default-config.js
3
+ * @description npm postinstall: creates ~/.config/thub/coordinator.json on a real global install,
4
+ * if it doesn't already exist, with a freshly generated sessionSecret rather than a
5
+ * shared placeholder — so every real installation isn't using the same known secret
6
+ * for its session cookies and artifact-download HMACs (README §12, §13)
7
+ *
8
+ * @author Andrian Yablonskyy
9
+ * @copyright Copyright (c) 2026 Andrian Yablonskyy. All rights reserved.
10
+ *
11
+ * This file is part of TestHub and is proprietary and confidential.
12
+ * Unauthorized copying, modification, distribution, or use of this file,
13
+ * via any medium, is strictly prohibited without prior written permission
14
+ * from AdSystem.PRO.
15
+ */
16
+
17
+ 'use strict';
18
+
19
+ const fs = require('node:fs'),
20
+ os = require('node:os'),
21
+ path = require('node:path'),
22
+ crypto = require('node:crypto'),
23
+
24
+ CONFIG_PATH = path.join(os.homedir(), '.config', 'thub', 'coordinator.json');
25
+
26
+ // npm only sets this for an actual `npm install -g` — absent for a plain
27
+ // local/workspace install (e.g. this monorepo's own `npm install`).
28
+ function isGlobalInstall(){
29
+ return process.env.npm_config_global === 'true';
30
+ }
31
+
32
+ // Deliberately minimal, not a copy of the bundled config.json (which uses
33
+ // a relative dataDir and a fixed sessionSecret for zero-setup `npm run
34
+ // coordinator` in the monorepo — fine there since it's never a real
35
+ // deployment). loadConfig()'s own DEFAULTS already cover everything else
36
+ // (listen, publicUrl, heartbeat/scheduler/jobs/retention/artifacts), so
37
+ // only what genuinely needs to differ for a real standalone install goes
38
+ // here:
39
+ // - dataDir: home-anchored, not cwd-relative — a real install shouldn't
40
+ // silently get a different data directory depending on which
41
+ // directory you happened to launch thub-coordinator from.
42
+ // - sessionSecret: freshly random per install. The code's own default
43
+ // ("dev-only-change-me") is fine for local dev but would otherwise
44
+ // mean every real installation nobody got around to changing shares
45
+ // the exact same session-signing key.
46
+ // - clientJoinKey stays unset (null, the code's own default) — auto-
47
+ // registration is opt-in, and there's nothing sensible to
48
+ // auto-generate here since it also has to be copied to every Client.
49
+ function defaultContent(){
50
+ return {
51
+ dataDir: path.join(os.homedir(), '.local', 'share', 'thub'),
52
+ sessionSecret: crypto.randomBytes(32).toString('hex')
53
+ };
54
+ }
55
+
56
+ // Best-effort and never fails the `npm install` itself. Never overwrites
57
+ // an existing file — a re-install/upgrade must not clobber whatever the
58
+ // user already configured, and must never regenerate sessionSecret out
59
+ // from under a running deployment (that would invalidate every session).
60
+ function main(){
61
+ if (!isGlobalInstall() || fs.existsSync(CONFIG_PATH)){
62
+ return;
63
+ }
64
+ try {
65
+ fs.mkdirSync(path.dirname(CONFIG_PATH), { recursive: true });
66
+ fs.writeFileSync(CONFIG_PATH, JSON.stringify(defaultContent(), null, 2) + '\n', { mode: 0o600 });
67
+ console.log(`thub-coordinator: created ${CONFIG_PATH} with a freshly generated sessionSecret`);
68
+ }
69
+ catch (err){
70
+ console.warn(`thub-coordinator: could not create ${CONFIG_PATH} automatically (${err.message}).`);
71
+ }
72
+ }
73
+
74
+ main();
package/src/config.js CHANGED
@@ -14,11 +14,10 @@
14
14
  'use strict';
15
15
 
16
16
  const fs = require('node:fs'),
17
+ os = require('node:os'),
17
18
  path = require('node:path');
18
19
 
19
- // Defaults mirror README.md §13. Unlike the Client (config.js there), there
20
- // is no FHS-path fallback here — a real deployment must set
21
- // THUB_COORDINATOR_CONFIG explicitly to override the bundled default below.
20
+ // Defaults mirror README.md §13.
22
21
  const DEFAULTS = {
23
22
  listen: '127.0.0.1:8080',
24
23
  publicUrl: 'http://localhost:8080',
@@ -63,13 +62,17 @@ function deepMerge(base, override){
63
62
  return out;
64
63
  }
65
64
 
66
- // Bundled with the package so the Coordinator has something sane to run
67
- // with out of the box; a real deployment overrides it with
68
- // THUB_COORDINATOR_CONFIG or /srv/thub/coordinator.json (§13).
69
- const PACKAGE_DEFAULT_CONFIG_PATH = path.join(__dirname, '..', 'config.json');
65
+ // User-level default (§13) — consulted when THUB_COORDINATOR_CONFIG isn't
66
+ // set, before falling back to the bundled default below.
67
+ const USER_CONFIG_PATH = path.join(os.homedir(), '.config', 'thub', 'coordinator.json'),
68
+
69
+ // Bundled with the package so the Coordinator has something sane to run
70
+ // with out of the box; a real deployment overrides it with
71
+ // THUB_COORDINATOR_CONFIG or ~/.config/thub/coordinator.json (§13).
72
+ PACKAGE_DEFAULT_CONFIG_PATH = path.join(__dirname, '..', 'config.json');
70
73
 
71
74
  function loadConfig(configPath = process.env.THUB_COORDINATOR_CONFIG){
72
- const candidate = [configPath, PACKAGE_DEFAULT_CONFIG_PATH].find(
75
+ const candidate = [configPath, USER_CONFIG_PATH, PACKAGE_DEFAULT_CONFIG_PATH].find(
73
76
  (p) => p && fs.existsSync(p)
74
77
  ),
75
78
  fileConfig = candidate ? JSON.parse(fs.readFileSync(candidate, 'utf8')) || {} : {},
package/src/server.js CHANGED
@@ -1,3 +1,5 @@
1
+ #!/usr/bin/env node
2
+
1
3
  /**
2
4
  * @file packages/coordinator/src/server.js
3
5
  * @description Coordinator entry point: wires services, mounts routes, and starts the HTTP server
@@ -162,7 +164,7 @@ function ensureBootstrapAdmin(services){
162
164
  if (services.adminUsers.count() === 0){
163
165
  console.warn(
164
166
  'No admin_users exist and THUB_BOOTSTRAP_ADMIN_PASSWORD is not set — ' +
165
- 'create one with: node bin/thub-admin.js create-admin <user> <password>'
167
+ 'create one with: thub-admin create-admin <user> <password> (or node bin/thub-admin.js ... from a checkout)'
166
168
  );
167
169
  }
168
170
  return;