@andrian.yablonskyy/thub-coordinator 1.1.16 → 1.1.17

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
@@ -172,7 +172,7 @@ The Coordinator's own version (`ver. X.Y.Z`) is shown under the TestHub logo, to
172
172
  **Versions and self-update.** The Coordinator checks the npm registry for the latest Coordinator, Agent and Client every `updates.checkIntervalMin` (default 15 minutes, `0` disables; the older `checkIntervalHours` is ignored; `updates.registry` for a private mirror) and immediately with the navbar's **check for updates** button (admins; also on the Agents and Resources pages), which reports in a toast whether the Coordinator is current and which connected resources can update their Client. A newer Coordinator shows as an **Update app to X.Y.Z** button in the navbar for admins (a badge under the logo for viewers); Agents and Resources show each one's version with an **update available** or pending **→ vX.Y.Z** badge. Admins request self-updates per agent/resource (**Update**, which becomes **Cancel update**) or with **Update all agents** / **Update all clients**, always to the latest version:
173
173
  - Clients get a `self-update` command on their next heartbeat and install it through their root `thub-client-update` helper, once none of the host's instances is busy.
174
174
  - Agents install it at the start of their next run (`GET /api/v1/agents/me/update`), then re-run the command on the new version.
175
- - The Coordinator itself, only when started with `--self-update` (off by default; under systemd add it to the unit's `ExecStart`, a re-install keeps it): the **Update app** button writes `<dataDir>/update-request.json`; the root `thub-coordinator-update.path` unit (installed by `sudo npm i -g`) runs `npm i -g` for it, and the postinstall restarts the service (the dashboard and API are briefly down). Logs: `journalctl -u thub-coordinator-update`. Without that unit, use `thub-admin self-update` on the host.
175
+ - The Coordinator itself, only when started with `--self-update` (a systemd install has it; elsewhere, e.g. in a container, it's off and the dashboard shows nothing about Coordinator updates): the **Update app** button writes `<dataDir>/update-request.json`; the root `thub-coordinator-update.path` unit (installed by `sudo npm i -g`) runs `npm i -g` for it, and the postinstall restarts the service (the dashboard and API are briefly down). Logs: `journalctl -u thub-coordinator-update`. Without that unit, use `thub-admin self-update` on the host.
176
176
 
177
177
  A request is cleared when the Agent/Client reports the new version.
178
178
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@andrian.yablonskyy/thub-coordinator",
3
- "version": "1.1.16",
3
+ "version": "1.1.17",
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
  "engines": {
@@ -34,23 +34,15 @@ const fs = require('node:fs'),
34
34
  // The checked-in unit only has placeholders — fill in the target user and
35
35
  // wherever *this* install's node and server.js actually are, so it works
36
36
  // regardless of npm prefix or an nvm-managed Node.
37
- // `--self-update` (README §10.2) is off by default; once added to the
38
- // installed unit's ExecStart, a re-install keeps it there.
39
- function selfUpdateFlag(existingUnit = UNIT_DEST){
40
- try {
41
- return /^ExecStart=.*\s--self-update(\s|$)/m.test(fs.readFileSync(existingUnit, 'utf8')) ? ' --self-update' : '';
42
- }
43
- catch {
44
- return '';
45
- }
46
- }
47
-
48
37
  function renderUnit(user, paths){
49
38
  return fs.readFileSync(UNIT_SRC, 'utf8')
50
39
  .replace(/^User=.*$/m, `User=${user.name}`)
51
40
  .replace(/^Group=.*$/m, `Group=${user.gid}`)
52
41
  .replace(/^Environment=THUB_COORDINATOR_CONFIG=.*$/m, `Environment=THUB_COORDINATOR_CONFIG=${paths.configPath}`)
53
- .replace(/^ExecStart=.*$/m, `ExecStart=${process.execPath} ${SERVER_PATH}${selfUpdateFlag()}`)
42
+ // --self-update: the dashboard's "Update app" (README §10.2), through the
43
+ // root thub-coordinator-update units installed below — on for a systemd
44
+ // install. To turn it off, override ExecStart in a drop-in.
45
+ .replace(/^ExecStart=.*$/m, `ExecStart=${process.execPath} ${SERVER_PATH} --self-update`)
54
46
  .replace(/^ReadWritePaths=.*$/m, `ReadWritePaths=${[paths.dataDir, paths.dbDir].filter(Boolean).join(' ')}`);
55
47
  }
56
48
 
package/src/web/routes.js CHANGED
@@ -807,9 +807,13 @@ function createWebRouter({ services, config }){
807
807
  return { type: 'danger', text: `Update check failed — the npm registry couldn't be reached (${s.error}). Nothing was checked.` };
808
808
  }
809
809
 
810
- parts.push(s.coordinatorUpdate
811
- ? `Coordinator v${s.coordinatorVersion} → v${s.coordinatorUpdate} available.`
812
- : `Coordinator is up to date (v${s.coordinatorVersion}).`);
810
+ // The Coordinator's own version only when it can update itself
811
+ // (--self-update); Agents and Clients are always reported.
812
+ if (s.selfUpdate){
813
+ parts.push(s.coordinatorUpdate
814
+ ? `Coordinator v${s.coordinatorVersion} → v${s.coordinatorUpdate} available.`
815
+ : `Coordinator is up to date (v${s.coordinatorVersion}).`);
816
+ }
813
817
 
814
818
  const connected = services.registry.list().filter((r) => r.status !== RESOURCE_STATES.OUT_OF_SERVICE && r.last_heartbeat_at),
815
819
  outdated = connected.filter((r) => r.client_version && isNewer(s.latest.client, r.client_version)),
@@ -834,7 +838,7 @@ function createWebRouter({ services, config }){
834
838
  if (s.error){
835
839
  parts.push(`Some checks failed: ${s.error}`);
836
840
  }
837
- const behind = s.coordinatorUpdate || outdated.length || unknown.length;
841
+ const behind = (s.selfUpdate && s.coordinatorUpdate) || outdated.length || unknown.length;
838
842
  return { type: behind || s.error ? 'warning' : 'success', text: parts.join('\n') };
839
843
  }));
840
844
 
@@ -3,8 +3,10 @@ Description=TestHub Coordinator
3
3
  After=network-online.target
4
4
  Wants=network-online.target
5
5
 
6
- # Add --self-update to ExecStart to let admins update the Coordinator from
7
- # the dashboard ("Update app", README §10.2); a re-install keeps it.
6
+ # --self-update lets admins update the Coordinator from the dashboard
7
+ # ("Update app", README §10.2) through thub-coordinator-update.path. To turn
8
+ # it off: sudo systemctl edit thub-coordinator, then [Service], ExecStart=
9
+ # (empty) and ExecStart=<the same line without --self-update>.
8
10
  # User, Group, THUB_COORDINATOR_CONFIG, ReadWritePaths and ExecStart are
9
11
  # rewritten by scripts/install-systemd-unit.js at install time to the user
10
12
  # who ran `sudo npm i -g` and this install's real node/server.js paths.
@@ -13,7 +15,7 @@ Type=simple
13
15
  User=thub
14
16
  Group=thub
15
17
  Environment=THUB_COORDINATOR_CONFIG=/var/lib/thub/.config/thub/coordinator.json
16
- ExecStart=/usr/bin/node /usr/lib/node_modules/@andrian.yablonskyy/thub-coordinator/src/server.js
18
+ ExecStart=/usr/bin/node /usr/lib/node_modules/@andrian.yablonskyy/thub-coordinator/src/server.js --self-update
17
19
  Restart=always
18
20
  RestartSec=2
19
21
  NoNewPrivileges=yes
@@ -105,9 +105,11 @@ test('on startup, a finished update\'s status is cleared; a failure is kept for
105
105
  assert.equal(fs.existsSync(statusFile), true);
106
106
  });
107
107
 
108
- test('self-update is off unless the Coordinator was started with --self-update: no request, no button', async (t) => {
108
+ test('without --self-update: no request, no Coordinator check or button; Agents and Clients keep theirs', async (t) => {
109
109
  const dataDir = fs.mkdtempSync(path.join(os.tmpdir(), 'thub-updates-')),
110
- updates = createUpdatesService({ config: { dataDir, updates: {} }, pathUnit: path.join(dataDir, 'fake.path'), fetchLatest: async () => '99.0.0' });
110
+ updates = createUpdatesService({
111
+ config: { dataDir, updates: {} }, pathUnit: path.join(dataDir, 'fake.path'), fetchLatest: async () => '99.0.0'
112
+ });
111
113
  fs.writeFileSync(path.join(dataDir, 'fake.path'), '');
112
114
  await updates.checkNow();
113
115
  assert.equal(updates.status().selfUpdate, false);
@@ -123,7 +125,9 @@ test('self-update is off unless the Coordinator was started with --self-update:
123
125
  for (const selfUpdate of [false, true]){
124
126
  const config = { ...loadConfig(file), selfUpdate },
125
127
  services = buildServices(config);
126
- services.updates.checkNow = async () => {};
128
+ services.updates.checkNow = async () => ({
129
+ fetched: ['coordinator', 'client'], latest: { client: '9.9.9' }, coordinatorVersion: '1.0.0', coordinatorUpdate: '99.0.0', selfUpdate
130
+ });
127
131
  Object.assign(services.updates, { status: () => ({ latest: {}, coordinatorVersion: '1.0.0', coordinatorUpdate: '99.0.0', selfUpdate }) });
128
132
  services.adminUsers.create({ username: `root${selfUpdate}`, email: `r${selfUpdate}@example.com`, role: 'admin', password: 'pw' }, { by: 't' });
129
133
  const server = createApp(config, services).listen(0, '127.0.0.1');
@@ -137,7 +141,19 @@ test('self-update is off unless the Coordinator was started with --self-update:
137
141
  method: 'POST', body: new URLSearchParams({ username: `root${selfUpdate}`, password: 'pw' }), redirect: 'manual'
138
142
  })).headers.get('set-cookie').split(';')[0],
139
143
  html = await (await fetch(`${base}/runners`, { headers: { cookie } })).text();
144
+ // The Coordinator's own: Update app and the navbar's check — only with --self-update.
140
145
  assert.equal(/action="\/updates\/coordinator"/.test(html), selfUpdate, `Update app button with selfUpdate=${selfUpdate}`);
141
- assert.match(html, /v99\.0\.0 available|Update app to 99\.0\.0/);
146
+ assert.equal(/action="\/updates\/check" data-submit-busy/.test(html), selfUpdate, `navbar update check with selfUpdate=${selfUpdate}`);
147
+ assert.equal(/99\.0\.0/.test(html.slice(0, html.indexOf('<main'))), selfUpdate); // nothing about a new Coordinator in the navbar
148
+ // Clients' (and Agents') check and update-all: always.
149
+ assert.match(html, /<form class="d-inline" method="post" action="\/updates\/check">/);
150
+ assert.match(html, /action="\/runners\/update-all"/);
151
+ // "Check for updates" (Runners page): Clients always, the Coordinator only with --self-update.
152
+ const check = await fetch(`${base}/updates/check`, {
153
+ method: 'POST', headers: { cookie }, body: new URLSearchParams({ returnTo: '/runners' }), redirect: 'manual'
154
+ }),
155
+ toast = await (await fetch(`${base}${check.headers.get('location')}`, { headers: { cookie } })).text();
156
+ assert.match(toast, /No connected runners|connected runner/);
157
+ assert.equal(/Coordinator v1\.0\.0 → v99\.0\.0 available/.test(toast), selfUpdate);
142
158
  }
143
159
  });
@@ -13,7 +13,7 @@
13
13
  #[code ~/.config/thub/coordinator.json] (with a random #[code sessionSecret]) and
14
14
  #[code ~/var/lib/thub] (database, avatars). It installs, enables and starts
15
15
  #[code thub-coordinator.service], plus the root helper behind the navbar's #[strong Update app] button
16
- (off unless the Coordinator runs with #[code --self-update]; see Updates below).
16
+ (the unit starts the Coordinator with #[code --self-update]; see Updates below).
17
17
  The unit uses #[code ProtectSystem=strict] with #[code PrivateTmp=yes], so the data directory and a
18
18
  private #[code /tmp] (SQLite's temp files) are the only places the service can write.
19
19
 
@@ -176,11 +176,10 @@
176
176
  h3.h6 Updates
177
177
  p.small.mb-0.
178
178
  The Coordinator checks npm for new Coordinator, Agent and Client versions. Admins update Agents and Clients
179
- from the #[a(href="/admin/agents") Agents] and #[a(href="/runners") Runners] pages; Clients install an update
180
- only between jobs. Updating the #[strong Coordinator] from the dashboard (#[strong Update app] in the navbar)
181
- is #[strong off by default]: start it with #[code --self-update] to turn it on. Under systemd:
182
- #[code sudo sed -i 's|server.js$|server.js --self-update|' /etc/systemd/system/thub-coordinator.service],
183
- then #[code sudo systemctl daemon-reload &amp;&amp; sudo systemctl restart thub-coordinator] (a re-install keeps it).
184
- Without it, a #[strong vX.Y.Z available] badge shows; update by hand — #[code thub-admin self-update],
185
- #[code sudo npm i -g …], or a new container image. Manual alternatives for the others:
186
- #[code thub self-update] and #[code thub-client self-update].
179
+ from the #[a(href="/admin/agents") CI tokens] and #[a(href="/runners") Runners] pages (always there); Clients install
180
+ an update only between jobs. The #[strong Coordinator]'s own update (the navbar's check for updates and
181
+ #[strong Update app]) is there only when it runs with #[code --self-update]. A systemd install has it on
182
+ (#[code sudo npm i -g] writes it into the unit); to turn it off, override #[code ExecStart] in a drop-in
183
+ (#[code sudo systemctl edit thub-coordinator]). Started any other way, e.g. in a container, it's off and the dashboard
184
+ shows nothing about Coordinator updates: update it by hand (#[code thub-admin self-update], #[code sudo npm i -g …])
185
+ or with a new image. Manual alternatives for the others: #[code thub self-update] and #[code thub-client self-update].
@@ -2,8 +2,8 @@
2
2
  p.
3
3
  The Coordinator runs as one container: the published package in a Node.js image, its SQLite database on a
4
4
  persistent volume, TLS at the ingress. Clients and Agents connect from outside at #[code publicUrl].
5
- Self-update stays off: the image starts the Coordinator without #[code --self-update], and you update it by
6
- deploying a new image tag.
5
+ Self-update stays off: the image starts the Coordinator without #[code --self-update], so the dashboard shows
6
+ nothing about Coordinator updates; you update it by deploying a new image tag.
7
7
 
8
8
  h3.h6 1. Build the image
9
9
  +code('Dockerfile').
@@ -160,7 +160,7 @@
160
160
  h3.h6 Things to know
161
161
  ul.small
162
162
  li #[strong One replica.] One SQLite file and an in-process scheduler: no scale-out, no HPA. #[code Recreate] means a few seconds of downtime per upgrade; Clients and Agents reconnect by themselves.
163
- li #[strong Updating:] deploy a new image tag. Migrations run on the new pod's first start. #[strong Update app] stays off (no #[code --self-update]); admins see a #[strong vX.Y.Z available] badge. Agents and Clients still update from the dashboard.
163
+ li #[strong Updating:] deploy a new image tag. Migrations run on the new pod's first start. No #[code --self-update], so no Coordinator update check or #[strong Update app] on the dashboard. Agents and Clients still update from it.
164
164
  li #[strong Storage:] block storage (ReadWriteOnce), not NFS. A separate database volume: mount it and set #[code THUB_DB_PATH].
165
165
  li #[strong /tmp] is an #[code emptyDir]: SQLite needs a writable temp directory and the root filesystem is read-only.
166
166
  li #[strong Backups:] #[code kubectl -n thub exec deploy/thub-coordinator -- node -e "new (require('/app/node_modules/better-sqlite3'))('/var/lib/thub/thub.db').backup('/var/lib/thub/backup.db')"], then #[code kubectl cp].
package/views/layout.pug CHANGED
@@ -29,12 +29,13 @@ html(lang="en" data-bs-theme=(user && user.theme) || "auto")
29
29
  data-bs-title=`Coordinator v${coordinatorVersion} · thub-common v${commonVersion} (validates job specs)`
30
30
  )
31
31
  | ver. #{coordinatorVersion}
32
- //- Without --self-update (or for non-admins): just the notice.
33
- if user && updates.coordinatorUpdate && !(can.admin && updates.selfUpdate)
32
+ //- With --self-update, for those who can't update it themselves.
33
+ //- Without it, nothing about Coordinator updates is shown.
34
+ if user && updates.selfUpdate && updates.coordinatorUpdate && !can.admin
34
35
  span.badge.text-bg-warning.ms-1(
35
36
  data-bs-toggle="tooltip"
36
37
  data-bs-placement="bottom"
37
- data-bs-title=updates.selfUpdate ? 'An admin can update the Coordinator from the navbar' : 'Update the Coordinator by hand (thub-admin self-update, npm i -g, or a new container image); self-update from the dashboard is off'
38
+ data-bs-title="An admin can update the Coordinator from the navbar"
38
39
  )= `v${updates.coordinatorUpdate} available`
39
40
  button.navbar-toggler(type="button" data-bs-toggle="collapse" data-bs-target="#nav")
40
41
  span.navbar-toggler-icon
@@ -80,7 +81,9 @@ html(lang="en" data-bs-theme=(user && user.theme) || "auto")
80
81
  )
81
82
  i.bi.bi-question-circle.me-1
82
83
  | Help
83
- if user && can.operate
84
+ //- The Coordinator's own update check: only with --self-update.
85
+ //- (Agents and Clients are checked from their own pages, always.)
86
+ if user && can.operate && updates.selfUpdate
84
87
  li.nav-item.d-flex.align-items-center.me-2
85
88
  form(method="post" action="/updates/check" data-submit-busy)
86
89
  input(type="hidden" name="returnTo" value=currentPath)