@crvouga/dev-fleet 0.6.0 → 0.7.0
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 +4 -10
- package/ansible/macos/manager.yml +39 -2
- package/ansible/macos/policy.json +6 -1
- package/ansible/monitoring/agent.yml +2 -0
- package/ansible/ubuntu/baseline.yml +6 -0
- package/ansible/ubuntu/recovery.yml +2 -0
- package/ansible/ubuntu/tailscale.yml +2 -0
- package/docs/consumer-setup.md +2 -4
- package/docs/manager-recovery.md +78 -0
- package/docs/operations.md +8 -0
- package/docs/state.md +38 -6
- package/docs/topology.md +4 -5
- package/package.json +2 -2
- package/schema/check-ids.json +4 -0
- package/src/check-owners.ts +163 -0
- package/src/cli/fleet-restore.ts +205 -0
- package/src/cli/fleet-state.ts +57 -5
- package/src/cli/fleet-sync-manager.ts +53 -15
- package/src/cli/fleet-sync-state.ts +18 -8
- package/src/cli/fleet-sync.ts +32 -3
- package/src/cli/register-fleet.ts +31 -0
- package/src/cli/registry.ts +14 -1
- package/src/platform/manager-host.ts +184 -0
- package/src/repair-catalog.ts +64 -0
- package/src/repair-plan.ts +25 -13
- package/src/state/check.ts +7 -0
- package/src/state/holder.ts +50 -0
- package/src/state/litestream.ts +6 -3
- package/src/state/migrations/0001-documents.sql +13 -0
- package/src/state/migrations/0002-lease.sql +7 -0
- package/src/state/store.ts +125 -26
- package/src/state/sync.ts +131 -10
package/README.md
CHANGED
|
@@ -170,16 +170,10 @@ says where the bucket is and the command is passed its credentials:
|
|
|
170
170
|
npx devfleet fleet-state # where the state is, and how it stands with the bucket
|
|
171
171
|
```
|
|
172
172
|
|
|
173
|
-
The copy on the manager is disposable
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
npm ci
|
|
178
|
-
npx devfleet fleet-sync # with the two bucket credentials in the environment
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
The roster and the approved revision come back from the bucket, and the
|
|
182
|
-
machine is rebuilt from Git.
|
|
173
|
+
The copy on the manager is disposable: the roster and the approved revision
|
|
174
|
+
come back from the bucket, and the machine is rebuilt from Git.
|
|
175
|
+
[Replacing the manager](docs/manager-recovery.md) is the runbook for a new or
|
|
176
|
+
lost manager.
|
|
183
177
|
|
|
184
178
|
Some runs are started by a machine rather than by you: a worker that hands
|
|
185
179
|
`fleet-sync` to the manager, for example. They get no environment, so save the
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
---
|
|
2
2
|
# Manager power policy. Workers depend on the manager's singleton services
|
|
3
|
-
# (CLI proxy, Beszel hub), so the manager never sleeps on AC power
|
|
4
|
-
#
|
|
3
|
+
# (CLI proxy, Beszel hub), so the manager never sleeps on AC power and starts
|
|
4
|
+
# again by itself when power returns (autorestart). Sleep on battery is left
|
|
5
|
+
# to the human. The settings are the `pmset` object in
|
|
5
6
|
# policy.json; fleet-sync measures the same object as manager:power. Changing
|
|
6
7
|
# them needs an administrator: ansible-playbook -K manager.yml
|
|
7
8
|
- name: Keep the fleet manager awake on AC power
|
|
@@ -29,5 +30,41 @@
|
|
|
29
30
|
when: observed != item.value
|
|
30
31
|
changed_when: true
|
|
31
32
|
|
|
33
|
+
# Docker Desktop hosts the CLI proxy and the monitoring hub, so it starts at
|
|
34
|
+
# login: after a power cut the manager comes back without a person. The
|
|
35
|
+
# setting is the `dockerDesktop` object in policy.json; fleet-sync measures it
|
|
36
|
+
# as part of manager:boot-recovery. Only that key of the user's settings file
|
|
37
|
+
# is changed.
|
|
38
|
+
- name: Start Docker Desktop at login
|
|
39
|
+
hosts: localhost
|
|
40
|
+
connection: local
|
|
41
|
+
become: false
|
|
42
|
+
gather_facts: false
|
|
43
|
+
vars_files:
|
|
44
|
+
- policy.json
|
|
45
|
+
vars:
|
|
46
|
+
docker_settings: "{{ lookup('ansible.builtin.env', 'HOME') }}/{{ dockerDesktop.settings }}"
|
|
47
|
+
tasks:
|
|
48
|
+
- name: Find the Docker Desktop settings
|
|
49
|
+
ansible.builtin.stat:
|
|
50
|
+
path: '{{ docker_settings }}'
|
|
51
|
+
register: docker_settings_file
|
|
52
|
+
- name: Read the Docker Desktop settings
|
|
53
|
+
ansible.builtin.slurp:
|
|
54
|
+
src: '{{ docker_settings }}'
|
|
55
|
+
register: docker_settings_text
|
|
56
|
+
when: docker_settings_file.stat.exists
|
|
57
|
+
- name: Turn on start at login, keeping every other setting
|
|
58
|
+
ansible.builtin.copy:
|
|
59
|
+
dest: '{{ docker_settings }}'
|
|
60
|
+
content: "{{ current | combine({dockerDesktop.key: true}) | to_nice_json(indent=2) }}\n"
|
|
61
|
+
mode: '0644'
|
|
62
|
+
vars:
|
|
63
|
+
current: '{{ docker_settings_text.content | b64decode | from_json }}'
|
|
64
|
+
# Docker Desktop writes the file the first time it runs.
|
|
65
|
+
when:
|
|
66
|
+
- docker_settings_file.stat.exists
|
|
67
|
+
- (current[dockerDesktop.key] | default(false)) != true
|
|
68
|
+
|
|
32
69
|
- name: Stage the fleet's T3 policy
|
|
33
70
|
ansible.builtin.import_playbook: t3-policy.yml
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
"architectures": ["arm64", "x86_64"],
|
|
5
5
|
"pmset": {
|
|
6
6
|
"sleep": "0",
|
|
7
|
-
"womp": "1"
|
|
7
|
+
"womp": "1",
|
|
8
|
+
"autorestart": "1"
|
|
8
9
|
},
|
|
9
10
|
"beszelDaemon": "dev.devfleet.beszel-agent",
|
|
10
11
|
"disk": {
|
|
@@ -16,5 +17,9 @@
|
|
|
16
17
|
"unhealthyChecks": 3,
|
|
17
18
|
"maxRestartsPerHour": 3,
|
|
18
19
|
"intervalSeconds": 60
|
|
20
|
+
},
|
|
21
|
+
"dockerDesktop": {
|
|
22
|
+
"settings": "Library/Group Containers/group.com.docker/settings-store.json",
|
|
23
|
+
"key": "AutoStart"
|
|
19
24
|
}
|
|
20
25
|
}
|
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
connection: local
|
|
5
5
|
become: true
|
|
6
6
|
gather_facts: true
|
|
7
|
+
# fleet-repair re-applies this play alone for a failing beszel agent check.
|
|
8
|
+
tags: [monitoring]
|
|
7
9
|
vars:
|
|
8
10
|
beszel_lock: "{{ lookup('ansible.builtin.file', 'lock.json') | from_json }}"
|
|
9
11
|
beszel_arch: "{{ {'x86_64': 'amd64', 'aarch64': 'arm64'}.get(ansible_facts.architecture, 'unsupported') }}"
|
|
@@ -8,24 +8,30 @@
|
|
|
8
8
|
- policy.json
|
|
9
9
|
pre_tasks:
|
|
10
10
|
- ansible.builtin.import_tasks: tasks/release.yml
|
|
11
|
+
tags: [always]
|
|
11
12
|
- name: Require an explicit non-root user
|
|
12
13
|
ansible.builtin.assert:
|
|
13
14
|
that:
|
|
14
15
|
- development_user is match('^[a-z_][a-z0-9_-]{0,31}$')
|
|
15
16
|
- development_user != 'root'
|
|
16
17
|
fail_msg: Unsupported user; name the non-root developer account.
|
|
18
|
+
tags: [always]
|
|
17
19
|
- name: Read the designated existing account
|
|
18
20
|
ansible.builtin.getent:
|
|
19
21
|
database: passwd
|
|
20
22
|
key: '{{ development_user }}'
|
|
23
|
+
tags: [always]
|
|
21
24
|
- name: Refuse a different user identity or home
|
|
22
25
|
ansible.builtin.assert:
|
|
23
26
|
that:
|
|
24
27
|
- ansible_facts.getent_passwd[development_user][1] | int == development_uid | int
|
|
25
28
|
- ansible_facts.getent_passwd[development_user][4] == development_home
|
|
29
|
+
tags: [always]
|
|
26
30
|
- ansible.builtin.import_tasks: tasks/dpkg.yml
|
|
27
31
|
# Memory and disk safety must land even when the disk is nearly full.
|
|
32
|
+
# fleet-repair re-applies them alone (--tags disk) for worker:capacity:*.
|
|
28
33
|
- ansible.builtin.import_tasks: tasks/resources.yml
|
|
34
|
+
tags: [disk]
|
|
29
35
|
- name: Inspect capacity before package and service changes
|
|
30
36
|
ansible.builtin.command:
|
|
31
37
|
argv:
|
package/docs/consumer-setup.md
CHANGED
|
@@ -382,10 +382,8 @@ and codes; leave out everything private.
|
|
|
382
382
|
- **Upgrade the fleet.** `npm install --save-dev --save-exact @crvouga/dev-fleet@latest`,
|
|
383
383
|
commit, `npx devfleet fleet-sync`. The canary proves the new revision first.
|
|
384
384
|
Install an older version and sync to roll back.
|
|
385
|
-
- **Replace the manager.**
|
|
386
|
-
|
|
387
|
-
roster and the approved revision come back from the bucket. See
|
|
388
|
-
[state](state.md) for what the bucket does not hold yet.
|
|
385
|
+
- **Replace the manager.** Follow
|
|
386
|
+
[replacing the manager](manager-recovery.md).
|
|
389
387
|
|
|
390
388
|
## Moving from a pinned commit and a wrapper script
|
|
391
389
|
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Replacing the manager
|
|
2
|
+
|
|
3
|
+
This is the one runbook for a manager that is replaced or lost: stolen, a dead
|
|
4
|
+
disk, or a new machine. Other documents link here and do not describe the
|
|
5
|
+
procedure themselves.
|
|
6
|
+
|
|
7
|
+
## What must exist off the machine
|
|
8
|
+
|
|
9
|
+
Nothing on the old manager is needed. Before you need this runbook, make sure
|
|
10
|
+
these exist elsewhere:
|
|
11
|
+
|
|
12
|
+
| What | Where |
|
|
13
|
+
| ----------------------------------- | ------------------------------------------------------------------------ |
|
|
14
|
+
| Your project: `fleet.json`, the pin | Its Git remote |
|
|
15
|
+
| The fleet state | The bucket `fleet.json` names under `state` |
|
|
16
|
+
| The bucket's two credentials | Your password manager (never the manager alone) |
|
|
17
|
+
| Secrets | Your `secrets` provider; `file` and `env` secrets must be kept elsewhere |
|
|
18
|
+
| The CLI proxy's encrypted snapshots | Its restic repository (`vault` secrets provider only) |
|
|
19
|
+
| Your Tailscale and Docker accounts | Yours: signing in is the only step only you can do |
|
|
20
|
+
|
|
21
|
+
## Restore
|
|
22
|
+
|
|
23
|
+
On the new machine, with Git, Node and Docker installed:
|
|
24
|
+
|
|
25
|
+
1. Give it the manager's local host name: `manager.id` in `fleet.json`
|
|
26
|
+
(`sudo scutil --set LocalHostName ID` on macOS). `fleet-sync` refuses to
|
|
27
|
+
run on any other machine.
|
|
28
|
+
2. Sign in to Tailscale and approve the device. Sign in to Docker Desktop if
|
|
29
|
+
your plan needs it.
|
|
30
|
+
3. Fetch the project and check every prerequisite. Nothing is changed:
|
|
31
|
+
|
|
32
|
+
```sh
|
|
33
|
+
git clone YOUR-PROJECT && cd YOUR-PROJECT && npm ci
|
|
34
|
+
export DEVFLEET_BUCKET_ACCESS_KEY_ID=… DEVFLEET_BUCKET_SECRET_ACCESS_KEY=…
|
|
35
|
+
npx devfleet fleet-restore --dry-run
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
It fails on anything missing (`restore:*` checks, each with its remedy)
|
|
39
|
+
and lists what will be restored, in order.
|
|
40
|
+
|
|
41
|
+
4. Restore, then converge:
|
|
42
|
+
|
|
43
|
+
```sh
|
|
44
|
+
npx devfleet fleet-restore # the fleet state, from the bucket
|
|
45
|
+
npx devfleet fleet-sync --take-over # the manager, then every worker
|
|
46
|
+
npx devfleet fleet-state --init # credentials for runs the manager starts itself
|
|
47
|
+
npx devfleet cliproxy install # the proxy, from its snapshots
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
The proxy's own steps are in [its migration runbook](../cliproxy/docs/migration.md).
|
|
51
|
+
`--take-over` is needed while the old manager's lease runs (seven days
|
|
52
|
+
after its last change). Use it only once the old machine is gone: an old
|
|
53
|
+
manager that wakes up afterwards is refused with `FLEET_MANAGER_LEASE_HELD`.
|
|
54
|
+
|
|
55
|
+
5. Adopt the T3 instances again: `npx devfleet t3-apply --import ID`.
|
|
56
|
+
|
|
57
|
+
Workers need nothing: they reach the manager by its Tailscale name, so they
|
|
58
|
+
follow it once the new machine holds that name. If the Tailscale name
|
|
59
|
+
changed, change `manager.network.tailscaleName` in `fleet.json` and run
|
|
60
|
+
`fleet-sync`, which re-points every worker.
|
|
61
|
+
|
|
62
|
+
## What is lost
|
|
63
|
+
|
|
64
|
+
The monitoring hub's metric history, alerts and settings made by hand. Its
|
|
65
|
+
accounts and systems are recreated. With `env` or `file` secrets, whatever
|
|
66
|
+
was only on the old manager.
|
|
67
|
+
|
|
68
|
+
## Coming back by itself
|
|
69
|
+
|
|
70
|
+
A manager restarts after a power cut only when it is set up to
|
|
71
|
+
(`manager:boot-recovery` in `fleet-status`): `autorestart` and Docker Desktop
|
|
72
|
+
at login are applied by `ansible/macos/manager.yml`. The per-user services
|
|
73
|
+
need a user session. **With FileVault on, a Mac waits at the login window
|
|
74
|
+
after every restart until a person signs in**, so nothing comes back
|
|
75
|
+
unattended. That is the trade-off: FileVault protects the proxy's logins and
|
|
76
|
+
the bucket credentials on a stolen Mac; automatic login without FileVault
|
|
77
|
+
lets the fleet recover from a power cut alone. `fleet-status` reports
|
|
78
|
+
FileVault as a warning with that consequence; the choice is yours.
|
package/docs/operations.md
CHANGED
|
@@ -304,6 +304,14 @@ The public CLI exposes `devfleet fleet-repair --list` (static catalog) and
|
|
|
304
304
|
in this package only executes allowlisted existing owner commands. Native
|
|
305
305
|
account logins and unknown/manual checks always remain operator steps.
|
|
306
306
|
|
|
307
|
+
Every check ID has an owner in `src/check-owners.ts`: a recipe, or the reason
|
|
308
|
+
only a person can repair it, which the plan prints beside the check
|
|
309
|
+
(`manual`). Machine repairs are one tag of a playbook the worker already
|
|
310
|
+
applies: `recovery` (`worker:service-recovery`), `disk`
|
|
311
|
+
(`worker:capacity:*`), `monitoring` (the Beszel agent) and `network` (the
|
|
312
|
+
Tailscale daemon). `state:*` checks take the fleet state from the bucket
|
|
313
|
+
again. A feature fails when a check is reported that has no owner.
|
|
314
|
+
|
|
307
315
|
Use `--apply --yes` only after review; `--plan-hash` binds application to the
|
|
308
316
|
previewed steps, targets and checkout/shared-input fingerprints. The remote
|
|
309
317
|
protocol independently requires explicit apply, a supported repair action and
|
package/docs/state.md
CHANGED
|
@@ -64,9 +64,9 @@ arguments and never written to a report or a log.
|
|
|
64
64
|
|
|
65
65
|
**A new fleet.** Nothing more: the first `fleet-sync` seeds the bucket.
|
|
66
66
|
|
|
67
|
-
**A replacement manager
|
|
68
|
-
|
|
69
|
-
|
|
67
|
+
**A replacement manager.** The bucket already holds the fleet, so its state is
|
|
68
|
+
downloaded before anything is changed. Follow
|
|
69
|
+
[replacing the manager](manager-recovery.md).
|
|
70
70
|
|
|
71
71
|
**Runs the manager starts by itself.** A worker that hands `fleet-sync` to the
|
|
72
72
|
manager starts a command there that gets no environment and no project. Save
|
|
@@ -172,6 +172,7 @@ it records what it applied. Its report carries the result as the
|
|
|
172
172
|
| `local-ahead` | This machine has changes the bucket has not received | The next change, or `--push`, uploads them |
|
|
173
173
|
| `bucket-ahead` | Another manager changed the bucket; this copy is stale | The next change or `fleet-sync` takes the bucket's version |
|
|
174
174
|
| `unreachable` | The bucket did not answer, or did not take an upload | Changes still apply locally and are uploaded later |
|
|
175
|
+
| `corrupt` | The bucket answered with a version that cannot be read | Changes apply locally; `--restore --timestamp` repairs it |
|
|
175
176
|
| `diverged` | Both sides changed | Refused. `--restore --yes` takes the bucket's version |
|
|
176
177
|
|
|
177
178
|
A stale copy never overwrites the bucket. A machine that was offline while
|
|
@@ -210,6 +211,7 @@ once `fleet-sync` has fetched it back.
|
|
|
210
211
|
| `DEVFLEET_CONFIG` | A configuration file, instead of the project's `fleet.json` |
|
|
211
212
|
| `DEVFLEET_STATE` | Another state directory. Earlier files are not read for it |
|
|
212
213
|
| `DEVFLEET_BUCKET` | Another `bucket.json` |
|
|
214
|
+
| `DEVFLEET_MANAGER_HOLDER` | How the manager lease names this machine |
|
|
213
215
|
|
|
214
216
|
How a command reaches the bucket:
|
|
215
217
|
|
|
@@ -254,7 +256,37 @@ mise exec litestream -- litestream restore -no-expand-env \
|
|
|
254
256
|
~/.local/share/dev-fleet/state/fleet.db
|
|
255
257
|
```
|
|
256
258
|
|
|
259
|
+
`devfleet fleet-state --restore --timestamp 2026-11-01T00:00:00Z --yes` does
|
|
260
|
+
the same and then uploads that version as the bucket's current one. It is the
|
|
261
|
+
remedy for a `corrupt` replica: the bucket answers, but its latest version
|
|
262
|
+
cannot be read.
|
|
263
|
+
|
|
257
264
|
Litestream is pinned in `mise.toml` and installed the first time it is used.
|
|
258
|
-
The manager is the only machine that runs it.
|
|
259
|
-
|
|
260
|
-
|
|
265
|
+
The manager is the only machine that runs it.
|
|
266
|
+
|
|
267
|
+
## One manager at a time
|
|
268
|
+
|
|
269
|
+
The fleet state carries a **manager lease**: which machine is the manager,
|
|
270
|
+
and until when. Every change takes or renews it, and it reaches the bucket
|
|
271
|
+
with the change. A second machine (a replacement while the old manager still
|
|
272
|
+
runs, or an old manager that wakes up) reads it from the bucket before it
|
|
273
|
+
writes anything and stops with `FLEET_MANAGER_LEASE_HELD`. `fleet-state
|
|
274
|
+
--init` still restores the state on such a machine; it only writes nothing.
|
|
275
|
+
|
|
276
|
+
The lease runs for seven days after the last change. After that, any machine
|
|
277
|
+
may take it. Before that, `--take-over` (on `fleet-sync`, `fleet-state --init`
|
|
278
|
+
or `fleet-state --import`) makes this machine the manager; use it only once
|
|
279
|
+
the other one is gone ([replacing the manager](manager-recovery.md)).
|
|
280
|
+
|
|
281
|
+
A machine is named in the lease by a digest of its machine ID
|
|
282
|
+
(`/etc/machine-id` on Linux, the hardware UUID on macOS), never by its host
|
|
283
|
+
name, which a replacement shares. `DEVFLEET_MANAGER_HOLDER` names it instead.
|
|
284
|
+
|
|
285
|
+
## Schema version
|
|
286
|
+
|
|
287
|
+
The database records its schema version in `PRAGMA user_version`. The schema
|
|
288
|
+
is a series of numbered migrations (`src/state/migrations/0001-….sql`, …),
|
|
289
|
+
and a command brings an older database forward before it writes. A database a
|
|
290
|
+
newer dev-fleet wrote is refused with `FLEET_STATE_NEWER_THAN_CLI`, read or
|
|
291
|
+
written, and nothing in it changes: install the dev-fleet version the fleet
|
|
292
|
+
runs.
|
package/docs/topology.md
CHANGED
|
@@ -41,7 +41,8 @@ your project's `fleet.json` (version 3, checked by [the schema](../schema/fleet.
|
|
|
41
41
|
manager is asleep, closed or offline, workers have no CLI proxy and no
|
|
42
42
|
dashboard; nothing fails over, because a singleton never runs twice.
|
|
43
43
|
- Its state (proxy OAuth/runtime state, the hub database) is backed up off the
|
|
44
|
-
machine as each service documents, so a replacement manager restores it
|
|
44
|
+
machine as each service documents, so a replacement manager restores it
|
|
45
|
+
([replacing the manager](manager-recovery.md)).
|
|
45
46
|
|
|
46
47
|
## Workers
|
|
47
48
|
|
|
@@ -63,7 +64,5 @@ your project's `fleet.json` (version 3, checked by [the schema](../schema/fleet.
|
|
|
63
64
|
|
|
64
65
|
- **Add or replace a worker:** add it to `workers`, enroll it with
|
|
65
66
|
`fleet-sync`, done. Remove it by deleting the entry.
|
|
66
|
-
- **Replace the manager:**
|
|
67
|
-
|
|
68
|
-
[CLI proxy](../cliproxy/docs/migration.md),
|
|
69
|
-
[Beszel hub](../ansible/monitoring/README.md).
|
|
67
|
+
- **Replace the manager:** follow
|
|
68
|
+
[replacing the manager](manager-recovery.md).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crvouga/dev-fleet",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Keeps a set of development machines in sync: one manager and many workers",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": "github:crvouga/dev-fleet",
|
|
@@ -72,5 +72,5 @@
|
|
|
72
72
|
"engines": {
|
|
73
73
|
"node": ">=22"
|
|
74
74
|
},
|
|
75
|
-
"gitHead": "
|
|
75
|
+
"gitHead": "77efce7ad99a3d521169a5e773ad6b97ca21bc75"
|
|
76
76
|
}
|
package/schema/check-ids.json
CHANGED
|
@@ -218,6 +218,10 @@
|
|
|
218
218
|
"id": "login:*",
|
|
219
219
|
"meaning": "A native per-machine login is in place."
|
|
220
220
|
},
|
|
221
|
+
{
|
|
222
|
+
"id": "manager:boot-recovery",
|
|
223
|
+
"meaning": "The manager comes back by itself after a power cut or reboot."
|
|
224
|
+
},
|
|
221
225
|
{
|
|
222
226
|
"id": "manager:docker",
|
|
223
227
|
"meaning": "The manager runs the pinned Docker Desktop."
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/** Who repairs a check: a repair recipe (`repair`), or the reason only a
|
|
2
|
+
* person can (`manual`). Every check ID dev-fleet reports matches one entry,
|
|
3
|
+
* by its exact ID or by a prefix ending in `*` (`beszel:*`). The most
|
|
4
|
+
* specific match wins. `login:*` is the sign-in recipe named after the rest
|
|
5
|
+
* of an `auth:` check's ID.
|
|
6
|
+
*/
|
|
7
|
+
export interface CheckOwner {
|
|
8
|
+
pattern: string;
|
|
9
|
+
repair?: string;
|
|
10
|
+
manual?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const IDENTITY =
|
|
14
|
+
'It needs a person: a sign-in, an approval or a choice only the operator can make.';
|
|
15
|
+
const EVIDENCE =
|
|
16
|
+
'It reports how a command or a run went, not the state of a machine: repeat the command once its remedy is followed.';
|
|
17
|
+
const DECLARATION =
|
|
18
|
+
'The fleet configuration (fleet.json) or the roster is wrong: only the operator edits them.';
|
|
19
|
+
const HARDWARE =
|
|
20
|
+
'It is a property of the hardware, the firmware or the operating system release, which no command changes.';
|
|
21
|
+
|
|
22
|
+
export const CHECK_OWNERS: CheckOwner[] = [
|
|
23
|
+
// Toolchain and workstation: automatic.
|
|
24
|
+
{ pattern: 'tool:*', repair: 'workspace' },
|
|
25
|
+
{ pattern: 'tool:docker-*', repair: 'docker' },
|
|
26
|
+
{ pattern: 'tool:github-auth*', repair: 'login:github' },
|
|
27
|
+
{ pattern: 'dependencies', repair: 'workspace' },
|
|
28
|
+
{ pattern: 'auth:toolchain', repair: 'workspace' },
|
|
29
|
+
{ pattern: 'workstation:*', repair: 'workstation' },
|
|
30
|
+
{ pattern: 'auth:*', repair: 'login:*' },
|
|
31
|
+
{ pattern: 'auth:vscode', manual: IDENTITY },
|
|
32
|
+
{ pattern: 'auth:workstation', manual: IDENTITY },
|
|
33
|
+
{ pattern: 't3:settings', repair: 't3' },
|
|
34
|
+
{ pattern: 't3:api', repair: 't3' },
|
|
35
|
+
// The checks that fail when a machine breaks.
|
|
36
|
+
{ pattern: 'worker:service-recovery', repair: 'recovery' },
|
|
37
|
+
{ pattern: 'worker:capacity', repair: 'disk' },
|
|
38
|
+
{ pattern: 'worker:capacity:*', repair: 'disk' },
|
|
39
|
+
{ pattern: 'beszel:agent', repair: 'monitoring' },
|
|
40
|
+
{ pattern: 'beszel:service', repair: 'monitoring' },
|
|
41
|
+
{ pattern: 'beszel:installation', repair: 'monitoring' },
|
|
42
|
+
{ pattern: 'beszel:protocol', repair: 'monitoring' },
|
|
43
|
+
{ pattern: 'tailscale:configuration', repair: 'network' },
|
|
44
|
+
{ pattern: 'tailscale:online', repair: 'network' },
|
|
45
|
+
{ pattern: 'tailscale:reachability', repair: 'network' },
|
|
46
|
+
{ pattern: 'state:replica', repair: 'state' },
|
|
47
|
+
{ pattern: 'state:*', repair: 'state' },
|
|
48
|
+
// What only a person can do.
|
|
49
|
+
{
|
|
50
|
+
pattern: 'beszel:*',
|
|
51
|
+
manual:
|
|
52
|
+
'The monitoring hub and its accounts are converged by fleet-sync on the manager; follow the remedy, then run fleet-sync.',
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
pattern: 'tailscale:*',
|
|
56
|
+
manual:
|
|
57
|
+
'Signing a machine in to the tailnet and approving it is a person’s; the daemon itself is repaired by the network recipe.',
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
pattern: 'state:bucket',
|
|
61
|
+
manual:
|
|
62
|
+
'The bucket and its credentials are the operator’s: name them in fleet.json and pass the credentials.',
|
|
63
|
+
},
|
|
64
|
+
{ pattern: 'state:definition', manual: DECLARATION },
|
|
65
|
+
{
|
|
66
|
+
pattern: 'worker:*',
|
|
67
|
+
manual:
|
|
68
|
+
'Re-converge the worker with fleet-sync: it applies the whole worker policy at the approved revision.',
|
|
69
|
+
},
|
|
70
|
+
{ pattern: 'worker:power-loss-recovery', manual: HARDWARE },
|
|
71
|
+
{ pattern: 'worker:github-principal', manual: IDENTITY },
|
|
72
|
+
{ pattern: 'worker:vault-renewal', manual: IDENTITY },
|
|
73
|
+
{
|
|
74
|
+
pattern: 'manager:*',
|
|
75
|
+
manual:
|
|
76
|
+
'The manager converges itself in fleet-sync; a setting that needs an administrator is applied with ansible-playbook -K ansible/macos/manager.yml.',
|
|
77
|
+
},
|
|
78
|
+
{ pattern: 'manager:identity', manual: DECLARATION },
|
|
79
|
+
{
|
|
80
|
+
pattern: 'manager:docker',
|
|
81
|
+
manual:
|
|
82
|
+
'Docker Desktop is installed and updated by a person; no playbook installs it.',
|
|
83
|
+
},
|
|
84
|
+
{ pattern: 'macos:*', manual: HARDWARE },
|
|
85
|
+
{ pattern: 'ubuntu:release', manual: HARDWARE },
|
|
86
|
+
{
|
|
87
|
+
pattern: 'ubuntu:*',
|
|
88
|
+
manual:
|
|
89
|
+
'Re-converge the worker with fleet-sync: it applies the Ubuntu baseline.',
|
|
90
|
+
},
|
|
91
|
+
{ pattern: 'platform:*', manual: HARDWARE },
|
|
92
|
+
{
|
|
93
|
+
pattern: 'ssh:*',
|
|
94
|
+
manual:
|
|
95
|
+
'SSH trust (host keys, keys in the agent, the alias) is set up by a person once per machine; dev-fleet never accepts a host key for you.',
|
|
96
|
+
},
|
|
97
|
+
{ pattern: 'login:*', manual: IDENTITY },
|
|
98
|
+
{ pattern: 'provider:*', manual: IDENTITY },
|
|
99
|
+
{
|
|
100
|
+
pattern: 'agent:*',
|
|
101
|
+
manual:
|
|
102
|
+
'Re-converge the machine with fleet-sync; an agent that needs a sign-in is a person’s.',
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
pattern: 't3:*',
|
|
106
|
+
manual:
|
|
107
|
+
'T3 pairing and its private route are approved by a person; fleet-sync applies the rest.',
|
|
108
|
+
},
|
|
109
|
+
{ pattern: 'vscode:*', manual: IDENTITY },
|
|
110
|
+
{
|
|
111
|
+
pattern: 'git:*',
|
|
112
|
+
manual:
|
|
113
|
+
'Uncommitted or unpushed work belongs to a person; dev-fleet never resets a checkout.',
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
pattern: 'git:configuration',
|
|
117
|
+
manual:
|
|
118
|
+
'fleet-sync writes the Git identity and credential helper with ansible/github/git.yml; run it from the manager.',
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
pattern: 'github:*',
|
|
122
|
+
manual:
|
|
123
|
+
'fleet-sync signs every node in to GitHub from the stored github-token (a worker retries every ten minutes); a token that is missing, expired or short of a scope or organisation needs a person to issue a new one.',
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
pattern: 'github:git-credential',
|
|
127
|
+
manual:
|
|
128
|
+
'fleet-sync writes the GitHub credential helper with ansible/github/git.yml when the git block is declared; run it from the manager.',
|
|
129
|
+
},
|
|
130
|
+
{ pattern: 'github:rate-limit', manual: EVIDENCE },
|
|
131
|
+
{ pattern: 'github:workflow', manual: EVIDENCE },
|
|
132
|
+
{ pattern: 'fleet:*', manual: DECLARATION },
|
|
133
|
+
{ pattern: 'enrollment:*', manual: DECLARATION },
|
|
134
|
+
{ pattern: 'notify:*', manual: DECLARATION },
|
|
135
|
+
{
|
|
136
|
+
pattern: 'proxy:*',
|
|
137
|
+
manual:
|
|
138
|
+
'The CLI proxy is installed with devfleet cliproxy install and its accounts are signed in by a person.',
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
pattern: 'accounts:roster-current',
|
|
142
|
+
manual:
|
|
143
|
+
'The roster reaches every node with devfleet fleet-sync --only accounts, run from the manager.',
|
|
144
|
+
},
|
|
145
|
+
{ pattern: 'accounts:native-login', manual: IDENTITY },
|
|
146
|
+
{ pattern: 'configuration:*', manual: EVIDENCE },
|
|
147
|
+
{ pattern: 'source:*', manual: EVIDENCE },
|
|
148
|
+
{ pattern: 'bootstrap:*', manual: EVIDENCE },
|
|
149
|
+
{ pattern: 'rollout:*', manual: EVIDENCE },
|
|
150
|
+
{ pattern: 'repair:*', manual: EVIDENCE },
|
|
151
|
+
];
|
|
152
|
+
|
|
153
|
+
const matches = (pattern: string, id: string): boolean =>
|
|
154
|
+
pattern.endsWith('*') ? id.startsWith(pattern.slice(0, -1)) : id === pattern;
|
|
155
|
+
|
|
156
|
+
/** The entry that owns a check, or null for an ID nobody registered. */
|
|
157
|
+
export function checkOwner(id: string): CheckOwner | null {
|
|
158
|
+
return (
|
|
159
|
+
CHECK_OWNERS.filter((owner) => matches(owner.pattern, id)).sort(
|
|
160
|
+
(a, b) => b.pattern.length - a.pattern.length
|
|
161
|
+
)[0] ?? null
|
|
162
|
+
);
|
|
163
|
+
}
|