@meshxdata/fops 0.1.36 → 0.1.37

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.
@@ -0,0 +1,254 @@
1
+ /**
2
+ * Fleet management, swarm, audit, snapshot/restore commands.
3
+ * All operations target multiple Azure VMs or the swarm cluster.
4
+ */
5
+ import chalk from "chalk";
6
+
7
+ export function registerFleetCommands(azure) {
8
+ // ── Fleet management ───────────────────────────────────────────────────
9
+
10
+ azure
11
+ .command("exec <command>")
12
+ .description("Run a command on all tracked VMs in parallel")
13
+ .option("--vm-name <name>", "Target a specific VM instead of all")
14
+ .option("--timeout <seconds>", "Per-VM command timeout (default: 120)", "120")
15
+ .option("--quiet", "Show only summary, not command output")
16
+ .action(async (command, opts) => {
17
+ const { fleetExec } = await import("../azure-fleet.js");
18
+ await fleetExec(command, { vmName: opts.vmName, timeout: Number(opts.timeout), quiet: opts.quiet });
19
+ });
20
+
21
+ azure
22
+ .command("diff")
23
+ .description("Compare configuration across all VMs — detect drift")
24
+ .option("--vm-name <name>", "Target a specific VM instead of all")
25
+ .action(async (opts) => {
26
+ const { fleetDiff } = await import("../azure-fleet.js");
27
+ await fleetDiff({ vmName: opts.vmName });
28
+ });
29
+
30
+ azure
31
+ .command("rollout")
32
+ .description("Rolling deploy: pull, restart, health-check across VMs in batches")
33
+ .option("--vm-name <name>", "Target a specific VM instead of all")
34
+ .option("--batch <size>", "Number of VMs to deploy in parallel per batch (default: 1)", "1")
35
+ .option("--branch <branch>", "Git branch to deploy (default: main)")
36
+ .option("--health-timeout <seconds>", "Seconds to wait for healthy after restart (default: 120)", "120")
37
+ .option("--force", "Continue rolling out even if a batch fails")
38
+ .action(async (opts) => {
39
+ const { fleetRollout } = await import("../azure-fleet.js");
40
+ await fleetRollout({
41
+ vmName: opts.vmName, batch: Number(opts.batch), branch: opts.branch,
42
+ healthTimeout: Number(opts.healthTimeout), force: opts.force,
43
+ });
44
+ });
45
+
46
+ azure
47
+ .command("sync")
48
+ .description("Push local config files to all VMs")
49
+ .option("--vm-name <name>", "Target a specific VM instead of all")
50
+ .option("--files <files>", "Comma-separated list of files to sync (default: docker-compose.yaml,.env)", "docker-compose.yaml,.env")
51
+ .option("--restart", "Run fops up after syncing files")
52
+ .action(async (opts) => {
53
+ const { fleetSync } = await import("../azure-fleet.js");
54
+ await fleetSync({ vmName: opts.vmName, files: opts.files.split(","), restart: opts.restart });
55
+ });
56
+
57
+ azure
58
+ .command("health")
59
+ .description("Deep health report across all VMs — containers, disk, memory, load")
60
+ .option("--vm-name <name>", "Target a specific VM instead of all")
61
+ .action(async (opts) => {
62
+ const { fleetHealth } = await import("../azure-fleet.js");
63
+ await fleetHealth({ vmName: opts.vmName });
64
+ });
65
+
66
+ azure
67
+ .command("snapshot [name]")
68
+ .description("Create Azure disk snapshots of all (or one) tracked VMs")
69
+ .option("--vm-name <name>", "Target a specific VM instead of all")
70
+ .option("--tag <tag>", "Snapshot tag/label (default: ISO timestamp)")
71
+ .option("--profile <subscription>", "Azure subscription name or ID")
72
+ .action(async (name, opts) => {
73
+ const { fleetSnapshot } = await import("../azure-fleet.js");
74
+ await fleetSnapshot({ vmName: opts.vmName || name, tag: opts.tag, profile: opts.profile });
75
+ });
76
+
77
+ azure
78
+ .command("restore <name>")
79
+ .description("Restore a VM from an Azure disk snapshot")
80
+ .option("--snapshot <name>", "Snapshot name to restore from (omit to list available)")
81
+ .option("--profile <subscription>", "Azure subscription name or ID")
82
+ .option("--yes", "Skip confirmation prompt")
83
+ .action(async (name, opts) => {
84
+ const { fleetRestore } = await import("../azure-fleet.js");
85
+ await fleetRestore({ vmName: name, snapshot: opts.snapshot, profile: opts.profile, yes: opts.yes });
86
+ });
87
+
88
+ // ── Audit subcommands ──────────────────────────────────────────────────
89
+
90
+ const audit = azure
91
+ .command("audit")
92
+ .description("Security & compliance audit across Azure resources");
93
+
94
+ audit
95
+ .command("all", { isDefault: true })
96
+ .description("Run all audit checks (VMs, AKS, storage encryption)")
97
+ .option("--profile <subscription>", "Azure subscription name or ID")
98
+ .option("--vm-name <name>", "Limit VM audit to a specific VM")
99
+ .option("--cluster <name>", "Limit AKS audit to a specific cluster")
100
+ .option("--account <name>", "Limit storage audit to a specific account")
101
+ .option("--verbose", "Show info-level suggestions alongside warnings")
102
+ .action(async (opts) => {
103
+ const { auditAll } = await import("../azure-audit.js");
104
+ await auditAll({ profile: opts.profile, vmName: opts.vmName, clusterName: opts.cluster, account: opts.account, verbose: opts.verbose });
105
+ });
106
+
107
+ audit
108
+ .command("vm [vmName]")
109
+ .description("Audit VM security — disk encryption, NSG rules, managed identity, patching")
110
+ .option("--profile <subscription>", "Azure subscription name or ID")
111
+ .option("--vm-name <name>", "Audit a specific VM (default: all tracked)")
112
+ .option("--verbose", "Show info-level suggestions alongside warnings")
113
+ .action(async (vmName, opts) => {
114
+ const { auditVms } = await import("../azure-audit.js");
115
+ await auditVms({ profile: opts.profile, vmName: opts.vmName || vmName, verbose: opts.verbose });
116
+ });
117
+
118
+ audit
119
+ .command("aks [clusterName]")
120
+ .description("Audit AKS cluster security — RBAC, network policy, auto-upgrade, Defender")
121
+ .option("--profile <subscription>", "Azure subscription name or ID")
122
+ .option("--verbose", "Show info-level suggestions alongside warnings")
123
+ .action(async (clusterName, opts) => {
124
+ const { auditAks } = await import("../azure-audit.js");
125
+ await auditAks({ profile: opts.profile, clusterName, verbose: opts.verbose });
126
+ });
127
+
128
+ audit
129
+ .command("storage")
130
+ .description("Audit storage account encryption & security posture")
131
+ .option("--profile <subscription>", "Azure subscription name or ID")
132
+ .option("--account <name>", "Audit a specific storage account (default: all)")
133
+ .option("--verbose", "Show info-level suggestions alongside warnings")
134
+ .action(async (opts) => {
135
+ const { auditStorage } = await import("../azure-audit.js");
136
+ await auditStorage({ profile: opts.profile, account: opts.account, verbose: opts.verbose });
137
+ });
138
+
139
+ audit
140
+ .command("sessions [vmName]")
141
+ .description("View SSH session recordings across the fleet")
142
+ .option("--session <id>", "Read a specific session by ID")
143
+ .option("--live", "Show only active (live) sessions")
144
+ .option("--cloud", "Read from blob storage instead of SSH")
145
+ .option("--push", "Push collected sessions to blob storage")
146
+ .option("--last <n>", "Show only the last N sessions", "50")
147
+ .option("--tail <n>", "When reading a session, show only the last N lines")
148
+ .action(async (vmName, opts) => {
149
+ const { fleetAudit } = await import("../azure-fleet.js");
150
+ await fleetAudit({
151
+ vmName,
152
+ session: opts.session,
153
+ live: opts.live,
154
+ cloud: opts.cloud,
155
+ push: opts.push,
156
+ last: Number(opts.last),
157
+ tail: opts.tail ? Number(opts.tail) : undefined,
158
+ });
159
+ });
160
+
161
+ audit
162
+ .command("zap [vmName]")
163
+ .description("Run an authenticated OWASP ZAP DAST scan against a VM's frontend")
164
+ .option("--vm-name <name>", "Target VM (default: active)")
165
+ .option("--target <url>", "Override target URL (default: https://<vm>.meshx.app)")
166
+ .option("--output <dir>", "Directory for JSON report (default: cwd)")
167
+ .option("--spider-minutes <n>", "Spider duration in minutes (default: 3)", "3")
168
+ .option("--ajax-minutes <n>", "Ajax spider duration in minutes (default: 3)", "3")
169
+ .option("--active-scan-minutes <n>", "Active scan rule timeout in minutes (default: 5)", "5")
170
+ .option("--max-minutes <n>", "Overall scan timeout in minutes (default: 20)", "20")
171
+ .option("--verbose", "Show fix suggestions for each finding")
172
+ .option("--aggressive", "Pentest mode: Penetration Tester policy, longer crawl/scan")
173
+ .action(async (vmName, opts) => {
174
+ const { auditZap } = await import("../azure-audit.js");
175
+ await auditZap({
176
+ vmName: opts.vmName || vmName,
177
+ target: opts.target,
178
+ output: opts.output,
179
+ spiderMinutes: opts.aggressive ? undefined : (opts.spiderMinutes ? Number(opts.spiderMinutes) : undefined),
180
+ ajaxMinutes: opts.aggressive ? undefined : (opts.ajaxMinutes ? Number(opts.ajaxMinutes) : undefined),
181
+ activeScanMinutes: opts.aggressive ? undefined : (opts.activeScanMinutes ? Number(opts.activeScanMinutes) : undefined),
182
+ maxMinutes: opts.aggressive ? undefined : (opts.maxMinutes ? Number(opts.maxMinutes) : undefined),
183
+ verbose: opts.verbose,
184
+ aggressive: opts.aggressive,
185
+ });
186
+ });
187
+
188
+ // ── Swarm subcommands ──────────────────────────────────────────────────
189
+
190
+ const swarm = azure
191
+ .command("swarm")
192
+ .description("Docker Swarm cluster management");
193
+
194
+ swarm
195
+ .command("init <vmName>")
196
+ .description("Initialize Docker Swarm on a VM (single-node manager)")
197
+ .option("--stack", "Also deploy the compose stack as swarm services")
198
+ .action(async (vmName, opts) => {
199
+ const { swarmInit } = await import("../azure-fleet.js");
200
+ await swarmInit({ vmName, stack: opts.stack });
201
+ });
202
+
203
+ swarm
204
+ .command("join <vmName>")
205
+ .description("Join a VM as a worker to an existing swarm (auto-creates the VM if it doesn't exist)")
206
+ .requiredOption("--manager <name>", "Manager VM to join")
207
+ .option("--as-manager", "Join as a manager instead of worker")
208
+ .option("--vm-size <size>", "VM size when auto-creating (default: inherited from manager)")
209
+ .option("--location <region>", "Azure region when auto-creating (default: inherited from manager)")
210
+ .option("--image <urn>", "Custom image URN when auto-creating")
211
+ .option("--url <url>", "Public URL override when auto-creating")
212
+ .option("--profile <subscription>", "Azure subscription when auto-creating")
213
+ .action(async (vmName, opts) => {
214
+ const { swarmJoin } = await import("../azure-fleet.js");
215
+ await swarmJoin({
216
+ vmName, manager: opts.manager, asManager: opts.asManager,
217
+ vmSize: opts.vmSize, location: opts.location,
218
+ image: opts.image, url: opts.url, profile: opts.profile,
219
+ });
220
+ });
221
+
222
+ swarm
223
+ .command("status [vmName]")
224
+ .description("Show swarm node and service status")
225
+ .action(async (vmName) => {
226
+ const { swarmStatus } = await import("../azure-fleet.js");
227
+ await swarmStatus({ vmName });
228
+ });
229
+
230
+ swarm
231
+ .command("promote <vmName>")
232
+ .description("Promote a swarm worker to manager")
233
+ .action(async (vmName) => {
234
+ const { swarmPromote } = await import("../azure-fleet.js");
235
+ await swarmPromote({ vmName });
236
+ });
237
+
238
+ swarm
239
+ .command("deploy [vmName]")
240
+ .description("Deploy the compose stack as swarm services (or update existing)")
241
+ .action(async (vmName) => {
242
+ const { swarmDeploy } = await import("../azure-fleet.js");
243
+ await swarmDeploy({ vmName });
244
+ });
245
+
246
+ swarm
247
+ .command("leave <vmName>")
248
+ .description("Remove a VM from the swarm")
249
+ .option("--force", "Force leave (required for managers)")
250
+ .action(async (vmName, opts) => {
251
+ const { swarmLeave } = await import("../azure-fleet.js");
252
+ await swarmLeave({ vmName, force: opts.force });
253
+ });
254
+ }