@matthesketh/fleet 1.2.0 → 1.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.
Files changed (218) hide show
  1. package/README.md +183 -251
  2. package/dist/adapters/detector/index.d.ts +8 -0
  3. package/dist/adapters/detector/index.js +54 -0
  4. package/dist/adapters/notifier/index.d.ts +2 -0
  5. package/dist/adapters/notifier/index.js +2 -0
  6. package/dist/adapters/notifier/stdout.d.ts +2 -0
  7. package/dist/adapters/notifier/stdout.js +8 -0
  8. package/dist/adapters/notifier/webhook.d.ts +9 -0
  9. package/dist/adapters/notifier/webhook.js +38 -0
  10. package/dist/adapters/runner/claude-cli.d.ts +7 -0
  11. package/dist/adapters/runner/claude-cli.js +231 -0
  12. package/dist/adapters/runner/mcp-call.d.ts +8 -0
  13. package/dist/adapters/runner/mcp-call.js +82 -0
  14. package/dist/adapters/runner/shell.d.ts +2 -0
  15. package/dist/adapters/runner/shell.js +103 -0
  16. package/dist/adapters/scheduler/systemd-timer.d.ts +17 -0
  17. package/dist/adapters/scheduler/systemd-timer.js +149 -0
  18. package/dist/adapters/signals/ci-status.d.ts +2 -0
  19. package/dist/adapters/signals/ci-status.js +79 -0
  20. package/dist/adapters/signals/container-up.d.ts +5 -0
  21. package/dist/adapters/signals/container-up.js +54 -0
  22. package/dist/adapters/signals/git-clean.d.ts +2 -0
  23. package/dist/adapters/signals/git-clean.js +55 -0
  24. package/dist/adapters/signals/index.d.ts +6 -0
  25. package/dist/adapters/signals/index.js +7 -0
  26. package/dist/adapters/types.d.ts +52 -0
  27. package/dist/adapters/types.js +1 -0
  28. package/dist/cli.js +46 -2
  29. package/dist/commands/add.js +0 -6
  30. package/dist/commands/boot-start.d.ts +1 -0
  31. package/dist/commands/boot-start.js +51 -0
  32. package/dist/commands/deploy.js +13 -0
  33. package/dist/commands/deps.js +5 -0
  34. package/dist/commands/egress.d.ts +1 -0
  35. package/dist/commands/egress.js +106 -0
  36. package/dist/commands/freeze.d.ts +4 -0
  37. package/dist/commands/freeze.js +64 -0
  38. package/dist/commands/guard.d.ts +1 -0
  39. package/dist/commands/guard.js +144 -0
  40. package/dist/commands/logs.d.ts +1 -1
  41. package/dist/commands/logs.js +237 -8
  42. package/dist/commands/patch-systemd.d.ts +1 -0
  43. package/dist/commands/patch-systemd.js +126 -0
  44. package/dist/commands/rollback.d.ts +1 -0
  45. package/dist/commands/rollback.js +58 -0
  46. package/dist/commands/routine-run.d.ts +1 -0
  47. package/dist/commands/routine-run.js +122 -0
  48. package/dist/commands/routines.d.ts +1 -0
  49. package/dist/commands/routines.js +25 -0
  50. package/dist/commands/secrets.js +449 -16
  51. package/dist/commands/status.js +7 -3
  52. package/dist/commands/watchdog.d.ts +1 -1
  53. package/dist/commands/watchdog.js +16 -40
  54. package/dist/core/boot-refresh.d.ts +57 -0
  55. package/dist/core/boot-refresh.js +116 -0
  56. package/dist/core/deps/actors/pr-creator.js +11 -9
  57. package/dist/core/deps/collectors/docker-running.js +2 -2
  58. package/dist/core/deps/collectors/github-pr.js +5 -2
  59. package/dist/core/deps/collectors/npm.js +10 -5
  60. package/dist/core/deps/collectors/vulnerability.js +10 -6
  61. package/dist/core/deps/reporters/motd.js +1 -1
  62. package/dist/core/deps/reporters/telegram.js +2 -29
  63. package/dist/core/docker.js +45 -15
  64. package/dist/core/egress.d.ts +41 -0
  65. package/dist/core/egress.js +161 -0
  66. package/dist/core/exec.d.ts +7 -1
  67. package/dist/core/exec.js +25 -17
  68. package/dist/core/git.d.ts +1 -0
  69. package/dist/core/git.js +36 -23
  70. package/dist/core/github.js +27 -8
  71. package/dist/core/health.d.ts +3 -0
  72. package/dist/core/health.js +15 -3
  73. package/dist/core/logs-multi.d.ts +73 -0
  74. package/dist/core/logs-multi.js +163 -0
  75. package/dist/core/logs-policy.d.ts +55 -0
  76. package/dist/core/logs-policy.js +148 -0
  77. package/dist/core/nginx.js +8 -4
  78. package/dist/core/notify.d.ts +15 -0
  79. package/dist/core/notify.js +55 -0
  80. package/dist/core/registry.d.ts +25 -0
  81. package/dist/core/registry.js +57 -10
  82. package/dist/core/routines/cost-queries.d.ts +24 -0
  83. package/dist/core/routines/cost-queries.js +65 -0
  84. package/dist/core/routines/db.d.ts +9 -0
  85. package/dist/core/routines/db.js +126 -0
  86. package/dist/core/routines/defaults.d.ts +2 -0
  87. package/dist/core/routines/defaults.js +72 -0
  88. package/dist/core/routines/engine.d.ts +59 -0
  89. package/dist/core/routines/engine.js +175 -0
  90. package/dist/core/routines/incidents.d.ts +13 -0
  91. package/dist/core/routines/incidents.js +35 -0
  92. package/dist/core/routines/schema.d.ts +418 -0
  93. package/dist/core/routines/schema.js +113 -0
  94. package/dist/core/routines/signals-collector.d.ts +35 -0
  95. package/dist/core/routines/signals-collector.js +114 -0
  96. package/dist/core/routines/store.d.ts +316 -0
  97. package/dist/core/routines/store.js +99 -0
  98. package/dist/core/routines/test-utils.d.ts +2 -0
  99. package/dist/core/routines/test-utils.js +13 -0
  100. package/dist/core/secrets-audit.d.ts +21 -0
  101. package/dist/core/secrets-audit.js +60 -0
  102. package/dist/core/secrets-metadata.d.ts +39 -0
  103. package/dist/core/secrets-metadata.js +82 -0
  104. package/dist/core/secrets-motd.d.ts +20 -0
  105. package/dist/core/secrets-motd.js +72 -0
  106. package/dist/core/secrets-ops.d.ts +3 -1
  107. package/dist/core/secrets-ops.js +78 -13
  108. package/dist/core/secrets-providers.d.ts +50 -0
  109. package/dist/core/secrets-providers.js +291 -0
  110. package/dist/core/secrets-rotation.d.ts +52 -0
  111. package/dist/core/secrets-rotation.js +165 -0
  112. package/dist/core/secrets-snapshots.d.ts +26 -0
  113. package/dist/core/secrets-snapshots.js +95 -0
  114. package/dist/core/secrets-validate.js +2 -1
  115. package/dist/core/secrets.d.ts +12 -1
  116. package/dist/core/secrets.js +35 -24
  117. package/dist/core/self-update.d.ts +41 -0
  118. package/dist/core/self-update.js +73 -0
  119. package/dist/core/systemd.js +29 -12
  120. package/dist/core/telegram.d.ts +6 -0
  121. package/dist/core/telegram.js +32 -0
  122. package/dist/core/validate.d.ts +7 -0
  123. package/dist/core/validate.js +42 -0
  124. package/dist/index.js +0 -4
  125. package/dist/mcp/deps-tools.js +9 -1
  126. package/dist/mcp/git-tools.js +4 -4
  127. package/dist/mcp/server.js +193 -8
  128. package/dist/templates/systemd.js +3 -3
  129. package/dist/templates/unseal.js +5 -1
  130. package/dist/tui/components/KeyHint.js +10 -0
  131. package/dist/tui/exec-bridge.js +26 -12
  132. package/dist/tui/hooks/use-fleet-data.js +5 -2
  133. package/dist/tui/hooks/use-health.js +5 -2
  134. package/dist/tui/router.js +60 -7
  135. package/dist/tui/routines/RoutinesApp.d.ts +8 -0
  136. package/dist/tui/routines/RoutinesApp.js +277 -0
  137. package/dist/tui/routines/components/AlertsPanel.d.ts +7 -0
  138. package/dist/tui/routines/components/AlertsPanel.js +22 -0
  139. package/dist/tui/routines/components/AlertsPanel.test.d.ts +1 -0
  140. package/dist/tui/routines/components/AlertsPanel.test.js +52 -0
  141. package/dist/tui/routines/components/CommandPalette.d.ts +12 -0
  142. package/dist/tui/routines/components/CommandPalette.js +21 -0
  143. package/dist/tui/routines/components/LiveRunPanel.d.ts +12 -0
  144. package/dist/tui/routines/components/LiveRunPanel.js +107 -0
  145. package/dist/tui/routines/components/RoutineForm.d.ts +8 -0
  146. package/dist/tui/routines/components/RoutineForm.js +254 -0
  147. package/dist/tui/routines/components/SignalsGrid.d.ts +13 -0
  148. package/dist/tui/routines/components/SignalsGrid.js +34 -0
  149. package/dist/tui/routines/components/SignalsGrid.test.d.ts +1 -0
  150. package/dist/tui/routines/components/SignalsGrid.test.js +43 -0
  151. package/dist/tui/routines/format.d.ts +7 -0
  152. package/dist/tui/routines/format.js +51 -0
  153. package/dist/tui/routines/hooks/use-git-fleet.d.ts +33 -0
  154. package/dist/tui/routines/hooks/use-git-fleet.js +82 -0
  155. package/dist/tui/routines/hooks/use-logs-stream.d.ts +13 -0
  156. package/dist/tui/routines/hooks/use-logs-stream.js +64 -0
  157. package/dist/tui/routines/hooks/use-ops-fleet.d.ts +20 -0
  158. package/dist/tui/routines/hooks/use-ops-fleet.js +70 -0
  159. package/dist/tui/routines/hooks/use-repo-detail.d.ts +31 -0
  160. package/dist/tui/routines/hooks/use-repo-detail.js +104 -0
  161. package/dist/tui/routines/hooks/use-security.d.ts +33 -0
  162. package/dist/tui/routines/hooks/use-security.js +110 -0
  163. package/dist/tui/routines/hooks/use-signals.d.ts +9 -0
  164. package/dist/tui/routines/hooks/use-signals.js +60 -0
  165. package/dist/tui/routines/runtime.d.ts +20 -0
  166. package/dist/tui/routines/runtime.js +40 -0
  167. package/dist/tui/routines/tabs/CostTab.d.ts +7 -0
  168. package/dist/tui/routines/tabs/CostTab.js +24 -0
  169. package/dist/tui/routines/tabs/DashboardTab.d.ts +15 -0
  170. package/dist/tui/routines/tabs/DashboardTab.js +10 -0
  171. package/dist/tui/routines/tabs/GitTab.d.ts +6 -0
  172. package/dist/tui/routines/tabs/GitTab.js +39 -0
  173. package/dist/tui/routines/tabs/LogsTab.d.ts +6 -0
  174. package/dist/tui/routines/tabs/LogsTab.js +58 -0
  175. package/dist/tui/routines/tabs/OpsTab.d.ts +6 -0
  176. package/dist/tui/routines/tabs/OpsTab.js +34 -0
  177. package/dist/tui/routines/tabs/RepoDetailView.d.ts +6 -0
  178. package/dist/tui/routines/tabs/RepoDetailView.js +12 -0
  179. package/dist/tui/routines/tabs/RoutinesTab.d.ts +10 -0
  180. package/dist/tui/routines/tabs/RoutinesTab.js +58 -0
  181. package/dist/tui/routines/tabs/ScaffoldTab.d.ts +2 -0
  182. package/dist/tui/routines/tabs/ScaffoldTab.js +127 -0
  183. package/dist/tui/routines/tabs/SecurityTab.d.ts +6 -0
  184. package/dist/tui/routines/tabs/SecurityTab.js +31 -0
  185. package/dist/tui/routines/tabs/SettingsTab.d.ts +6 -0
  186. package/dist/tui/routines/tabs/SettingsTab.js +61 -0
  187. package/dist/tui/routines/tabs/TimelineTab.d.ts +7 -0
  188. package/dist/tui/routines/tabs/TimelineTab.js +26 -0
  189. package/dist/tui/state.js +1 -1
  190. package/dist/tui/tests/keyboard-integration.test.js +3 -0
  191. package/dist/tui/tests/test-app.js +1 -1
  192. package/dist/tui/types.d.ts +2 -2
  193. package/dist/tui/views/AppDetail.js +3 -4
  194. package/dist/tui/views/HealthView.js +7 -1
  195. package/dist/tui/views/LogsView.js +24 -1
  196. package/dist/tui/views/MultiLogsView.d.ts +2 -0
  197. package/dist/tui/views/MultiLogsView.js +165 -0
  198. package/dist/tui/views/SecretEdit.js +10 -3
  199. package/dist/tui/views/SecretsView.js +6 -3
  200. package/dist/ui/prompt.d.ts +52 -0
  201. package/dist/ui/prompt.js +169 -0
  202. package/package.json +34 -21
  203. package/scripts/guard/cert-expiry-watch +109 -0
  204. package/scripts/guard/cf-audit-monitor +169 -0
  205. package/scripts/guard/cf-snapshot +124 -0
  206. package/scripts/guard/cron.d-cf-protect +11 -0
  207. package/scripts/guard/dns-drift-watch +138 -0
  208. package/scripts/guard/fleet-guard +282 -0
  209. package/scripts/guard/fleet-guard-execute +197 -0
  210. package/scripts/guard/notify +108 -0
  211. package/dist/commands/motd.d.ts +0 -1
  212. package/dist/commands/motd.js +0 -10
  213. package/dist/templates/motd.d.ts +0 -1
  214. package/dist/templates/motd.js +0 -7
  215. package/dist/tui/components/AppList.d.ts +0 -12
  216. package/dist/tui/components/AppList.js +0 -32
  217. package/dist/tui/hooks/use-keyboard.d.ts +0 -1
  218. 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
+ [![audited by auto-audit](https://img.shields.io/badge/audited_by-auto--audit-6366f1?logo=github&logoColor=white)](https://auto-audit.hesketh.pro)
6
+
5
7
  **Docker production management CLI + MCP server**
6
8
 
7
9
  [![CI](https://github.com/wrxck/fleet/actions/workflows/ci.yml/badge.svg)](https://github.com/wrxck/fleet/actions/workflows/ci.yml)
@@ -10,7 +12,9 @@
10
12
  [![TypeScript](https://img.shields.io/badge/TypeScript-5.6-blue?logo=typescript&logoColor=white)](https://www.typescriptlang.org/)
11
13
  [![License](https://img.shields.io/github/license/wrxck/fleet)](LICENSE)
12
14
 
13
- Manages Docker Compose applications on a single server with systemd orchestration, nginx configuration, encrypted secrets, Git/GitHub workflows, health monitoring, dependency tracking, and Telegram alerts.
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
- fleet CLI (TypeScript/Node.js)
23
- ├── Commands CLI interface (fleet <command>)
24
- ├── MCP Server Claude Code integration (fleet mcp)
25
- ├── Registry App inventory (data/registry.json)
26
- ├── Secrets Vault age-encrypted secrets (vault/*.age)
27
- ├── Deps Monitor Dependency health scanning + alerting
28
- └── Templates systemd, nginx, gitignore generators
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
- ### How it works
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
- Each Docker Compose app is registered in fleet's registry with its compose path, service name, domains, port, and container names. Fleet generates a systemd service unit for each app so they start on boot in the correct order (databases first, then dependents). Secrets are encrypted at rest using [age](https://github.com/FiloSottile/age) and decrypted to a tmpfs at `/run/fleet-secrets/` on boot via a systemd oneshot service.
66
+ ## Key Features
37
67
 
38
- ## Requirements
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
- - Node.js 20+
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
- ## Install
72
+ **Nginx routing** -- Generate proxy, SPA, or Next.js server blocks with `fleet nginx add`. Automatic config testing and reload.
48
73
 
49
- ### From npm
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
- ```bash
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
- ### From source
78
+ **Git workflows** -- Onboard apps to GitHub, manage branches, PRs, and releases from the CLI.
56
79
 
57
- ```bash
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
- ### Install as Claude Code MCP server
82
+ See the [CLI reference](https://fleet.hesketh.pro/cli/) for the complete command list.
66
83
 
67
- ```bash
68
- sudo fleet install-mcp
69
- ```
84
+ ## Secrets Flow
70
85
 
71
- This writes the MCP server config to `~/.claude.json` so all Claude Code sessions can use fleet tools. Alternatively, add manually:
72
-
73
- ```json
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
- ## Usage
91
+ Import --> Encrypt["age encrypt"]
92
+ Set --> Encrypt
85
93
 
86
- Fleet requires root for all commands except `mcp` and `install-mcp`.
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
- ```bash
89
- fleet <command> [options]
99
+ Runtime -->|"fleet secrets seal"| Encrypt
100
+ Vault -.->|"drift detection"| Runtime
90
101
  ```
91
102
 
92
- ### App lifecycle
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
- ```bash
95
- fleet deploy <app-dir> # Register, build, and start (full pipeline)
96
- fleet add <app-dir> # Register an existing app without deploying
97
- fleet remove <app> # Stop, disable, and deregister
98
- fleet init # Auto-discover all existing apps on the server
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
- `deploy` is the primary command -- it registers the app if needed, runs `docker compose build`, and starts/restarts the systemd service.
120
+ Rotation strategies (auto-detected from secret name, see `src/core/secrets-providers.ts`):
102
121
 
103
- ### Service control
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
- ```bash
106
- fleet start <app> # Start via systemctl
107
- fleet stop <app> # Stop via systemctl
108
- fleet restart <app> # Restart via systemctl
109
- fleet logs <app> [-f] # Container logs (follow mode with -f)
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
- ### Monitoring
138
+ ### Log lifecycle (v1.6)
113
139
 
114
- ```bash
115
- fleet status # Dashboard: all apps, systemd state, containers, health
116
- fleet list [--json] # List registered apps
117
- fleet health [app] # Health checks: systemd + container + HTTP
118
- fleet watchdog # Check all services, send Telegram alert on failure
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
- `watchdog` is designed to run on a cron schedule. It checks systemd unit status, container state, and HTTP health endpoints, then sends a Telegram alert if anything is unhealthy. Configure Telegram credentials at `/etc/fleet/telegram.json`:
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
- ```json
124
- {
125
- "botToken": "123456:ABC-DEF...",
126
- "chatId": "-100..."
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
- ### Dependency health
157
+ ### Egress observation (v1.6)
131
158
 
132
- ```bash
133
- fleet deps [app] # Summary dashboard or per-app detail
134
- fleet deps scan # Run fresh dependency scan
135
- fleet deps fix <app> [--dry-run] # Create PR for fixable dependency updates
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
- Scans all registered apps for outdated packages (npm, Composer, pip), Docker image updates, runtime EOL warnings (via endoflife.date), security vulnerabilities (via OSV API), and open dependency PRs on GitHub. Results are cached and surfaced via CLI, MOTD on SSH login, and Telegram notifications. Runs automatically every 6 hours via cron (configurable).
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
- ### Nginx management
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
- ```bash
146
- fleet nginx add <domain> --port <port> [--type proxy|spa|nextjs]
147
- fleet nginx remove <domain>
148
- fleet nginx list
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
- Generates an nginx server block, writes it to `/etc/nginx/sites-available/`, symlinks to `sites-enabled/`, tests the config, and reloads nginx. Supports three config types:
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
- - **proxy** -- reverse proxy to a backend port (default)
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
- ### Secrets management
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
- Fleet uses [age](https://github.com/FiloSottile/age) encryption for secrets at rest. Each app's secrets (`.env` files or secret directories) are encrypted as `.age` files in the `vault/` directory. On boot, a systemd oneshot service decrypts everything to `/run/fleet-secrets/` (tmpfs -- never touches disk).
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
- fleet secrets init # Create age keypair, install unseal service
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
- Two secret types are supported:
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
- #### Vault safety features
208
+ **Registry field: `lastBuiltCommit`**
182
209
 
183
- All seal operations are protected with:
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
- ### Git and GitHub
212
+ **First boot after upgrade**
190
213
 
191
- Fleet can onboard apps to GitHub and manage their full Git workflow. All GitHub operations use `gh` CLI over HTTPS.
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
- ```bash
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
- The `onboard` command handles everything: initialises git if needed, creates a private GitHub repo, pushes `main` and `develop` branches, and sets up branch protection rules.
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
- ### Global flags
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
- ## MCP Server
229
+ ```bash
230
+ sudo fleet install-mcp
231
+ ```
218
232
 
219
- Running `fleet mcp` starts a stdio-based [Model Context Protocol](https://modelcontextprotocol.io/) server. This exposes all fleet operations as tools that Claude Code (or any MCP client) can call.
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 Telegram bot (`bot/`) that provides remote server management through chat. It runs Claude Code sessions, giving Claude access to fleet's MCP tools for hands-free operations.
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
- Built and deployed separately:
239
+ See the [bot documentation](https://fleet.hesketh.pro/bot/setup/) for setup instructions.
265
240
 
266
- ```bash
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
- ## Project structure
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
- src/
277
- ├── index.ts Entry point (detects "mcp" arg)
278
- ├── cli.ts CLI router and help text
279
- ├── commands/ CLI command implementations
280
- │ ├── add.ts Register an app
281
- │ ├── deploy.ts Full deploy pipeline
282
- │ ├── deps.ts Dependency health monitoring
283
- │ ├── git.ts Git/GitHub operations
284
- │ ├── health.ts Health checks
285
- │ ├── init.ts Auto-discover apps
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
- npm run dev # Run with tsx (no build needed)
335
- npm run build # Compile TypeScript to dist/
336
- npm test # Run tests with vitest
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,2 @@
1
+ export { createStdoutNotifier } from './stdout.js';
2
+ export { createWebhookNotifier, type WebhookOptions } from './webhook.js';
@@ -0,0 +1,2 @@
1
+ export { createStdoutNotifier } from './stdout.js';
2
+ export { createWebhookNotifier } from './webhook.js';
@@ -0,0 +1,2 @@
1
+ import type { NotifierAdapter } from '../types.js';
2
+ export declare function createStdoutNotifier(): NotifierAdapter;
@@ -0,0 +1,8 @@
1
+ export function createStdoutNotifier() {
2
+ return {
3
+ id: 'stdout',
4
+ async notify(subject, body) {
5
+ process.stdout.write(`\n${subject}\n${body}\n`);
6
+ },
7
+ };
8
+ }
@@ -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;