@crvouga/dev-fleet 0.8.0 → 0.9.1
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 +65 -9
- package/agents/audit.md +18 -18
- package/ansible/agents/agents.yml +134 -0
- package/ansible/agents/templates/antigravity.settings.json.j2 +3 -0
- package/ansible/agents/templates/claude.settings.json.j2 +3 -0
- package/ansible/agents/templates/codex.config.toml.j2 +11 -0
- package/ansible/agents/templates/gemini.settings.json.j2 +14 -0
- package/ansible/agents/templates/grok.config.toml.j2 +19 -0
- package/ansible/agents/templates/opencode.json.j2 +24 -0
- package/ansible/agents/templates/receipt.json.j2 +10 -0
- package/ansible/ansible.cfg +14 -0
- package/ansible/github/git.yml +82 -0
- package/ansible/macos/manager-privilege.yml +55 -9
- package/ansible/macos/manager.yml +154 -7
- package/ansible/macos/policy.json +13 -0
- package/ansible/macos/t3-policy.yml +5 -3
- package/ansible/macos/vault-renew.yml +85 -0
- package/ansible/monitoring/agent-darwin.yml +3 -0
- package/ansible/monitoring/agent.yml +2 -0
- package/ansible/monitoring/hub-darwin.yml +3 -0
- package/ansible/monitoring/hub-route-darwin.yml +1 -0
- package/ansible/net/remote-probe.sh +4 -1
- package/ansible/platforms.json +2 -2
- package/ansible/pyproject.toml +5 -0
- package/ansible/ubuntu/baseline.yml +39 -13
- package/ansible/ubuntu/disk-gc.py +6 -2
- package/ansible/ubuntu/firewall.nft.j2 +50 -0
- package/ansible/ubuntu/policy.json +125 -6
- package/ansible/ubuntu/reboot.yml +89 -0
- package/ansible/ubuntu/recovery.yml +13 -3
- package/ansible/ubuntu/t3/policy.json +3 -0
- package/ansible/ubuntu/t3-desktop.yml +23 -2
- package/ansible/ubuntu/t3-headless.yml +128 -0
- package/ansible/ubuntu/t3-health.sh +24 -10
- package/ansible/ubuntu/t3-idle.py +37 -2
- package/ansible/ubuntu/t3-policy.yml +4 -1
- package/ansible/ubuntu/t3-route.yml +1 -0
- package/ansible/ubuntu/tailscale.yml +54 -8
- package/ansible/ubuntu/tasks/browser.yml +3 -0
- package/ansible/ubuntu/tasks/containers-off.yml +43 -0
- package/ansible/ubuntu/tasks/docker.yml +53 -6
- package/ansible/ubuntu/tasks/dpkg.yml +4 -0
- package/ansible/ubuntu/tasks/features.yml +22 -0
- package/ansible/ubuntu/tasks/firewall.yml +172 -0
- package/ansible/ubuntu/tasks/install.yml +2 -1
- package/ansible/ubuntu/tasks/packages.yml +4 -2
- package/ansible/ubuntu/tasks/release.yml +6 -0
- package/ansible/ubuntu/tasks/resources.yml +6 -4
- package/ansible/ubuntu/tasks/services.yml +2 -2
- package/ansible/ubuntu/tasks/sizing.yml +49 -0
- package/ansible/ubuntu/tasks/snapshot.yml +14 -3
- package/ansible/ubuntu/tasks/ssh.yml +61 -4
- package/ansible/ubuntu/tasks/t3-restart.yml +3 -1
- package/ansible/ubuntu/tasks/user.yml +1 -0
- package/ansible/ubuntu/worker-account.yml +117 -0
- package/ansible/uv.lock +265 -0
- package/bin/devfleet.mjs +42 -4
- package/bootstrap/fleet-remote-bootstrap.sh +91 -25
- package/bootstrap/global-config.ts +29 -3
- package/bootstrap/package-smoke.ts +127 -0
- package/bootstrap/pins.ts +189 -0
- package/bootstrap/release-notes.ts +88 -0
- package/bootstrap/release-version.ts +41 -7
- package/bootstrap/worker-features.ts +114 -0
- package/bootstrap/worker-seed.yaml +7 -3
- package/bootstrap/worker-tools.ts +39 -9
- package/cliproxy/README.md +1 -1
- package/cliproxy/docs/health-workflow.example.yml +20 -143
- package/docs/authentication.md +19 -11
- package/docs/consumer-setup.md +42 -27
- package/docs/convergence.md +67 -0
- package/docs/declared.md +61 -34
- package/docs/machine-setup.md +73 -7
- package/docs/manager-setup.md +10 -3
- package/docs/operations.md +64 -9
- package/docs/releasing.md +49 -1
- package/docs/runbooks.md +98 -7
- package/docs/state.md +23 -11
- package/docs/support-matrix.md +40 -4
- package/docs/topology.md +67 -6
- package/docs/ubuntu-sync.md +78 -28
- package/docs/worker-contract.md +122 -19
- package/docs/worker-requirements.md +107 -0
- package/examples/fleet.json +1 -0
- package/examples/workers-from-infrastructure/main.tf +132 -0
- package/examples/workers-from-infrastructure/worker-records.json +14 -0
- package/llms.txt +3 -1
- package/mise.lock +29 -0
- package/mise.toml +5 -3
- package/package.json +2 -2
- package/schema/check-ids.json +1967 -170
- package/schema/check-ids.schema.json +34 -0
- package/schema/fleet.schema.json +327 -8
- package/schema/output/check.schema.json +1 -1
- package/schema/output/fleet-state-definition.schema.json +14 -0
- package/schema/output/fleet-sync-schedule.schema.json +26 -0
- package/schema/output/fleet-sync.schema.json +18 -0
- package/schema/output/report.schema.json +3 -2
- package/schema/worker-record.schema.json +76 -0
- package/src/agents/converge.ts +140 -24
- package/src/agents/files.ts +2 -32
- package/src/agents/native-verify.ts +34 -3
- package/src/agents/secrets.ts +39 -4
- package/src/check-owners.ts +61 -2
- package/src/check-registry.ts +630 -0
- package/src/cli/agent-guide.ts +3 -3
- package/src/cli/cli.ts +4 -0
- package/src/cli/fleet-manifest.ts +9 -3
- package/src/cli/fleet-node-lifecycle.ts +313 -64
- package/src/cli/fleet-node-park.ts +238 -0
- package/src/cli/fleet-node-seed.ts +51 -35
- package/src/cli/fleet-state.ts +2 -1
- package/src/cli/fleet-sync-checkout.ts +7 -0
- package/src/cli/fleet-sync-enrollment.ts +193 -32
- package/src/cli/fleet-sync-forward.ts +4 -0
- package/src/cli/fleet-sync-manager.ts +103 -33
- package/src/cli/fleet-sync-output.ts +31 -11
- package/src/cli/fleet-sync-plan.ts +65 -9
- package/src/cli/fleet-sync-records.ts +113 -0
- package/src/cli/fleet-sync-remote.ts +151 -9
- package/src/cli/fleet-sync-schedule.ts +14 -4
- package/src/cli/fleet-sync-state.ts +28 -16
- package/src/cli/fleet-sync-vault.ts +77 -0
- package/src/cli/fleet-sync-worker.ts +214 -0
- package/src/cli/fleet-sync.ts +150 -19
- package/src/cli/fleet-validate.ts +59 -21
- package/src/cli/fleet-watch.ts +7 -1
- package/src/cli/fleet.ts +87 -5
- package/src/cli/managed-health.ts +25 -1
- package/src/cli/register-fleet.ts +115 -4
- package/src/cli/registry.ts +14 -0
- package/src/cli/tailnet.ts +43 -10
- package/src/cli/ui-data.ts +3 -1
- package/src/cli/worker-usage-report.ts +63 -0
- package/src/clock.ts +42 -0
- package/src/definition.ts +332 -9
- package/src/diagnostics.ts +5 -2
- package/src/exposure.ts +102 -0
- package/src/fleet-config.ts +3 -1
- package/src/git-identity.ts +76 -3
- package/src/ide/ide.ts +5 -0
- package/src/ide/t3.ts +12 -2
- package/src/input-hashes.ts +6 -0
- package/src/machine/machine-class.ts +59 -0
- package/src/machine/platform.ts +90 -11
- package/src/machine/state.ts +37 -15
- package/src/machine/support-matrix.ts +11 -9
- package/src/machine-health.ts +28 -5
- package/src/manager-health.ts +148 -2
- package/src/manager-sync.ts +65 -4
- package/src/monitoring/beszel.ts +8 -8
- package/src/monitoring/enroll.ts +8 -1
- package/src/monitoring/hub-access.ts +10 -4
- package/src/monitoring/systems.ts +138 -0
- package/src/network-definition.ts +13 -4
- package/src/node-converge-linux.ts +136 -27
- package/src/node-converge.ts +13 -3
- package/src/platform/ansible-receipt.ts +17 -2
- package/src/platform/ansible.ts +40 -4
- package/src/platform/manager-host.ts +25 -8
- package/src/platform/resource-verify.ts +18 -7
- package/src/platform/t3-policy.ts +24 -10
- package/src/platform/t3-service.ts +109 -34
- package/src/platform/ubuntu-release.ts +24 -0
- package/src/platform/ubuntu-verify.ts +31 -3
- package/src/platform/ubuntu-worker.ts +58 -0
- package/src/platform/worker-platform.ts +72 -0
- package/src/protocol.ts +1 -1
- package/src/public-reachability.ts +186 -0
- package/src/remote-bootstrap.ts +50 -12
- package/src/remote-operation.ts +18 -4
- package/src/repair-plan.ts +23 -12
- package/src/report.ts +2 -1
- package/src/schedule-check.ts +21 -2
- package/src/secrets.ts +4 -0
- package/src/service-auth.ts +55 -48
- package/src/ssh-connection.ts +141 -0
- package/src/ssh.ts +63 -24
- package/src/state/store.ts +4 -2
- package/src/state/sync.ts +35 -6
- package/src/state/workers.ts +140 -0
- package/src/t3/pairing.ts +59 -0
- package/src/t3/peer-link.ts +333 -0
- package/src/t3/version.ts +1 -1
- package/src/tailnet-cleanup.ts +185 -0
- package/src/tailnet-control.ts +346 -0
- package/src/tailscale.ts +1 -1
- package/src/toolchain-checks.ts +11 -1
- package/src/types.ts +4 -2
- package/src/worker-account.ts +63 -0
- package/src/worker-activity.ts +303 -0
- package/src/worker-capability-checks.ts +55 -16
- package/src/worker-capacity.ts +31 -0
- package/src/worker-exposure.ts +388 -0
- package/src/worker-features.ts +201 -0
- package/src/worker-host-checks.ts +71 -20
- package/src/worker-manifest.ts +27 -4
- package/src/worker-parking.ts +70 -0
- package/src/worker-policy.ts +17 -1
- package/src/worker-readiness.ts +15 -3
- package/src/worker-reboot.ts +129 -0
- package/src/worker-records.ts +305 -0
- package/src/worker-seed.ts +16 -11
- package/src/worker-sizing.ts +76 -0
- package/src/cli/schedule-logs.ts +0 -36
package/README.md
CHANGED
|
@@ -41,8 +41,10 @@ dev-fleet is opinionated. These are part of it, not options:
|
|
|
41
41
|
|
|
42
42
|
- The **manager** is a macOS machine. You run `fleet-sync` on it, and it hosts
|
|
43
43
|
the shared services (the CLI proxy and the monitoring hub) in Docker Desktop.
|
|
44
|
-
- **Workers** are Ubuntu 26.04 machines reached over SSH. They are stateless:
|
|
45
|
-
everything on them is rebuilt from Git and your secrets.
|
|
44
|
+
- **Workers** are Ubuntu 26.04 or 24.04 machines reached over SSH. They are stateless:
|
|
45
|
+
everything on them is rebuilt from Git and your secrets.
|
|
46
|
+
[What a worker must offer](docs/worker-requirements.md) is one page: image,
|
|
47
|
+
architecture, size, access, first-boot data and network. The
|
|
46
48
|
[support matrix](docs/support-matrix.md) lists every platform and what
|
|
47
49
|
happens to it.
|
|
48
50
|
- The fleet state is kept in an **S3-compatible bucket** you own.
|
|
@@ -128,10 +130,26 @@ npm install --save-dev --save-exact @crvouga/dev-fleet@latest
|
|
|
128
130
|
npx devfleet fleet-sync # the fleet moves to that release
|
|
129
131
|
```
|
|
130
132
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
133
|
+
One worker, the canary, proves a new revision before the others receive it:
|
|
134
|
+
the first reachable worker, or the one `canary` in `fleet.json` (or
|
|
135
|
+
`--canary`) names when it is reachable. An offline worker never holds the
|
|
136
|
+
others back.
|
|
137
|
+
|
|
138
|
+
A sync never ends a running agent turn. A worker's T3 that must restart for
|
|
139
|
+
a new release or provider policy restarts only once it runs no agent turn;
|
|
140
|
+
until then the restart is pending and the worker's recovery timer applies it
|
|
141
|
+
at the first idle check. `fleet-sync --restart-busy` restarts it at once and
|
|
142
|
+
ends those turns, and `fleet-sync --dry-run` (with or without the flag) says
|
|
143
|
+
which of the two a sync would do.
|
|
144
|
+
|
|
145
|
+
To go back, run `npx devfleet fleet-sync --rollback` on the manager: it
|
|
146
|
+
converges every machine to the revision applied before the approved one, which
|
|
147
|
+
the fleet state records: after an upgrade whose canary failed, that is the
|
|
148
|
+
revision the fleet still runs. A rollback covers what a revision declares: the
|
|
149
|
+
checkout, the pinned tools and T3 Code. System packages roll forward only
|
|
150
|
+
(apt never downgrades them), so a release that moved one keeps it. Installing
|
|
151
|
+
an older version and running `fleet-sync` does the same for any earlier
|
|
152
|
+
release.
|
|
135
153
|
|
|
136
154
|
## Your project's own tools
|
|
137
155
|
|
|
@@ -139,8 +157,10 @@ On a fleet machine the global mise configuration (`~/.config/mise/config.toml`
|
|
|
139
157
|
and its `mise.lock`) belongs to dev-fleet: `fleet-sync` publishes it with the
|
|
140
158
|
fleet's pinned tools and the agent worktree roots mise must trust. A project
|
|
141
159
|
pins its own tools in its own `mise.toml` and installs them with
|
|
142
|
-
`mise install --locked` in its checkout.
|
|
143
|
-
|
|
160
|
+
`mise install --locked` in its checkout. On a fleet machine mise installs a
|
|
161
|
+
project's tools only from the project's `mise.lock` (run `mise lock` after
|
|
162
|
+
changing a pin); a tool the lock does not name is refused. A project never
|
|
163
|
+
publishes a global configuration. If anything replaces dev-fleet's, `fleet-status` fails the check
|
|
144
164
|
`tool:global-mise-config` until the next `fleet-sync`.
|
|
145
165
|
|
|
146
166
|
## Using it from code
|
|
@@ -219,6 +239,12 @@ propagates them without a full sync.
|
|
|
219
239
|
}
|
|
220
240
|
```
|
|
221
241
|
|
|
242
|
+
A worker may add `"features": { "preset": "editing" }` for a rented
|
|
243
|
+
machine that only edits code: no Docker, and T3 as a headless server with
|
|
244
|
+
no desktop. `containers` and `session` set each one; fleet-wide defaults go
|
|
245
|
+
under `worker.features`. The [worker contract](docs/worker-contract.md)
|
|
246
|
+
says what each feature installs and the minimum size of a worker.
|
|
247
|
+
|
|
222
248
|
3. Run `fleet-sync`. A machine that has only an SSH server and an account
|
|
223
249
|
with sudo is enough: every pull request proves that against a bare
|
|
224
250
|
Ubuntu machine, over SSH
|
|
@@ -243,7 +269,10 @@ Remove a machine with `npx devfleet fleet-node-retire --id ID --apply --yes`
|
|
|
243
269
|
and leaves a tombstone in the fleet state), then delete its entry and run
|
|
244
270
|
`fleet-sync`. To put a fresh machine in place of a worker under the same id,
|
|
245
271
|
run `npx devfleet fleet-node-replace --id ID --host-key "TYPE BASE64" --apply
|
|
246
|
-
--yes` and then `fleet-sync --node ID
|
|
272
|
+
--yes` and then `fleet-sync --node ID`; the manager's T3 pairs with the new
|
|
273
|
+
machine's T3 by itself, with no code to copy. A run scoped with `--node` records only the
|
|
274
|
+
revision of the workers it covered; the fleet's applied revision moves with a
|
|
275
|
+
full `fleet-sync`.
|
|
247
276
|
|
|
248
277
|
## Configuration
|
|
249
278
|
|
|
@@ -399,6 +428,32 @@ Every pull request runs these checks ([`ci.yml`](.github/workflows/ci.yml)):
|
|
|
399
428
|
| Fleet state | The pinned Litestream replicates the fleet state to a directory and to an S3 API, and a second manager restores it |
|
|
400
429
|
| Fresh worker over SSH | A new Ubuntu machine with only an SSH server and sudo is bootstrapped by the production code over real SSH, on amd64 and on arm64 |
|
|
401
430
|
| Workflows | The workflow files pass `actionlint` |
|
|
431
|
+
| Playbooks | Every playbook passes `ansible-lint` (production profile) and `--syntax-check` |
|
|
432
|
+
| Manager on macOS | Every feature on macOS, launchd jobs linted by macOS, and the manager playbook converged, then re-run and checked with no change |
|
|
433
|
+
|
|
434
|
+
The ruleset of `main` requires one check, **Required checks**, which passes
|
|
435
|
+
only when every job above passed: a job that is added, renamed or split is
|
|
436
|
+
required from the pull request that does it. The two fresh worker features
|
|
437
|
+
run side by side, each in a job of its own for every release and
|
|
438
|
+
architecture.
|
|
439
|
+
|
|
440
|
+
A pull request needs nobody attending
|
|
441
|
+
([`pull-requests.feature`](features/rules/pull-requests.feature)):
|
|
442
|
+
|
|
443
|
+
- A pull request from a branch of this repository is squash-merged as soon as
|
|
444
|
+
**Required checks** passes ([`auto-merge.yml`](.github/workflows/auto-merge.yml)).
|
|
445
|
+
A draft is left alone until it is marked ready; a fork's is left to a person.
|
|
446
|
+
- The merge is checked again on `main` and then released. A merge made with
|
|
447
|
+
the workflow's own token starts no workflow, so `auto-merge.yml` starts CI
|
|
448
|
+
on `main` itself when the newest commit there has no run.
|
|
449
|
+
- A workflow that fails on `main` (CI, the release, the weekly VM and tailnet
|
|
450
|
+
tests, the pins) has one open issue labelled
|
|
451
|
+
[`ci-failure`](https://github.com/crvouga/dev-fleet/issues?q=is%3Aissue+is%3Aopen+label%3Aci-failure)
|
|
452
|
+
for as long as it fails ([`failures.yml`](.github/workflows/failures.yml)):
|
|
453
|
+
the run, the commit, the jobs and steps that failed. Each failing run
|
|
454
|
+
rewrites it and the first passing run closes it, so those issues are what is
|
|
455
|
+
broken on `main` now. Start there; fix with a pull request that says
|
|
456
|
+
`Fixes #<issue>`.
|
|
402
457
|
|
|
403
458
|
Some scenarios need something a laptop may not have, and are skipped there:
|
|
404
459
|
|
|
@@ -406,6 +461,7 @@ Some scenarios need something a laptop may not have, and are skipped there:
|
|
|
406
461
|
DEVFLEET_TEST_FRESH_BOX=1 bun test features/fresh-box # Docker; tests the HEAD commit
|
|
407
462
|
mise install litestream
|
|
408
463
|
DEVFLEET_TEST_S3=http://127.0.0.1:5000 bun test features/state
|
|
464
|
+
bun ansible/ubuntu/tests/run-vm.ts baseline # Linux amd64 with KVM; the Ubuntu VM workflow
|
|
409
465
|
```
|
|
410
466
|
|
|
411
467
|
## License
|
package/agents/audit.md
CHANGED
|
@@ -7,24 +7,24 @@ nodes were inspected or authenticated. The execution host exposes Codex, Claude
|
|
|
7
7
|
and T3 on PATH; the mise catalog additionally includes Gemini, Copilot and OpenCode.
|
|
8
8
|
Cursor and Antigravity are desktop prerequisites, not mise-managed binaries.
|
|
9
9
|
|
|
10
|
-
| Source/owner
|
|
11
|
-
|
|
|
12
|
-
| `mise.toml`, `mise.lock`, `.mise/locks/`; bootstrap
|
|
13
|
-
| `agents/registry.json`; dev-fleet
|
|
14
|
-
| `
|
|
15
|
-
| same
|
|
16
|
-
| same
|
|
17
|
-
| same
|
|
18
|
-
| `
|
|
19
|
-
| Existing `opencode/provider-secrets.ts`, OpenRouter package; dev-fleet
|
|
20
|
-
| Existing OpenCode plugins/bin and cli/lib/links.ts; dev-fleet
|
|
21
|
-
| `agents/codex-proxy.ts`; dev-fleet
|
|
22
|
-
| `agents/t3.json`; personal T3 reconciler
|
|
23
|
-
| `../fleet/definition.json`, cli/lib/net; dev-fleet
|
|
24
|
-
| `.agents/commands/*.md`; agents:sync
|
|
25
|
-
| Root AGENTS.md, CLAUDE.md, GEMINI.md, .github/copilot-instructions.md
|
|
26
|
-
| Existing cliproxy clients/bootstrap.py and launcher.py
|
|
27
|
-
| Fleet configure
|
|
10
|
+
| Source/owner | Installed destination | Scope |
|
|
11
|
+
| ---------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
|
|
12
|
+
| `mise.toml`, `mise.lock`, `.mise/locks/`; bootstrap | Global mise config, shims and installed tools | Versions; no authentication |
|
|
13
|
+
| `agents/registry.json`; dev-fleet | Dedicated profile outputs described below | Endpoint, models, secret references, node model overrides |
|
|
14
|
+
| `ansible/agents/templates/` (specified by `src/agents/render.ts`); `ansible/agents/agents.yml` | `ROOT/NODE/codex/{direct,proxy}/config.toml` | Separate CODEX_HOME, Responses HTTP/SSE provider declaration |
|
|
15
|
+
| same | `ROOT/NODE/claude/{direct,proxy}/settings.json` | Passed using native --settings; process environment routes proxy |
|
|
16
|
+
| same | `ROOT/NODE/opencode/{direct,proxy}/opencode.json` | OPENCODE_CONFIG, environment key placeholder |
|
|
17
|
+
| same | `ROOT/NODE/gemini/direct/.gemini/settings.json` | GEMINI_CLI_HOME is the profile root; independent native login |
|
|
18
|
+
| `src/agents/converge.ts` decides, `ansible/agents/agents.yml` writes | Each profile's receipt.json; ROOT/backups | Nonsecret fingerprints/revision; private migration backups |
|
|
19
|
+
| Existing `opencode/provider-secrets.ts`, OpenRouter package; dev-fleet | Platform OpenCode directory/opencode.json | Vault provider catalog, OpenRouter auto policy; existing sync embeds private keys with mode 0600 |
|
|
20
|
+
| Existing OpenCode plugins/bin and cli/lib/links.ts; dev-fleet | Platform OpenCode plugins/bin, tui.json, notifier files | Notification plugins, sounds, platform-derived app paths |
|
|
21
|
+
| `agents/codex-proxy.ts`; dev-fleet | T3 binaryPath points directly into reviewed Git checkout | Runtime Vault injection; WS_AGENT_NODE is a declared machine value |
|
|
22
|
+
| `agents/t3.json`; personal T3 reconciler | T3CODE_HOME/userdata/settings.json | Owned provider instances only; plan/apply/import/cleanup and private ownership state |
|
|
23
|
+
| `../fleet/definition.json`, cli/lib/net; dev-fleet | Managed SSH block and VS Code Remote-SSH settings | Existing machine aliases, OS/user/identity public paths and T3 ports; no private keys |
|
|
24
|
+
| `.agents/commands/*.md`; agents:sync | Harness command symlinks | Canonical agent commands; do not edit copies |
|
|
25
|
+
| Root AGENTS.md, CLAUDE.md, GEMINI.md, .github/copilot-instructions.md | Repository instruction discovery | Same canonical repository rules |
|
|
26
|
+
| Existing cliproxy clients/bootstrap.py and launcher.py | ~/.config/cliproxy-fleet; ~/.local/bin/*-fleet | Legacy opt-in migration boundary; native settings preserved |
|
|
27
|
+
| Fleet configure | checkout/node_modules/.fleet-revisions/SHA; .fleet-configuration/*.json | Pinned Git deployment worktrees and per-selection rollout receipts |
|
|
28
28
|
|
|
29
29
|
ROOT is `~/.config/personal/agents` on macOS/Linux and
|
|
30
30
|
`%USERPROFILE%\AppData\Local\personal\agents` on native Windows. Home and platform
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
---
|
|
2
|
+
# One agent CLI's dedicated profile (src/agents/converge.ts): its
|
|
3
|
+
# configuration files, rendered from templates/, and the receipt that records
|
|
4
|
+
# them. Written as the developer: no become. These files already exist on
|
|
5
|
+
# running machines and every byte of them is an interface; the templates
|
|
6
|
+
# render exactly what src/agents/render.ts specifies
|
|
7
|
+
# (features/agents/agent-configuration-templates.feature proves it).
|
|
8
|
+
#
|
|
9
|
+
# TypeScript decides beforehand: it refuses a file someone edited unless the
|
|
10
|
+
# person adopts it, names the files to back up first, and passes the SHA-256
|
|
11
|
+
# of each file as it must be written. A rendered file without that digest
|
|
12
|
+
# never replaces the one in place, the play measures every file again before
|
|
13
|
+
# it writes the receipt, so a receipt never vouches for a file it does not
|
|
14
|
+
# describe, and TypeScript measures the profile once more afterwards. A
|
|
15
|
+
# profile with nothing to change is not played at all. Variables arrive
|
|
16
|
+
# through a private 0600
|
|
17
|
+
# variables file, never argv; none of them is a secret (a profile names the
|
|
18
|
+
# variable that holds the proxy key, never the key).
|
|
19
|
+
- name: Render an agent CLI's dedicated profile
|
|
20
|
+
hosts: localhost
|
|
21
|
+
connection: local
|
|
22
|
+
become: false
|
|
23
|
+
gather_facts: false
|
|
24
|
+
vars:
|
|
25
|
+
# Where each CLI reads its configuration, relative to its profile.
|
|
26
|
+
agent_templates:
|
|
27
|
+
codex:
|
|
28
|
+
directories: []
|
|
29
|
+
files:
|
|
30
|
+
- { dest: config.toml, src: codex.config.toml.j2 }
|
|
31
|
+
claude:
|
|
32
|
+
directories: []
|
|
33
|
+
files:
|
|
34
|
+
- { dest: settings.json, src: claude.settings.json.j2 }
|
|
35
|
+
grok:
|
|
36
|
+
directories: []
|
|
37
|
+
files:
|
|
38
|
+
- { dest: config.toml, src: grok.config.toml.j2 }
|
|
39
|
+
antigravity:
|
|
40
|
+
directories: [.gemini, .gemini/antigravity-cli]
|
|
41
|
+
files:
|
|
42
|
+
- {
|
|
43
|
+
dest: .gemini/antigravity-cli/settings.json,
|
|
44
|
+
src: antigravity.settings.json.j2,
|
|
45
|
+
}
|
|
46
|
+
gemini:
|
|
47
|
+
directories: [.gemini]
|
|
48
|
+
files:
|
|
49
|
+
- { dest: .gemini/settings.json, src: gemini.settings.json.j2 }
|
|
50
|
+
opencode:
|
|
51
|
+
directories: []
|
|
52
|
+
files:
|
|
53
|
+
- { dest: opencode.json, src: opencode.json.j2 }
|
|
54
|
+
agent_profile_directory: '{{ agent_root }}/{{ agent_node }}/{{ agent_tool }}/{{ agent_profile }}'
|
|
55
|
+
agent_spec: '{{ agent_templates[agent_tool] }}'
|
|
56
|
+
tasks:
|
|
57
|
+
- name: Require one known profile of one plain machine name
|
|
58
|
+
ansible.builtin.assert:
|
|
59
|
+
that:
|
|
60
|
+
- agent_root is match('^/')
|
|
61
|
+
- agent_node is match('^[a-z0-9][a-z0-9-]{0,62}$')
|
|
62
|
+
- agent_tool in agent_templates
|
|
63
|
+
- agent_profile in ['direct', 'proxy']
|
|
64
|
+
- agent_model is match('^[a-zA-Z0-9_./:+-]+$')
|
|
65
|
+
- agent_revision is match('^[a-f0-9]{40}$')
|
|
66
|
+
- agent_fingerprint is match('^[a-f0-9]{64}$')
|
|
67
|
+
- agent_profile == 'direct' or agent_proxy_endpoint is match('^https?://')
|
|
68
|
+
- agent_files.keys() | sort == agent_spec.files | map(attribute='dest') | sort
|
|
69
|
+
fail_msg: AGENT_PROFILE_REQUEST_INVALID; dev-fleet passes a validated selection and the digest of every file it renders.
|
|
70
|
+
- name: Create the profile directories
|
|
71
|
+
ansible.builtin.file:
|
|
72
|
+
path: '{{ item }}'
|
|
73
|
+
state: directory
|
|
74
|
+
mode: '0700'
|
|
75
|
+
loop: "{{ [agent_root ~ '/' ~ agent_node, agent_root ~ '/' ~ agent_node ~ '/' ~ agent_tool, agent_profile_directory] + (agent_spec.directories | map('regex_replace', '^', agent_profile_directory ~ '/') | list) }}"
|
|
76
|
+
# Only what TypeScript names: a file someone edited and the person
|
|
77
|
+
# adopted, or a managed file that changes.
|
|
78
|
+
- name: Create the backup directory
|
|
79
|
+
ansible.builtin.file:
|
|
80
|
+
path: '{{ agent_root }}/backups'
|
|
81
|
+
state: directory
|
|
82
|
+
mode: '0700'
|
|
83
|
+
when: agent_backups | length > 0
|
|
84
|
+
- name: Back up a file before it is replaced
|
|
85
|
+
ansible.builtin.copy:
|
|
86
|
+
src: '{{ item.source }}'
|
|
87
|
+
dest: '{{ item.dest }}'
|
|
88
|
+
remote_src: true
|
|
89
|
+
force: false
|
|
90
|
+
mode: '0600'
|
|
91
|
+
loop: '{{ agent_backups }}'
|
|
92
|
+
loop_control:
|
|
93
|
+
label: '{{ item.dest | basename }}'
|
|
94
|
+
# A rendered file replaces the one in place only when it has the digest
|
|
95
|
+
# dev-fleet specified: a template that drifted never reaches a machine.
|
|
96
|
+
- name: Render the configuration files
|
|
97
|
+
ansible.builtin.template:
|
|
98
|
+
src: '{{ item.src }}'
|
|
99
|
+
dest: '{{ agent_profile_directory }}/{{ item.dest }}'
|
|
100
|
+
mode: '0600'
|
|
101
|
+
validate: >-
|
|
102
|
+
{{ ansible_playbook_python | quote }} -c 'import hashlib, sys;
|
|
103
|
+
sys.exit(None if hashlib.sha256(open(sys.argv[1], "rb").read()).hexdigest() == sys.argv[2]
|
|
104
|
+
else "AGENT_PROFILE_RENDER_MISMATCH; the template and src/agents/render.ts disagree.")'
|
|
105
|
+
%s {{ agent_files[item.dest] | quote }}
|
|
106
|
+
loop: '{{ agent_spec.files }}'
|
|
107
|
+
loop_control:
|
|
108
|
+
label: '{{ item.dest }}'
|
|
109
|
+
- name: Measure what was written
|
|
110
|
+
ansible.builtin.stat:
|
|
111
|
+
path: '{{ agent_profile_directory }}/{{ item.dest }}'
|
|
112
|
+
checksum_algorithm: sha256
|
|
113
|
+
loop: '{{ agent_spec.files }}'
|
|
114
|
+
loop_control:
|
|
115
|
+
label: '{{ item.dest }}'
|
|
116
|
+
register: agent_written
|
|
117
|
+
when: not ansible_check_mode
|
|
118
|
+
- name: Require every file to be byte for byte what dev-fleet specifies
|
|
119
|
+
ansible.builtin.assert:
|
|
120
|
+
that:
|
|
121
|
+
- item.stat.checksum == agent_files[item.item.dest]
|
|
122
|
+
fail_msg: AGENT_PROFILE_RENDER_MISMATCH; the template and src/agents/render.ts disagree. No receipt was written.
|
|
123
|
+
quiet: true
|
|
124
|
+
loop: '{{ agent_written.results }}'
|
|
125
|
+
loop_control:
|
|
126
|
+
label: '{{ item.item.dest }}'
|
|
127
|
+
when: not ansible_check_mode
|
|
128
|
+
# A template, not copy's content: before 2.19, Ansible turns a task
|
|
129
|
+
# argument that reads as JSON into data and writes it back reformatted.
|
|
130
|
+
- name: Record the receipt of the profile
|
|
131
|
+
ansible.builtin.template:
|
|
132
|
+
src: receipt.json.j2
|
|
133
|
+
dest: '{{ agent_profile_directory }}/receipt.json'
|
|
134
|
+
mode: '0600'
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
model = {{ agent_model | to_json }}
|
|
2
|
+
{% if agent_profile == 'proxy' %}
|
|
3
|
+
model_provider = "personal_proxy"
|
|
4
|
+
[model_providers.personal_proxy]
|
|
5
|
+
name = "Personal CLI Proxy"
|
|
6
|
+
base_url = {{ (agent_proxy_endpoint ~ '/v1') | to_json }}
|
|
7
|
+
env_key = "CLIPROXY_API_KEY"
|
|
8
|
+
wire_api = "responses"
|
|
9
|
+
requires_openai_auth = false
|
|
10
|
+
supports_websockets = false
|
|
11
|
+
{% endif %}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
[cli]
|
|
2
|
+
auto_update = false
|
|
3
|
+
|
|
4
|
+
[marketplace]
|
|
5
|
+
default_skills_installs_purged = true
|
|
6
|
+
|
|
7
|
+
[models]
|
|
8
|
+
{% if agent_profile == 'proxy' %}
|
|
9
|
+
default = "personal-proxy"
|
|
10
|
+
|
|
11
|
+
[model.personal-proxy]
|
|
12
|
+
model = {{ agent_model | to_json }}
|
|
13
|
+
name = {{ (agent_model ~ ' (Personal CLI Proxy)') | to_json }}
|
|
14
|
+
base_url = {{ (agent_proxy_endpoint ~ '/v1') | to_json }}
|
|
15
|
+
env_key = "CLIPROXY_API_KEY"
|
|
16
|
+
api_backend = "chat_completions"
|
|
17
|
+
{% else %}
|
|
18
|
+
default = {{ agent_model | to_json }}
|
|
19
|
+
{% endif %}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://opencode.ai/config.json",
|
|
3
|
+
{% if agent_profile == 'proxy' %}
|
|
4
|
+
"model": {{ ('personal_proxy/' ~ agent_model) | to_json }},
|
|
5
|
+
"provider": {
|
|
6
|
+
"personal_proxy": {
|
|
7
|
+
"npm": "@ai-sdk/openai",
|
|
8
|
+
"options": {
|
|
9
|
+
"baseURL": {{ (agent_proxy_endpoint ~ '/v1') | to_json }},
|
|
10
|
+
"apiKey": "{env:CLIPROXY_API_KEY}"
|
|
11
|
+
},
|
|
12
|
+
"models": {
|
|
13
|
+
{% for id in (agent_proxy_models + [agent_model]) | unique %}
|
|
14
|
+
{{ id | to_json }}: {
|
|
15
|
+
"name": {{ id | to_json }}
|
|
16
|
+
}{{ '' if loop.last else ',' }}
|
|
17
|
+
{% endfor %}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
{% else %}
|
|
22
|
+
"model": {{ agent_model | to_json }}
|
|
23
|
+
{% endif %}
|
|
24
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"revision": {{ agent_revision | to_json }},
|
|
4
|
+
"fingerprint": {{ agent_fingerprint | to_json }},
|
|
5
|
+
"files": {
|
|
6
|
+
{% for name, sha256 in agent_files.items() %}
|
|
7
|
+
{{ name | to_json }}: {{ sha256 | to_json }}{{ '' if loop.last else ',' }}
|
|
8
|
+
{% endfor %}
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# How Ansible reaches a machine over SSH. dev-fleet points ANSIBLE_CONFIG at
|
|
2
|
+
# this file for every run (src/platform/ansible.ts), so no ansible.cfg of the
|
|
3
|
+
# operator's or of the working directory changes it. The SSH options
|
|
4
|
+
# themselves (strict host-key checking, keepalives, connection attempts, the
|
|
5
|
+
# fleet-owned known_hosts, user and port) come from src/ssh-connection.ts
|
|
6
|
+
# through the inventory, the same list every direct session takes, so the two
|
|
7
|
+
# cannot drift. Playbooks that run on the machine itself use no SSH at all.
|
|
8
|
+
[defaults]
|
|
9
|
+
timeout = 10
|
|
10
|
+
|
|
11
|
+
[ssh_connection]
|
|
12
|
+
retries = 3
|
|
13
|
+
pipelining = True
|
|
14
|
+
ssh_args = -o ControlMaster=auto -o ControlPersist=60s
|
package/ansible/github/git.yml
CHANGED
|
@@ -14,6 +14,11 @@
|
|
|
14
14
|
# gh answers for GitHub through the pinned toolchain's shim, so any
|
|
15
15
|
# session's PATH works.
|
|
16
16
|
git_credential_helper: "!'{{ git_home }}/.local/share/mise/shims/gh' auth git-credential"
|
|
17
|
+
# Optional commit signing (git.signing in fleet.json): each node signs
|
|
18
|
+
# with an SSH key of its own, made on the node. The private key never
|
|
19
|
+
# leaves it and is never in fleet.json, a variable or argv.
|
|
20
|
+
git_signs: "{{ git_signing | default('') == 'ssh' }}"
|
|
21
|
+
git_signing_key: '{{ git_home }}/.ssh/dev-fleet-git-signing'
|
|
17
22
|
tasks:
|
|
18
23
|
- name: Require a declared identity
|
|
19
24
|
ansible.builtin.assert:
|
|
@@ -64,3 +69,80 @@
|
|
|
64
69
|
- git_effective.results[2].stdout_lines | default([]) | last | default('') == git_credential_helper
|
|
65
70
|
fail_msg: GIT_IDENTITY_OVERRIDDEN; another Git configuration (often ~/.gitconfig) sets user.name, user.email or the GitHub credential helper after dev-fleet's. Remove those lines.
|
|
66
71
|
when: not ansible_check_mode
|
|
72
|
+
- name: Create the per-user SSH directory for the signing key
|
|
73
|
+
ansible.builtin.file:
|
|
74
|
+
path: '{{ git_home }}/.ssh'
|
|
75
|
+
state: directory
|
|
76
|
+
mode: '0700'
|
|
77
|
+
when: git_signs | bool
|
|
78
|
+
- name: Make this node's own commit signing key, once
|
|
79
|
+
ansible.builtin.command:
|
|
80
|
+
argv:
|
|
81
|
+
- ssh-keygen
|
|
82
|
+
- -q
|
|
83
|
+
- -t
|
|
84
|
+
- ed25519
|
|
85
|
+
- -N
|
|
86
|
+
- ''
|
|
87
|
+
- -C
|
|
88
|
+
- dev-fleet git signing
|
|
89
|
+
- -f
|
|
90
|
+
- '{{ git_signing_key }}'
|
|
91
|
+
creates: '{{ git_signing_key }}'
|
|
92
|
+
when: git_signs | bool
|
|
93
|
+
- name: Inspect the public half of the signing key
|
|
94
|
+
ansible.builtin.stat:
|
|
95
|
+
path: '{{ git_signing_key }}.pub'
|
|
96
|
+
register: git_signing_public_file
|
|
97
|
+
when: git_signs | bool
|
|
98
|
+
- name: Read the public half of the signing key
|
|
99
|
+
ansible.builtin.slurp:
|
|
100
|
+
src: '{{ git_signing_key }}.pub'
|
|
101
|
+
register: git_signing_public
|
|
102
|
+
when: git_signs | bool and git_signing_public_file.stat.exists
|
|
103
|
+
- name: Name the node's own key as the signer of the declared identity
|
|
104
|
+
ansible.builtin.copy:
|
|
105
|
+
dest: '{{ git_home }}/.config/git/allowed_signers'
|
|
106
|
+
content: "{{ git_email }} namespaces=\"git\" {{ git_signing_public.content | b64decode | trim }}\n"
|
|
107
|
+
mode: '0600'
|
|
108
|
+
when: git_signing_public is not skipped
|
|
109
|
+
- name: Sign every commit and tag with the node's own key, or stop signing
|
|
110
|
+
ansible.builtin.blockinfile:
|
|
111
|
+
path: '{{ git_config }}'
|
|
112
|
+
create: true
|
|
113
|
+
mode: '0600'
|
|
114
|
+
marker: '# {mark} dev-fleet: commit signing'
|
|
115
|
+
state: "{{ 'present' if git_signs | bool else 'absent' }}"
|
|
116
|
+
block: |
|
|
117
|
+
[gpg]
|
|
118
|
+
format = ssh
|
|
119
|
+
[gpg "ssh"]
|
|
120
|
+
allowedSignersFile = {{ git_home }}/.config/git/allowed_signers
|
|
121
|
+
[user]
|
|
122
|
+
signingkey = {{ git_signing_key }}.pub
|
|
123
|
+
[commit]
|
|
124
|
+
gpgsign = true
|
|
125
|
+
[tag]
|
|
126
|
+
gpgsign = true
|
|
127
|
+
- name: Read the effective signing configuration
|
|
128
|
+
ansible.builtin.command:
|
|
129
|
+
argv: [git, config, --global, --get-all, '{{ item }}']
|
|
130
|
+
environment:
|
|
131
|
+
HOME: '{{ git_home }}'
|
|
132
|
+
loop:
|
|
133
|
+
- gpg.format
|
|
134
|
+
- user.signingkey
|
|
135
|
+
- commit.gpgsign
|
|
136
|
+
register: git_signing_effective
|
|
137
|
+
check_mode: false
|
|
138
|
+
changed_when: false
|
|
139
|
+
failed_when: false
|
|
140
|
+
when: git_signs | bool
|
|
141
|
+
- name: Require the node's own key to sign every commit (worker:git-signing)
|
|
142
|
+
ansible.builtin.assert:
|
|
143
|
+
that:
|
|
144
|
+
- git_signing_effective.results[0].stdout_lines | default([]) | last | default('') == 'ssh'
|
|
145
|
+
- git_signing_effective.results[1].stdout_lines | default([]) | last | default('') == git_signing_key + '.pub'
|
|
146
|
+
- git_signing_effective.results[2].stdout_lines | default([]) | last | default('') == 'true'
|
|
147
|
+
fail_msg: GIT_SIGNING_OVERRIDDEN; another Git configuration (often ~/.gitconfig) sets gpg.format, user.signingkey or commit.gpgsign after dev-fleet's. Remove those lines.
|
|
148
|
+
when: git_signs | bool and not ansible_check_mode
|
|
@@ -1,17 +1,30 @@
|
|
|
1
1
|
---
|
|
2
|
-
#
|
|
2
|
+
# What a manager needs from root, given once by its administrator:
|
|
3
3
|
# ansible-playbook -K manager-privilege.yml -e user=$(id -un)
|
|
4
|
-
#
|
|
5
|
-
# manager.yml declares)
|
|
6
|
-
# manager
|
|
7
|
-
#
|
|
8
|
-
-
|
|
4
|
+
# - a sudoers rule that lets that user run exactly `pmset -c` (the AC power
|
|
5
|
+
# settings manager.yml declares) and read whether network time is on
|
|
6
|
+
# (`systemsetup -getusingnetworktime`, manager:clock), without a password,
|
|
7
|
+
# so every fleet-sync holds and measures them with no prompt;
|
|
8
|
+
# - a newsyslog drop-in that keeps the logs of the manager's scheduled jobs
|
|
9
|
+
# (fleet-sync --schedule and fleet-watch) under the size jobLogs in
|
|
10
|
+
# policy.json names. newsyslog runs as root, so it is installed here and
|
|
11
|
+
# measured by manager:job-logs;
|
|
12
|
+
# - the fleet's T3 administrator policy (t3-policy.yml), measured by
|
|
13
|
+
# t3:updates.
|
|
14
|
+
# Run it again after an upgrade when manager:clock or manager:job-logs says
|
|
15
|
+
# so, or with -e grant=absent to remove both; nothing else on the manager
|
|
16
|
+
# needs root.
|
|
17
|
+
- name: Grant the fleet manager its power settings and job log rotation
|
|
9
18
|
hosts: localhost
|
|
10
19
|
connection: local
|
|
11
20
|
become: true
|
|
12
21
|
gather_facts: false
|
|
22
|
+
vars_files:
|
|
23
|
+
- policy.json
|
|
13
24
|
vars:
|
|
14
25
|
grant: present
|
|
26
|
+
manager_home: "{{ ('~' ~ user) | expanduser }}"
|
|
27
|
+
job_log_directory: '{{ manager_home }}/{{ jobLogs.directory }}'
|
|
15
28
|
tasks:
|
|
16
29
|
- name: Check the request
|
|
17
30
|
ansible.builtin.assert:
|
|
@@ -19,19 +32,52 @@
|
|
|
19
32
|
- "user is match('^[a-z_][a-z0-9_.-]{0,31}$')"
|
|
20
33
|
- "user != 'root'"
|
|
21
34
|
- "grant in ['present', 'absent']"
|
|
22
|
-
|
|
35
|
+
- "manager_home is match('^/')"
|
|
36
|
+
- name: Allow exactly the AC power settings and the network time reading
|
|
23
37
|
ansible.builtin.copy:
|
|
24
38
|
dest: '/etc/sudoers.d/dev-fleet-manager-{{ user }}'
|
|
25
39
|
content: |
|
|
26
40
|
# Managed by dev-fleet: manager power policy
|
|
27
41
|
{{ user }} ALL=(root) NOPASSWD: /usr/bin/pmset -c *
|
|
42
|
+
{{ user }} ALL=(root) NOPASSWD: /usr/sbin/systemsetup -getusingnetworktime
|
|
28
43
|
owner: root
|
|
29
44
|
group: wheel
|
|
30
45
|
mode: '0440'
|
|
31
46
|
validate: /usr/sbin/visudo -cf %s
|
|
32
47
|
when: grant == 'present'
|
|
33
|
-
- name:
|
|
48
|
+
- name: Read the manager user's group
|
|
49
|
+
ansible.builtin.command:
|
|
50
|
+
argv: [/usr/bin/id, -gn, '{{ user }}']
|
|
51
|
+
changed_when: false
|
|
52
|
+
check_mode: false
|
|
53
|
+
register: manager_group
|
|
54
|
+
when: grant == 'present'
|
|
55
|
+
# newsyslog rotates a log once it is larger than the size given (in KiB),
|
|
56
|
+
# keeps that many compressed archives, and signals nothing (N): launchd
|
|
57
|
+
# opens the log again for every run of the job.
|
|
58
|
+
- name: Rotate the logs of the manager's scheduled jobs
|
|
59
|
+
ansible.builtin.copy:
|
|
60
|
+
dest: '/etc/newsyslog.d/dev-fleet-{{ user }}.conf'
|
|
61
|
+
content: |
|
|
62
|
+
# Managed by dev-fleet: manager job logs
|
|
63
|
+
# logfilename [owner:group] mode count size(KiB) when flags
|
|
64
|
+
{{ job_log_directory }}/{{ fleetSync.label }}.out.log {{ user }}:{{ manager_group.stdout }} 600 {{ jobLogs.keep }} {{ jobLogs.maxKiB }} * NZ
|
|
65
|
+
{{ job_log_directory }}/{{ fleetSync.label }}.err.log {{ user }}:{{ manager_group.stdout }} 600 {{ jobLogs.keep }} {{ jobLogs.maxKiB }} * NZ
|
|
66
|
+
{{ job_log_directory }}/{{ fleetWatch.label }}.out.log {{ user }}:{{ manager_group.stdout }} 600 {{ jobLogs.keep }} {{ jobLogs.maxKiB }} * NZ
|
|
67
|
+
{{ job_log_directory }}/{{ fleetWatch.label }}.err.log {{ user }}:{{ manager_group.stdout }} 600 {{ jobLogs.keep }} {{ jobLogs.maxKiB }} * NZ
|
|
68
|
+
owner: root
|
|
69
|
+
group: wheel
|
|
70
|
+
mode: '0644'
|
|
71
|
+
validate: /usr/sbin/newsyslog -n -f %s
|
|
72
|
+
when: grant == 'present'
|
|
73
|
+
- name: Remove the grant and the log rotation
|
|
34
74
|
ansible.builtin.file:
|
|
35
|
-
path: '
|
|
75
|
+
path: '{{ item }}'
|
|
36
76
|
state: absent
|
|
77
|
+
loop:
|
|
78
|
+
- '/etc/sudoers.d/dev-fleet-manager-{{ user }}'
|
|
79
|
+
- '/etc/newsyslog.d/dev-fleet-{{ user }}.conf'
|
|
37
80
|
when: grant == 'absent'
|
|
81
|
+
|
|
82
|
+
- name: Stage the fleet's T3 policy
|
|
83
|
+
ansible.builtin.import_playbook: t3-policy.yml
|