@matthesketh/fleet 1.1.0 → 1.6.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 +183 -251
- package/dist/adapters/detector/index.d.ts +8 -0
- package/dist/adapters/detector/index.js +54 -0
- package/dist/adapters/notifier/index.d.ts +2 -0
- package/dist/adapters/notifier/index.js +2 -0
- package/dist/adapters/notifier/stdout.d.ts +2 -0
- package/dist/adapters/notifier/stdout.js +8 -0
- package/dist/adapters/notifier/webhook.d.ts +9 -0
- package/dist/adapters/notifier/webhook.js +38 -0
- package/dist/adapters/runner/claude-cli.d.ts +7 -0
- package/dist/adapters/runner/claude-cli.js +231 -0
- package/dist/adapters/runner/mcp-call.d.ts +8 -0
- package/dist/adapters/runner/mcp-call.js +82 -0
- package/dist/adapters/runner/shell.d.ts +2 -0
- package/dist/adapters/runner/shell.js +103 -0
- package/dist/adapters/scheduler/systemd-timer.d.ts +17 -0
- package/dist/adapters/scheduler/systemd-timer.js +149 -0
- package/dist/adapters/signals/ci-status.d.ts +2 -0
- package/dist/adapters/signals/ci-status.js +79 -0
- package/dist/adapters/signals/container-up.d.ts +5 -0
- package/dist/adapters/signals/container-up.js +54 -0
- package/dist/adapters/signals/git-clean.d.ts +2 -0
- package/dist/adapters/signals/git-clean.js +55 -0
- package/dist/adapters/signals/index.d.ts +6 -0
- package/dist/adapters/signals/index.js +7 -0
- package/dist/adapters/types.d.ts +52 -0
- package/dist/adapters/types.js +1 -0
- package/dist/cli.js +43 -2
- package/dist/commands/add.js +0 -6
- package/dist/commands/boot-start.d.ts +1 -0
- package/dist/commands/boot-start.js +51 -0
- package/dist/commands/deploy.js +13 -0
- package/dist/commands/deps.js +5 -0
- package/dist/commands/egress.d.ts +1 -0
- package/dist/commands/egress.js +106 -0
- package/dist/commands/freeze.d.ts +4 -0
- package/dist/commands/freeze.js +64 -0
- package/dist/commands/logs.d.ts +1 -1
- package/dist/commands/logs.js +237 -8
- package/dist/commands/patch-systemd.d.ts +1 -0
- package/dist/commands/patch-systemd.js +126 -0
- package/dist/commands/rollback.d.ts +1 -0
- package/dist/commands/rollback.js +58 -0
- package/dist/commands/routine-run.d.ts +1 -0
- package/dist/commands/routine-run.js +122 -0
- package/dist/commands/routines.d.ts +1 -0
- package/dist/commands/routines.js +25 -0
- package/dist/commands/secrets.js +449 -16
- package/dist/commands/status.js +7 -3
- package/dist/commands/watchdog.d.ts +1 -1
- package/dist/commands/watchdog.js +16 -40
- package/dist/core/boot-refresh.d.ts +57 -0
- package/dist/core/boot-refresh.js +116 -0
- package/dist/core/deps/actors/pr-creator.js +11 -9
- package/dist/core/deps/collectors/docker-running.js +2 -2
- package/dist/core/deps/collectors/github-pr.js +5 -2
- package/dist/core/deps/collectors/npm.js +10 -5
- package/dist/core/deps/collectors/vulnerability.js +10 -6
- package/dist/core/deps/reporters/motd.js +1 -1
- package/dist/core/deps/reporters/telegram.js +2 -29
- package/dist/core/docker.js +45 -15
- package/dist/core/egress.d.ts +41 -0
- package/dist/core/egress.js +161 -0
- package/dist/core/exec.d.ts +7 -1
- package/dist/core/exec.js +25 -17
- package/dist/core/git.d.ts +1 -0
- package/dist/core/git.js +36 -23
- package/dist/core/github.js +27 -8
- package/dist/core/health.d.ts +3 -0
- package/dist/core/health.js +15 -3
- package/dist/core/logs-multi.d.ts +73 -0
- package/dist/core/logs-multi.js +163 -0
- package/dist/core/logs-policy.d.ts +55 -0
- package/dist/core/logs-policy.js +148 -0
- package/dist/core/nginx.js +8 -4
- package/dist/core/notify.d.ts +15 -0
- package/dist/core/notify.js +55 -0
- package/dist/core/registry.d.ts +25 -0
- package/dist/core/registry.js +57 -10
- package/dist/core/routines/cost-queries.d.ts +24 -0
- package/dist/core/routines/cost-queries.js +65 -0
- package/dist/core/routines/db.d.ts +9 -0
- package/dist/core/routines/db.js +126 -0
- package/dist/core/routines/defaults.d.ts +2 -0
- package/dist/core/routines/defaults.js +72 -0
- package/dist/core/routines/engine.d.ts +59 -0
- package/dist/core/routines/engine.js +175 -0
- package/dist/core/routines/incidents.d.ts +13 -0
- package/dist/core/routines/incidents.js +35 -0
- package/dist/core/routines/schema.d.ts +418 -0
- package/dist/core/routines/schema.js +113 -0
- package/dist/core/routines/signals-collector.d.ts +35 -0
- package/dist/core/routines/signals-collector.js +114 -0
- package/dist/core/routines/store.d.ts +316 -0
- package/dist/core/routines/store.js +99 -0
- package/dist/core/routines/test-utils.d.ts +2 -0
- package/dist/core/routines/test-utils.js +13 -0
- package/dist/core/secrets-audit.d.ts +21 -0
- package/dist/core/secrets-audit.js +60 -0
- package/dist/core/secrets-metadata.d.ts +39 -0
- package/dist/core/secrets-metadata.js +82 -0
- package/dist/core/secrets-motd.d.ts +20 -0
- package/dist/core/secrets-motd.js +72 -0
- package/dist/core/secrets-ops.d.ts +3 -1
- package/dist/core/secrets-ops.js +78 -13
- package/dist/core/secrets-providers.d.ts +50 -0
- package/dist/core/secrets-providers.js +291 -0
- package/dist/core/secrets-rotation.d.ts +52 -0
- package/dist/core/secrets-rotation.js +165 -0
- package/dist/core/secrets-snapshots.d.ts +26 -0
- package/dist/core/secrets-snapshots.js +95 -0
- package/dist/core/secrets-validate.js +2 -1
- package/dist/core/secrets.d.ts +12 -1
- package/dist/core/secrets.js +35 -24
- package/dist/core/self-update.d.ts +41 -0
- package/dist/core/self-update.js +73 -0
- package/dist/core/systemd.js +29 -12
- package/dist/core/telegram.d.ts +6 -0
- package/dist/core/telegram.js +32 -0
- package/dist/core/validate.d.ts +7 -0
- package/dist/core/validate.js +42 -0
- package/dist/index.js +0 -4
- package/dist/mcp/deps-tools.js +9 -1
- package/dist/mcp/git-tools.js +4 -4
- package/dist/mcp/server.js +193 -8
- package/dist/templates/systemd.js +3 -3
- package/dist/templates/unseal.js +5 -1
- package/dist/tui/components/Confirm.js +3 -4
- package/dist/tui/components/Header.js +37 -8
- package/dist/tui/components/KeyHint.js +14 -5
- package/dist/tui/exec-bridge.js +26 -12
- package/dist/tui/hooks/use-fleet-data.js +5 -2
- package/dist/tui/hooks/use-health.js +5 -2
- package/dist/tui/hooks/use-terminal-size.d.ts +1 -0
- package/dist/tui/hooks/use-terminal-size.js +1 -0
- package/dist/tui/router.js +133 -8
- package/dist/tui/routines/RoutinesApp.d.ts +8 -0
- package/dist/tui/routines/RoutinesApp.js +277 -0
- package/dist/tui/routines/components/AlertsPanel.d.ts +7 -0
- package/dist/tui/routines/components/AlertsPanel.js +22 -0
- package/dist/tui/routines/components/AlertsPanel.test.d.ts +1 -0
- package/dist/tui/routines/components/AlertsPanel.test.js +52 -0
- package/dist/tui/routines/components/CommandPalette.d.ts +12 -0
- package/dist/tui/routines/components/CommandPalette.js +21 -0
- package/dist/tui/routines/components/LiveRunPanel.d.ts +12 -0
- package/dist/tui/routines/components/LiveRunPanel.js +107 -0
- package/dist/tui/routines/components/RoutineForm.d.ts +8 -0
- package/dist/tui/routines/components/RoutineForm.js +254 -0
- package/dist/tui/routines/components/SignalsGrid.d.ts +13 -0
- package/dist/tui/routines/components/SignalsGrid.js +34 -0
- package/dist/tui/routines/components/SignalsGrid.test.d.ts +1 -0
- package/dist/tui/routines/components/SignalsGrid.test.js +43 -0
- package/dist/tui/routines/format.d.ts +7 -0
- package/dist/tui/routines/format.js +51 -0
- package/dist/tui/routines/hooks/use-git-fleet.d.ts +33 -0
- package/dist/tui/routines/hooks/use-git-fleet.js +82 -0
- package/dist/tui/routines/hooks/use-logs-stream.d.ts +13 -0
- package/dist/tui/routines/hooks/use-logs-stream.js +64 -0
- package/dist/tui/routines/hooks/use-ops-fleet.d.ts +20 -0
- package/dist/tui/routines/hooks/use-ops-fleet.js +70 -0
- package/dist/tui/routines/hooks/use-repo-detail.d.ts +31 -0
- package/dist/tui/routines/hooks/use-repo-detail.js +104 -0
- package/dist/tui/routines/hooks/use-security.d.ts +33 -0
- package/dist/tui/routines/hooks/use-security.js +110 -0
- package/dist/tui/routines/hooks/use-signals.d.ts +9 -0
- package/dist/tui/routines/hooks/use-signals.js +60 -0
- package/dist/tui/routines/runtime.d.ts +20 -0
- package/dist/tui/routines/runtime.js +40 -0
- package/dist/tui/routines/tabs/CostTab.d.ts +7 -0
- package/dist/tui/routines/tabs/CostTab.js +24 -0
- package/dist/tui/routines/tabs/DashboardTab.d.ts +15 -0
- package/dist/tui/routines/tabs/DashboardTab.js +10 -0
- package/dist/tui/routines/tabs/GitTab.d.ts +6 -0
- package/dist/tui/routines/tabs/GitTab.js +39 -0
- package/dist/tui/routines/tabs/LogsTab.d.ts +6 -0
- package/dist/tui/routines/tabs/LogsTab.js +58 -0
- package/dist/tui/routines/tabs/OpsTab.d.ts +6 -0
- package/dist/tui/routines/tabs/OpsTab.js +34 -0
- package/dist/tui/routines/tabs/RepoDetailView.d.ts +6 -0
- package/dist/tui/routines/tabs/RepoDetailView.js +12 -0
- package/dist/tui/routines/tabs/RoutinesTab.d.ts +10 -0
- package/dist/tui/routines/tabs/RoutinesTab.js +58 -0
- package/dist/tui/routines/tabs/ScaffoldTab.d.ts +2 -0
- package/dist/tui/routines/tabs/ScaffoldTab.js +127 -0
- package/dist/tui/routines/tabs/SecurityTab.d.ts +6 -0
- package/dist/tui/routines/tabs/SecurityTab.js +31 -0
- package/dist/tui/routines/tabs/SettingsTab.d.ts +6 -0
- package/dist/tui/routines/tabs/SettingsTab.js +61 -0
- package/dist/tui/routines/tabs/TimelineTab.d.ts +7 -0
- package/dist/tui/routines/tabs/TimelineTab.js +26 -0
- package/dist/tui/state.js +16 -1
- package/dist/tui/tests/flicker.test.d.ts +1 -0
- package/dist/tui/tests/flicker.test.js +105 -0
- package/dist/tui/tests/keyboard-integration.test.d.ts +1 -0
- package/dist/tui/tests/keyboard-integration.test.js +120 -0
- package/dist/tui/tests/test-app.d.ts +4 -0
- package/dist/tui/tests/test-app.js +79 -0
- package/dist/tui/types.d.ts +14 -1
- package/dist/tui/views/AppDetail.js +40 -26
- package/dist/tui/views/Dashboard.js +34 -9
- package/dist/tui/views/HealthView.js +42 -12
- package/dist/tui/views/LogsView.js +38 -10
- package/dist/tui/views/MultiLogsView.d.ts +2 -0
- package/dist/tui/views/MultiLogsView.js +165 -0
- package/dist/tui/views/SecretEdit.js +18 -7
- package/dist/tui/views/SecretsView.js +55 -39
- package/dist/ui/prompt.d.ts +52 -0
- package/dist/ui/prompt.js +169 -0
- package/package.json +33 -5
- package/dist/commands/motd.d.ts +0 -1
- package/dist/commands/motd.js +0 -10
- package/dist/templates/motd.d.ts +0 -1
- package/dist/templates/motd.js +0 -7
- package/dist/tui/components/AppList.d.ts +0 -12
- package/dist/tui/components/AppList.js +0 -32
- package/dist/tui/hooks/use-keyboard.d.ts +0 -1
- package/dist/tui/hooks/use-keyboard.js +0 -44
package/README.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
# fleet
|
|
4
4
|
|
|
5
|
+
[](https://auto-audit.hesketh.pro)
|
|
6
|
+
|
|
5
7
|
**Docker production management CLI + MCP server**
|
|
6
8
|
|
|
7
9
|
[](https://github.com/wrxck/fleet/actions/workflows/ci.yml)
|
|
@@ -10,7 +12,9 @@
|
|
|
10
12
|
[](https://www.typescriptlang.org/)
|
|
11
13
|
[](LICENSE)
|
|
12
14
|
|
|
13
|
-
|
|
15
|
+
Manage Docker Compose apps on a single server -- systemd orchestration, nginx routing, age-encrypted secrets, health monitoring, dependency tracking, Git workflows, and an MCP server for AI-assisted operations.
|
|
16
|
+
|
|
17
|
+
[Documentation](https://fleet.hesketh.pro) -- [npm](https://www.npmjs.com/package/@matthesketh/fleet) -- [GitHub](https://github.com/wrxck/fleet)
|
|
14
18
|
|
|
15
19
|
</div>
|
|
16
20
|
|
|
@@ -18,324 +22,252 @@ Manages Docker Compose applications on a single server with systemd orchestratio
|
|
|
18
22
|
|
|
19
23
|
## Architecture
|
|
20
24
|
|
|
25
|
+
```mermaid
|
|
26
|
+
graph TD
|
|
27
|
+
CLI["fleet CLI"]
|
|
28
|
+
TUI["TUI Dashboard"]
|
|
29
|
+
MCP["MCP Server"]
|
|
30
|
+
BOT["fleet-bot (Go)"]
|
|
31
|
+
|
|
32
|
+
CLI --> Core
|
|
33
|
+
TUI --> Core
|
|
34
|
+
MCP --> Core
|
|
35
|
+
BOT -->|"via MCP"| Core
|
|
36
|
+
|
|
37
|
+
subgraph Core["Core Modules"]
|
|
38
|
+
Registry["Registry"]
|
|
39
|
+
Docker["Docker Compose"]
|
|
40
|
+
Systemd["systemd"]
|
|
41
|
+
Nginx["nginx"]
|
|
42
|
+
Secrets["Secrets Vault"]
|
|
43
|
+
Health["Health Checks"]
|
|
44
|
+
Git["Git / GitHub"]
|
|
45
|
+
Deps["Dependency Monitor"]
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
Docker --> Containers["Containers"]
|
|
49
|
+
Systemd --> Services["systemd Services"]
|
|
50
|
+
Nginx --> Proxy["Reverse Proxy"]
|
|
51
|
+
Secrets --> Vault["vault/*.age"]
|
|
52
|
+
Secrets --> Runtime["/run/fleet-secrets"]
|
|
53
|
+
Health --> Alerts["Telegram / iMessage"]
|
|
21
54
|
```
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
fleet-bot (Go)
|
|
31
|
-
└── Telegram bot that runs Claude Code sessions for remote management
|
|
55
|
+
|
|
56
|
+
Each Docker Compose app is registered with its compose path, domains, port, and container names. Fleet generates systemd units so apps start on boot in the correct order. Secrets are encrypted at rest with [age](https://github.com/FiloSottile/age) and decrypted to a tmpfs on boot.
|
|
57
|
+
|
|
58
|
+
## Install
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
npm install -g @matthesketh/fleet
|
|
32
62
|
```
|
|
33
63
|
|
|
34
|
-
|
|
64
|
+
Requires Node.js 20+, Docker Compose v2, systemd, nginx, and [age](https://github.com/FiloSottile/age). See the [full setup guide](https://fleet.hesketh.pro/getting-started/) for details.
|
|
35
65
|
|
|
36
|
-
|
|
66
|
+
## Key Features
|
|
37
67
|
|
|
38
|
-
|
|
68
|
+
**Deploy and manage apps** -- `fleet deploy <app-dir>` registers, builds, and starts an app in one command. Control services with `start`, `stop`, `restart`, and `logs`.
|
|
39
69
|
|
|
40
|
-
-
|
|
41
|
-
- Docker + Docker Compose v2
|
|
42
|
-
- systemd
|
|
43
|
-
- nginx
|
|
44
|
-
- [age](https://github.com/FiloSottile/age) (for secrets)
|
|
45
|
-
- [gh](https://cli.github.com/) (for GitHub operations)
|
|
70
|
+
**Encrypted secrets** -- age-encrypted vault with automatic backups, pre-seal validation, drift detection, and atomic rollback. Decrypted to tmpfs at boot -- secrets never touch disk.
|
|
46
71
|
|
|
47
|
-
|
|
72
|
+
**Nginx routing** -- Generate proxy, SPA, or Next.js server blocks with `fleet nginx add`. Automatic config testing and reload.
|
|
48
73
|
|
|
49
|
-
|
|
74
|
+
**Health monitoring** -- Three-layer checks (systemd + container + HTTP) with `fleet health`. The `watchdog` command runs on cron and sends alerts on failure.
|
|
50
75
|
|
|
51
|
-
|
|
52
|
-
npm install -g @matthesketh/fleet
|
|
53
|
-
```
|
|
76
|
+
**Dependency scanning** -- Detects outdated packages, CVEs (via OSV), Docker image updates, and runtime EOL across all registered apps.
|
|
54
77
|
|
|
55
|
-
|
|
78
|
+
**Git workflows** -- Onboard apps to GitHub, manage branches, PRs, and releases from the CLI.
|
|
56
79
|
|
|
57
|
-
|
|
58
|
-
git clone https://github.com/wrxck/fleet.git
|
|
59
|
-
cd fleet
|
|
60
|
-
npm install
|
|
61
|
-
npm run build
|
|
62
|
-
sudo npm link
|
|
63
|
-
```
|
|
80
|
+
**Interactive dashboard** -- Run bare `fleet` to launch a full-screen TUI with real-time status.
|
|
64
81
|
|
|
65
|
-
|
|
82
|
+
See the [CLI reference](https://fleet.hesketh.pro/cli/) for the complete command list.
|
|
66
83
|
|
|
67
|
-
|
|
68
|
-
sudo fleet install-mcp
|
|
69
|
-
```
|
|
84
|
+
## Secrets Flow
|
|
70
85
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
"mcpServers": {
|
|
76
|
-
"fleet": {
|
|
77
|
-
"command": "fleet",
|
|
78
|
-
"args": ["mcp"]
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
```
|
|
86
|
+
```mermaid
|
|
87
|
+
graph LR
|
|
88
|
+
Import["fleet secrets import"]
|
|
89
|
+
Set["fleet secrets set"]
|
|
83
90
|
|
|
84
|
-
|
|
91
|
+
Import --> Encrypt["age encrypt"]
|
|
92
|
+
Set --> Encrypt
|
|
85
93
|
|
|
86
|
-
|
|
94
|
+
Encrypt --> Vault["vault/*.age"]
|
|
95
|
+
Vault -->|"boot / fleet secrets unseal"| Decrypt["age decrypt"]
|
|
96
|
+
Decrypt --> Runtime["/run/fleet-secrets (tmpfs)"]
|
|
97
|
+
Runtime -->|"env_file / secrets"| Containers["Docker Containers"]
|
|
87
98
|
|
|
88
|
-
|
|
89
|
-
|
|
99
|
+
Runtime -->|"fleet secrets seal"| Encrypt
|
|
100
|
+
Vault -.->|"drift detection"| Runtime
|
|
90
101
|
```
|
|
91
102
|
|
|
92
|
-
|
|
103
|
+
Secrets are imported or set individually, encrypted with age, and stored in the vault. On boot (or manually), they are decrypted to a tmpfs mount that Docker containers reference. Sealing writes runtime changes back to the vault. Drift detection compares vault vs runtime to catch unsaved changes.
|
|
93
104
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
fleet
|
|
97
|
-
|
|
98
|
-
|
|
105
|
+
### Per-secret rotation (v1.6)
|
|
106
|
+
|
|
107
|
+
Each secret carries metadata (`lastRotated`, `provider`, `strategy`) so fleet knows when it's stale and how to safely rotate it.
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
fleet secrets ages [<app>] # what's stale, who owns it, when last rotated
|
|
111
|
+
fleet secrets ages --motd # MOTD-formatted summary
|
|
112
|
+
fleet secrets motd-init # install /etc/update-motd.d/99-fleet-secrets
|
|
113
|
+
|
|
114
|
+
fleet secrets rotate <app> [<KEY>] # interactive walkthrough, [--dry-run] [--no-restart]
|
|
115
|
+
fleet secrets rollback <app> # restore latest snapshot, [--to <ts>]
|
|
116
|
+
fleet secrets snapshots <app> # list available snapshots
|
|
117
|
+
fleet secrets rotate-key # legacy: rotate the AGE master key
|
|
99
118
|
```
|
|
100
119
|
|
|
101
|
-
|
|
120
|
+
Rotation strategies (auto-detected from secret name, see `src/core/secrets-providers.ts`):
|
|
102
121
|
|
|
103
|
-
|
|
122
|
+
| Strategy | Examples | Behaviour |
|
|
123
|
+
|---|---|---|
|
|
124
|
+
| `immediate` | `STRIPE_SECRET_KEY`, `GITHUB_TOKEN`, `OPENAI_API_KEY` | Replace value, old dies |
|
|
125
|
+
| `dual-mode` | `JWT_SECRET`, `NEXTAUTH_SECRET`, `SESSION_SECRET` | New becomes primary, **old kept as `<NAME>_PREVIOUS`** so existing user sessions stay valid through grace period (your app must read both for verification) |
|
|
126
|
+
| `at-rest-key` | `ENCRYPTION_KEY`, `FIELD_ENCRYPTION_KEY` | Refused unless `--data-migrated` passed (you must re-encrypt stored data first) |
|
|
127
|
+
| `user-issued` | `USER_API_TOKEN`, `CUSTOMER_API_KEYS` | Refused — rotate per-user inside your app |
|
|
104
128
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
129
|
+
Safety rails on every rotation:
|
|
130
|
+
- Pre-rotation snapshot to `vault/.snapshots/<app>-<ts>.env.age` (atomic copy+rename)
|
|
131
|
+
- Hidden input prompt; new value never echoed in full (only `prefix…suffix (N chars)` for confirmation)
|
|
132
|
+
- Format validation against provider regex (catches paste typos)
|
|
133
|
+
- Entropy check rejects placeholders (`changeme`, `password`, all-same-char, < 8 chars)
|
|
134
|
+
- Auto-rollback on any failure during reseal
|
|
135
|
+
- Restart + 5s healthcheck gate after re-unseal; manual `fleet rollback` always available
|
|
136
|
+
- Append-only audit log at `~/.local/share/fleet/audit.jsonl` (mode 0600, never logs values)
|
|
111
137
|
|
|
112
|
-
###
|
|
138
|
+
### Log lifecycle (v1.6)
|
|
113
139
|
|
|
114
|
-
```
|
|
115
|
-
fleet
|
|
116
|
-
fleet
|
|
117
|
-
fleet
|
|
118
|
-
fleet
|
|
140
|
+
```
|
|
141
|
+
fleet logs setup <app> # interactive: retention/size/level
|
|
142
|
+
fleet logs setup --all # bulk default (7d / 100MB / info)
|
|
143
|
+
fleet logs status [<app>] # per-container size, driver, policy applied
|
|
144
|
+
fleet logs prune <app> # vacuum journald + truncate runaway json-file logs
|
|
145
|
+
fleet logs <app> --since 30m --grep err --level warn # filtered tail
|
|
119
146
|
```
|
|
120
147
|
|
|
121
|
-
`
|
|
148
|
+
`fleet logs setup` writes `<composePath>/.fleet/logging.override.yml` with json-file driver options for rotation. To activate, include the override in your compose start command (or fleet's systemd unit).
|
|
122
149
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
150
|
+
MCP tools — all token-conservative with small defaults and `truncated` flags:
|
|
151
|
+
- `fleet_logs_recent(app, lines=50, level=warn, sinceMinutes=15)` — bounded tail
|
|
152
|
+
- `fleet_logs_summary(app, sinceMinutes=60)` — counts + top 10 distinct error messages
|
|
153
|
+
- `fleet_logs_search(app, query, sinceMinutes=60, maxResults=20)` — bounded grep
|
|
154
|
+
- `fleet_logs_status(app?)` — driver + size per container
|
|
155
|
+
- `fleet_egress_snapshot(app)` — outbound destinations + violations
|
|
129
156
|
|
|
130
|
-
###
|
|
157
|
+
### Egress observation (v1.6)
|
|
131
158
|
|
|
132
|
-
```
|
|
133
|
-
fleet
|
|
134
|
-
fleet
|
|
135
|
-
fleet
|
|
136
|
-
fleet deps config # Show/set configuration
|
|
137
|
-
fleet deps ignore <pkg> --reason .. # Suppress a finding
|
|
138
|
-
fleet deps init # Install cron + MOTD for automated scanning
|
|
159
|
+
```
|
|
160
|
+
fleet egress observe <app> # snapshot current outbound flows via nsenter+ss
|
|
161
|
+
fleet egress show <app> # show config + allowlist
|
|
162
|
+
fleet egress allow <app> <host> # add to allowlist (supports *.host wildcards)
|
|
139
163
|
```
|
|
140
164
|
|
|
141
|
-
|
|
165
|
+
v1 is **observe-only** — it never blocks packets, so zero risk of breaking apps. Reads each container's network namespace via `nsenter` so it sees real container egress (not just host-side NAT'd flows). Reverse-resolves remote IPs to hostnames best-effort. RFC1918 destinations don't count as violations.
|
|
142
166
|
|
|
143
|
-
|
|
167
|
+
`enforce` mode (actual default-deny via nftables) is deferred to a future phase — by design, it requires the operator to explicitly promote a shadow-clean app, never auto-promotes.
|
|
144
168
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
169
|
+
## Deployment Flow
|
|
170
|
+
|
|
171
|
+
```mermaid
|
|
172
|
+
graph TD
|
|
173
|
+
Deploy["fleet deploy app-dir"]
|
|
174
|
+
Deploy --> Register{"Already\nregistered?"}
|
|
175
|
+
Register -->|No| Add["Register app"]
|
|
176
|
+
Register -->|Yes| Build
|
|
177
|
+
Add --> Build["docker compose build"]
|
|
178
|
+
Build --> Start{"Service\nrunning?"}
|
|
179
|
+
Start -->|No| StartSvc["systemctl start"]
|
|
180
|
+
Start -->|Yes| Restart["systemctl restart"]
|
|
181
|
+
StartSvc --> Healthy["App deployed"]
|
|
182
|
+
Restart --> Healthy
|
|
149
183
|
```
|
|
150
184
|
|
|
151
|
-
|
|
185
|
+
## Boot Refresh
|
|
186
|
+
|
|
187
|
+
On every systemd start — including reboots — Fleet pulls the latest code from GitHub and rebuilds the image if needed, before starting the container. The flow is entirely fail-safe: any failure at any step (dirty working tree, no remote, fetch error, non-fast-forward merge, build failure, or a 900-second wall-clock timeout) is logged and falls through to a plain `docker compose up` with the existing image. The container will always start.
|
|
152
188
|
|
|
153
|
-
|
|
154
|
-
- **spa** -- static SPA with `try_files` fallback to `index.html`
|
|
155
|
-
- **nextjs** -- Next.js-specific proxy with static asset handling
|
|
189
|
+
**New commands**
|
|
156
190
|
|
|
157
|
-
|
|
191
|
+
| Command | Description |
|
|
192
|
+
|---------|-------------|
|
|
193
|
+
| `fleet boot-start <app>` | Entry point systemd now invokes (`ExecStart`). Runs refresh then `docker compose up`. Not typically run by hand. |
|
|
194
|
+
| `fleet rollback <app>` | Re-tags `<image>:fleet-previous` → `<image>:latest` and restarts the service. Fleet tags the previous image automatically before every build. |
|
|
195
|
+
| `fleet patch-systemd` | Rewrites `ExecStart` in all installed unit files to use `fleet boot-start`, sets `TimeoutStartSec=900`, and backs up originals to `<path>.service.bak`. |
|
|
196
|
+
| `fleet patch-systemd --rollback` | Restores all `.bak` unit files and runs `daemon-reload`. |
|
|
158
197
|
|
|
159
|
-
|
|
198
|
+
**Kill switch**
|
|
199
|
+
|
|
200
|
+
To disable boot refresh entirely — next `systemctl start` goes straight to `docker compose up`:
|
|
160
201
|
|
|
161
202
|
```bash
|
|
162
|
-
|
|
163
|
-
fleet secrets import <app> [path] # Import .env or secrets dir into vault
|
|
164
|
-
fleet secrets export <app> # Print decrypted .env to stdout
|
|
165
|
-
fleet secrets list [app] # Show managed secrets (masked values)
|
|
166
|
-
fleet secrets set <app> <KEY> <VALUE> # Set a single secret
|
|
167
|
-
fleet secrets get <app> <KEY> # Print a single decrypted value
|
|
168
|
-
fleet secrets seal [app] # Re-encrypt from runtime back to vault
|
|
169
|
-
fleet secrets unseal # Decrypt vault to /run/fleet-secrets/
|
|
170
|
-
fleet secrets drift [app] # Detect vault vs runtime differences
|
|
171
|
-
fleet secrets restore <app> # Restore vault from backup
|
|
172
|
-
fleet secrets rotate # Generate new age key, re-encrypt everything
|
|
173
|
-
fleet secrets validate [app] # Check compose env vars vs vault keys
|
|
174
|
-
fleet secrets status # Vault state, key counts, seal status
|
|
203
|
+
sudo touch /etc/fleet/no-auto-refresh
|
|
175
204
|
```
|
|
176
205
|
|
|
177
|
-
|
|
178
|
-
- **env** -- `.env` files (key=value pairs), encrypted as `<app>.env.age`
|
|
179
|
-
- **secrets-dir** -- directories of secret files (e.g. database passwords), encrypted as `<app>.secrets.age`
|
|
206
|
+
Remove the file to re-enable.
|
|
180
207
|
|
|
181
|
-
|
|
208
|
+
**Registry field: `lastBuiltCommit`**
|
|
182
209
|
|
|
183
|
-
|
|
184
|
-
- **Automatic backups** -- vault files are backed up before any mutation
|
|
185
|
-
- **Pre-seal validation** -- rejects seal if >50% of keys would be removed (protects against accidental wipes)
|
|
186
|
-
- **Atomic rollback** -- backup is restored automatically if encryption fails
|
|
187
|
-
- **Drift detection** -- compare vault (survives reboot) vs runtime (lost on reboot) to catch unsaved changes
|
|
210
|
+
Each app in the registry stores the Git commit that was last built. Fleet sets this on `fleet deploy` and on every successful boot-refresh build. Boot refresh skips `docker compose build` when HEAD already matches this value, keeping boots fast when no code has changed.
|
|
188
211
|
|
|
189
|
-
|
|
212
|
+
**First boot after upgrade**
|
|
190
213
|
|
|
191
|
-
|
|
214
|
+
Any app with `lastBuiltCommit` unset will trigger a full rebuild the first time it boots after upgrading to this version. Expect a longer first boot for those apps.
|
|
192
215
|
|
|
193
|
-
|
|
194
|
-
fleet git status [app] # Git state for one or all apps
|
|
195
|
-
fleet git onboard <app> # Create GitHub repo, push, protect branches
|
|
196
|
-
fleet git onboard-all # Onboard all registered apps
|
|
197
|
-
fleet git branch <app> <name> [--from dev] # Create and push a feature branch
|
|
198
|
-
fleet git commit <app> -m "msg" # Stage and commit changes
|
|
199
|
-
fleet git push <app> # Push current branch
|
|
200
|
-
fleet git pr create <app> --title "..." # Create a pull request
|
|
201
|
-
fleet git pr list <app> # List open PRs
|
|
202
|
-
fleet git release <app> # Create develop -> main release PR
|
|
203
|
-
```
|
|
216
|
+
**Recovery escape hatches**
|
|
204
217
|
|
|
205
|
-
|
|
218
|
+
| Situation | Action |
|
|
219
|
+
|-----------|--------|
|
|
220
|
+
| One app misbehaving after a build | `fleet rollback <app>` |
|
|
221
|
+
| Registry corrupted | Auto-loads `.bak` on next read |
|
|
222
|
+
| Broad issue with boot-start behaviour | `sudo touch /etc/fleet/no-auto-refresh` |
|
|
223
|
+
| Worst case — revert all unit files | `fleet patch-systemd --rollback` |
|
|
206
224
|
|
|
207
|
-
|
|
225
|
+
## MCP Server
|
|
208
226
|
|
|
209
|
-
|
|
210
|
-
--json Output as JSON (where supported)
|
|
211
|
-
--dry-run Show what would happen without making changes
|
|
212
|
-
-y, --yes Skip confirmation prompts
|
|
213
|
-
-v Show version
|
|
214
|
-
-h Show help
|
|
215
|
-
```
|
|
227
|
+
Fleet exposes 36 tools via the [Model Context Protocol](https://modelcontextprotocol.io/) for AI-assisted server management. Run `fleet mcp` to start the stdio server, or install it into Claude Code:
|
|
216
228
|
|
|
217
|
-
|
|
229
|
+
```bash
|
|
230
|
+
sudo fleet install-mcp
|
|
231
|
+
```
|
|
218
232
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
### Available tools (33)
|
|
222
|
-
|
|
223
|
-
| Tool | Description |
|
|
224
|
-
|------|-------------|
|
|
225
|
-
| `fleet_status` | Dashboard data for all apps |
|
|
226
|
-
| `fleet_list` | List registered apps with config |
|
|
227
|
-
| `fleet_start` | Start an app via systemctl |
|
|
228
|
-
| `fleet_stop` | Stop an app via systemctl |
|
|
229
|
-
| `fleet_restart` | Restart an app via systemctl |
|
|
230
|
-
| `fleet_logs` | Get container logs |
|
|
231
|
-
| `fleet_health` | Run health checks for one/all apps |
|
|
232
|
-
| `fleet_deploy` | Build and restart an app |
|
|
233
|
-
| `fleet_nginx_add` | Create nginx config for a domain |
|
|
234
|
-
| `fleet_nginx_list` | List nginx site configs |
|
|
235
|
-
| `fleet_register` | Register a new app in the fleet registry |
|
|
236
|
-
| `fleet_secrets_status` | Vault state and counts |
|
|
237
|
-
| `fleet_secrets_list` | List secrets (masked values) |
|
|
238
|
-
| `fleet_secrets_unseal` | Decrypt vault to runtime |
|
|
239
|
-
| `fleet_secrets_validate` | Check compose env vars vs vault |
|
|
240
|
-
| `fleet_secrets_set` | Set a single secret key/value |
|
|
241
|
-
| `fleet_secrets_get` | Get a single decrypted value |
|
|
242
|
-
| `fleet_secrets_seal` | Seal runtime changes back to vault |
|
|
243
|
-
| `fleet_secrets_drift` | Detect vault vs runtime drift |
|
|
244
|
-
| `fleet_secrets_restore` | Restore vault from backup |
|
|
245
|
-
| `fleet_git_status` | Git state for one/all apps |
|
|
246
|
-
| `fleet_git_onboard` | GitHub setup: repo, push, protect |
|
|
247
|
-
| `fleet_git_branch` | Create and push a feature branch |
|
|
248
|
-
| `fleet_git_commit` | Stage and commit changes |
|
|
249
|
-
| `fleet_git_push` | Push current branch |
|
|
250
|
-
| `fleet_git_pr_create` | Create a pull request |
|
|
251
|
-
| `fleet_git_pr_list` | List pull requests |
|
|
252
|
-
| `fleet_git_release` | Create develop -> main release PR |
|
|
253
|
-
| `fleet_deps_status` | Dependency health summary from cache |
|
|
254
|
-
| `fleet_deps_scan` | Run a fresh dependency scan |
|
|
255
|
-
| `fleet_deps_app` | Dependency findings for a specific app |
|
|
256
|
-
| `fleet_deps_fix` | Create PR with dependency updates (dry-run default) |
|
|
257
|
-
| `fleet_deps_ignore` | Add an ignore rule for a finding |
|
|
258
|
-
| `fleet_deps_config` | Get or set dependency monitoring config |
|
|
233
|
+
Tools cover the full surface area: app lifecycle, secrets, nginx, Git, health checks, and dependency monitoring. See the [MCP documentation](https://fleet.hesketh.pro/mcp/) for the complete tool list.
|
|
259
234
|
|
|
260
235
|
## fleet-bot
|
|
261
236
|
|
|
262
|
-
A Go
|
|
237
|
+
A Go companion bot (`bot/`) that provides remote server management through Telegram or iMessage. It runs Claude Code sessions with access to fleet's MCP tools for hands-free operations.
|
|
263
238
|
|
|
264
|
-
|
|
239
|
+
See the [bot documentation](https://fleet.hesketh.pro/bot/setup/) for setup instructions.
|
|
265
240
|
|
|
266
|
-
|
|
267
|
-
cd bot
|
|
268
|
-
make build
|
|
269
|
-
sudo cp fleet-bot /usr/local/bin/
|
|
270
|
-
sudo systemctl enable --now fleet-bot
|
|
271
|
-
```
|
|
241
|
+
## Self-update
|
|
272
242
|
|
|
273
|
-
|
|
243
|
+
When `fleet`'s TUI launches it does a non-blocking `git fetch` against `origin/develop`. If the local repo is behind, a banner appears under the header:
|
|
274
244
|
|
|
275
245
|
```
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
│ ├── install-mcp.ts Self-install as Claude Code MCP server
|
|
287
|
-
│ ├── list.ts List apps
|
|
288
|
-
│ ├── logs.ts Container logs
|
|
289
|
-
│ ├── nginx.ts Nginx management
|
|
290
|
-
│ ├── remove.ts Deregister app
|
|
291
|
-
│ ├── restart.ts Restart service
|
|
292
|
-
│ ├── secrets.ts Secrets vault management
|
|
293
|
-
│ ├── start.ts Start service
|
|
294
|
-
│ ├── status.ts Dashboard
|
|
295
|
-
│ ├── stop.ts Stop service
|
|
296
|
-
│ └── watchdog.ts Health monitor + Telegram alerts
|
|
297
|
-
├── core/ Core logic
|
|
298
|
-
│ ├── docker.ts Docker Compose operations
|
|
299
|
-
│ ├── errors.ts Error types
|
|
300
|
-
│ ├── exec.ts Shell execution helpers
|
|
301
|
-
│ ├── git.ts Git operations
|
|
302
|
-
│ ├── git-onboard.ts GitHub onboarding logic
|
|
303
|
-
│ ├── github.ts GitHub API via gh CLI
|
|
304
|
-
│ ├── health.ts Health check logic
|
|
305
|
-
│ ├── nginx.ts Nginx file operations
|
|
306
|
-
│ ├── registry.ts App registry (data/registry.json)
|
|
307
|
-
│ ├── secrets.ts Vault primitives (age encrypt/decrypt, backup/restore)
|
|
308
|
-
│ ├── secrets-ops.ts High-level secrets operations (safe seal, drift, validation)
|
|
309
|
-
│ ├── secrets-validate.ts Compose vs vault validation
|
|
310
|
-
│ ├── systemd.ts systemctl operations
|
|
311
|
-
│ └── deps/ Dependency health (collectors, reporters, actors)
|
|
312
|
-
├── mcp/
|
|
313
|
-
│ ├── server.ts MCP server setup + tool registration
|
|
314
|
-
│ ├── git-tools.ts Git-related MCP tools
|
|
315
|
-
│ ├── secrets-tools.ts Secrets MCP tools (set, get, seal, drift, restore)
|
|
316
|
-
│ └── deps-tools.ts Dependency monitoring MCP tools
|
|
317
|
-
├── templates/
|
|
318
|
-
│ ├── gitignore.ts .gitignore generator
|
|
319
|
-
│ ├── nginx.ts Nginx config generator
|
|
320
|
-
│ ├── systemd.ts systemd unit generator
|
|
321
|
-
│ └── unseal.ts Unseal service generator
|
|
322
|
-
└── ui/
|
|
323
|
-
├── confirm.ts Interactive confirmation
|
|
324
|
-
└── output.ts Coloured terminal output
|
|
325
|
-
|
|
326
|
-
bot/ fleet-bot (Go Telegram bot)
|
|
327
|
-
data/ Runtime data (registry.json)
|
|
328
|
-
vault/ Encrypted secrets (*.age files)
|
|
246
|
+
↑ Update available: 3 commits ahead — feat: ... Press U to install.
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
Pressing `U` runs `git pull --ff-only` then `npm run build` (refused if the working tree is dirty). The new binary is live for the next `fleet …` invocation. Recheck happens every 30 minutes for long-running TUI sessions.
|
|
250
|
+
|
|
251
|
+
## Testing
|
|
252
|
+
|
|
253
|
+
```bash
|
|
254
|
+
npm test # unit + mocked tests (1106 passing)
|
|
255
|
+
FLEET_INTEGRATION=1 npm test # also runs boot-refresh integration tests (1156 passing, 0 skipped)
|
|
329
256
|
```
|
|
330
257
|
|
|
258
|
+
Set `FLEET_INTEGRATION=1` to opt into integration tests that hit real systemd / docker. Skipped by default in CI.
|
|
259
|
+
|
|
331
260
|
## Development
|
|
332
261
|
|
|
333
262
|
```bash
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
npm
|
|
263
|
+
git clone https://github.com/wrxck/fleet.git
|
|
264
|
+
cd fleet
|
|
265
|
+
npm install
|
|
266
|
+
npm test # vitest
|
|
267
|
+
npm run build # compile TypeScript to dist/
|
|
268
|
+
npm run dev # run with tsx (no build needed)
|
|
337
269
|
```
|
|
338
270
|
|
|
339
271
|
## License
|
|
340
272
|
|
|
341
|
-
MIT
|
|
273
|
+
MIT
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { StackDetector } from '../types.js';
|
|
2
|
+
export declare const nodeDetector: StackDetector;
|
|
3
|
+
export declare const dockerDetector: StackDetector;
|
|
4
|
+
export declare const pythonDetector: StackDetector;
|
|
5
|
+
export declare const rustDetector: StackDetector;
|
|
6
|
+
export declare const genericDetector: StackDetector;
|
|
7
|
+
export declare const BUILT_IN_DETECTORS: readonly StackDetector[];
|
|
8
|
+
export declare function detectStacks(repoPath: string, detectors?: readonly StackDetector[]): StackDetector['id'][];
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
export const nodeDetector = {
|
|
4
|
+
id: 'node',
|
|
5
|
+
priority: 10,
|
|
6
|
+
detect(repoPath) {
|
|
7
|
+
return existsSync(join(repoPath, 'package.json'));
|
|
8
|
+
},
|
|
9
|
+
};
|
|
10
|
+
export const dockerDetector = {
|
|
11
|
+
id: 'docker',
|
|
12
|
+
priority: 20,
|
|
13
|
+
detect(repoPath) {
|
|
14
|
+
return (existsSync(join(repoPath, 'docker-compose.yml'))
|
|
15
|
+
|| existsSync(join(repoPath, 'docker-compose.yaml'))
|
|
16
|
+
|| existsSync(join(repoPath, 'Dockerfile')));
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
export const pythonDetector = {
|
|
20
|
+
id: 'python',
|
|
21
|
+
priority: 10,
|
|
22
|
+
detect(repoPath) {
|
|
23
|
+
return (existsSync(join(repoPath, 'pyproject.toml'))
|
|
24
|
+
|| existsSync(join(repoPath, 'requirements.txt'))
|
|
25
|
+
|| existsSync(join(repoPath, 'uv.lock')));
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
export const rustDetector = {
|
|
29
|
+
id: 'rust',
|
|
30
|
+
priority: 10,
|
|
31
|
+
detect(repoPath) {
|
|
32
|
+
return existsSync(join(repoPath, 'Cargo.toml'));
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
export const genericDetector = {
|
|
36
|
+
id: 'generic',
|
|
37
|
+
priority: 1,
|
|
38
|
+
detect() {
|
|
39
|
+
return true;
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
export const BUILT_IN_DETECTORS = Object.freeze([
|
|
43
|
+
dockerDetector,
|
|
44
|
+
nodeDetector,
|
|
45
|
+
pythonDetector,
|
|
46
|
+
rustDetector,
|
|
47
|
+
genericDetector,
|
|
48
|
+
]);
|
|
49
|
+
export function detectStacks(repoPath, detectors = BUILT_IN_DETECTORS) {
|
|
50
|
+
return detectors
|
|
51
|
+
.filter(d => d.detect(repoPath))
|
|
52
|
+
.sort((a, b) => b.priority - a.priority)
|
|
53
|
+
.map(d => d.id);
|
|
54
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { NotifierAdapter } from '../types.js';
|
|
2
|
+
export interface WebhookOptions {
|
|
3
|
+
url: string;
|
|
4
|
+
secret?: string;
|
|
5
|
+
headers?: Record<string, string>;
|
|
6
|
+
fetcher?: typeof fetch;
|
|
7
|
+
timeoutMs?: number;
|
|
8
|
+
}
|
|
9
|
+
export declare function createWebhookNotifier(opts: WebhookOptions): NotifierAdapter;
|