@celilo/cli 0.1.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 +1566 -0
- package/bin/celilo +16 -0
- package/drizzle/0000_complex_puma.sql +179 -0
- package/drizzle/0001_dizzy_wolfpack.sql +2 -0
- package/drizzle/0002_web_routes.sql +16 -0
- package/drizzle/0003_backup_storage.sql +32 -0
- package/drizzle/meta/0000_snapshot.json +1151 -0
- package/drizzle/meta/0001_snapshot.json +1167 -0
- package/drizzle/meta/0002_snapshot.json +1257 -0
- package/drizzle/meta/_journal.json +27 -0
- package/package.json +64 -0
- package/schemas/system_config.json +106 -0
- package/src/__integration__/container-services-cli.integration.test.ts +246 -0
- package/src/ansible/dependencies.test.ts +309 -0
- package/src/ansible/dependencies.ts +896 -0
- package/src/ansible/inventory.test.ts +463 -0
- package/src/ansible/inventory.ts +445 -0
- package/src/ansible/secrets.ts +222 -0
- package/src/ansible/validation.test.ts +92 -0
- package/src/ansible/validation.ts +272 -0
- package/src/api-clients/digitalocean.ts +94 -0
- package/src/api-clients/proxmox.ts +655 -0
- package/src/capabilities/logging-wrapper.test.ts +217 -0
- package/src/capabilities/lookup.test.ts +149 -0
- package/src/capabilities/lookup.ts +89 -0
- package/src/capabilities/public-web-helpers.test.ts +198 -0
- package/src/capabilities/public-web-publish.test.ts +458 -0
- package/src/capabilities/registration.test.ts +395 -0
- package/src/capabilities/registration.ts +200 -0
- package/src/capabilities/route-validation.test.ts +121 -0
- package/src/capabilities/route-validation.ts +96 -0
- package/src/capabilities/secret-ref.test.ts +313 -0
- package/src/capabilities/secret-validation.ts +157 -0
- package/src/capabilities/secrets.test.ts +750 -0
- package/src/capabilities/secrets.ts +244 -0
- package/src/capabilities/validation.test.ts +613 -0
- package/src/capabilities/validation.ts +160 -0
- package/src/capabilities/well-known.test.ts +238 -0
- package/src/capabilities/well-known.ts +222 -0
- package/src/cli/cli.test.ts +654 -0
- package/src/cli/command-registry.ts +742 -0
- package/src/cli/command-tree-parser.test.ts +180 -0
- package/src/cli/command-tree-parser.ts +193 -0
- package/src/cli/commands/backup-create.ts +137 -0
- package/src/cli/commands/backup-delete.ts +74 -0
- package/src/cli/commands/backup-import.ts +97 -0
- package/src/cli/commands/backup-list.ts +132 -0
- package/src/cli/commands/backup-name.ts +73 -0
- package/src/cli/commands/backup-prune.ts +98 -0
- package/src/cli/commands/backup-restore.ts +122 -0
- package/src/cli/commands/capability-info.ts +121 -0
- package/src/cli/commands/capability-list.ts +47 -0
- package/src/cli/commands/completion.ts +87 -0
- package/src/cli/commands/hook-run.ts +176 -0
- package/src/cli/commands/ipam.ts +607 -0
- package/src/cli/commands/machine-add.ts +235 -0
- package/src/cli/commands/machine-earmark.ts +82 -0
- package/src/cli/commands/machine-list.ts +77 -0
- package/src/cli/commands/machine-remove.ts +90 -0
- package/src/cli/commands/machine-status.ts +131 -0
- package/src/cli/commands/module-audit.ts +51 -0
- package/src/cli/commands/module-build.ts +60 -0
- package/src/cli/commands/module-config.ts +170 -0
- package/src/cli/commands/module-deploy.ts +71 -0
- package/src/cli/commands/module-generate.ts +236 -0
- package/src/cli/commands/module-health.ts +108 -0
- package/src/cli/commands/module-import.ts +80 -0
- package/src/cli/commands/module-list.ts +43 -0
- package/src/cli/commands/module-logs.ts +73 -0
- package/src/cli/commands/module-remove.ts +162 -0
- package/src/cli/commands/module-show.ts +208 -0
- package/src/cli/commands/module-status.ts +131 -0
- package/src/cli/commands/module-types.ts +189 -0
- package/src/cli/commands/module-upgrade.ts +192 -0
- package/src/cli/commands/package.ts +68 -0
- package/src/cli/commands/secret-list.ts +99 -0
- package/src/cli/commands/secret-set.ts +134 -0
- package/src/cli/commands/service-add-digitalocean.ts +133 -0
- package/src/cli/commands/service-add-proxmox.ts +342 -0
- package/src/cli/commands/service-config-get.ts +83 -0
- package/src/cli/commands/service-config-set.ts +145 -0
- package/src/cli/commands/service-list.ts +74 -0
- package/src/cli/commands/service-reconfigure.ts +230 -0
- package/src/cli/commands/service-remove.ts +103 -0
- package/src/cli/commands/service-verify.ts +240 -0
- package/src/cli/commands/status.ts +216 -0
- package/src/cli/commands/storage-add-local.ts +106 -0
- package/src/cli/commands/storage-add-s3.ts +114 -0
- package/src/cli/commands/storage-list.ts +72 -0
- package/src/cli/commands/storage-remove.ts +54 -0
- package/src/cli/commands/storage-set-default.ts +44 -0
- package/src/cli/commands/storage-verify.ts +54 -0
- package/src/cli/commands/system-config.ts +168 -0
- package/src/cli/commands/system-init.ts +314 -0
- package/src/cli/commands/system-secret-get.ts +98 -0
- package/src/cli/commands/system-secret-set.ts +76 -0
- package/src/cli/commands/system-vault-password.ts +34 -0
- package/src/cli/completion.test.ts +37 -0
- package/src/cli/completion.ts +482 -0
- package/src/cli/fuel-gauge.test.ts +208 -0
- package/src/cli/fuel-gauge.ts +405 -0
- package/src/cli/generate-zsh-completion.test.ts +95 -0
- package/src/cli/generate-zsh-completion.ts +497 -0
- package/src/cli/index.ts +1583 -0
- package/src/cli/interactive-config.test.ts +201 -0
- package/src/cli/interactive-config.ts +62 -0
- package/src/cli/parser.test.ts +227 -0
- package/src/cli/parser.ts +244 -0
- package/src/cli/prompts.test.ts +33 -0
- package/src/cli/prompts.ts +121 -0
- package/src/cli/types.ts +38 -0
- package/src/cli/validators.test.ts +235 -0
- package/src/cli/validators.ts +188 -0
- package/src/config/env.ts +41 -0
- package/src/config/paths.test.ts +172 -0
- package/src/config/paths.ts +108 -0
- package/src/db/client.ts +190 -0
- package/src/db/migrate.ts +30 -0
- package/src/db/schema.test.ts +221 -0
- package/src/db/schema.ts +434 -0
- package/src/hooks/capability-loader-firewall.test.ts +246 -0
- package/src/hooks/capability-loader.test.ts +100 -0
- package/src/hooks/capability-loader.ts +520 -0
- package/src/hooks/define-hook.test.ts +488 -0
- package/src/hooks/executor.test.ts +462 -0
- package/src/hooks/executor.ts +469 -0
- package/src/hooks/logger.test.ts +54 -0
- package/src/hooks/logger.ts +95 -0
- package/src/hooks/test-fixtures/failing-hook.ts +13 -0
- package/src/hooks/test-fixtures/no-default-hook.ts +6 -0
- package/src/hooks/test-fixtures/success-hook.ts +20 -0
- package/src/hooks/test-fixtures/unbranded-hook.ts +11 -0
- package/src/hooks/test-fixtures/void-hook.ts +13 -0
- package/src/hooks/types.ts +89 -0
- package/src/infrastructure/property-extractor.test.ts +194 -0
- package/src/infrastructure/property-extractor.ts +151 -0
- package/src/ipam/allocator.test.ts +442 -0
- package/src/ipam/allocator.ts +369 -0
- package/src/ipam/auto-allocator.test.ts +247 -0
- package/src/ipam/auto-allocator.ts +270 -0
- package/src/ipam/subnet-parser.test.ts +107 -0
- package/src/ipam/subnet-parser.ts +136 -0
- package/src/manifest/contracts/index.ts +61 -0
- package/src/manifest/contracts/v1.ts +118 -0
- package/src/manifest/json-schema-roundtrip.test.ts +99 -0
- package/src/manifest/schema.ts +367 -0
- package/src/manifest/template-validator.test.ts +231 -0
- package/src/manifest/template-validator.ts +322 -0
- package/src/manifest/validate.test.ts +1180 -0
- package/src/manifest/validate.ts +415 -0
- package/src/module/import.test.ts +355 -0
- package/src/module/import.ts +676 -0
- package/src/module/packaging/audit.ts +169 -0
- package/src/module/packaging/build.ts +228 -0
- package/src/module/packaging/checksum.ts +41 -0
- package/src/module/packaging/extract.ts +234 -0
- package/src/module/packaging/signature.ts +47 -0
- package/src/secrets/encryption.test.ts +284 -0
- package/src/secrets/encryption.ts +162 -0
- package/src/secrets/generators.test.ts +112 -0
- package/src/secrets/generators.ts +127 -0
- package/src/secrets/master-key.test.ts +159 -0
- package/src/secrets/master-key.ts +114 -0
- package/src/secrets/storage.test.ts +115 -0
- package/src/secrets/storage.ts +106 -0
- package/src/secrets/vault.test.ts +35 -0
- package/src/secrets/vault.ts +42 -0
- package/src/services/backup-create.ts +532 -0
- package/src/services/backup-metadata.ts +198 -0
- package/src/services/backup-restore.ts +229 -0
- package/src/services/backup-retention.ts +84 -0
- package/src/services/backup-storage.ts +281 -0
- package/src/services/build-stream.test.ts +122 -0
- package/src/services/build-stream.ts +201 -0
- package/src/services/config-interview.ts +694 -0
- package/src/services/container-service.test.ts +298 -0
- package/src/services/container-service.ts +401 -0
- package/src/services/cross-module-data-manager.test.ts +405 -0
- package/src/services/cross-module-data-manager.ts +412 -0
- package/src/services/deploy-ansible.ts +88 -0
- package/src/services/deploy-planner.ts +153 -0
- package/src/services/deploy-preflight.ts +274 -0
- package/src/services/deploy-ssh.ts +131 -0
- package/src/services/deploy-terraform.test.ts +55 -0
- package/src/services/deploy-terraform.ts +445 -0
- package/src/services/deploy-validation.ts +311 -0
- package/src/services/dns-auto-register.ts +211 -0
- package/src/services/health-runner.ts +184 -0
- package/src/services/infrastructure-selector.test.ts +485 -0
- package/src/services/infrastructure-selector.ts +245 -0
- package/src/services/infrastructure-variable-resolver.test.ts +751 -0
- package/src/services/infrastructure-variable-resolver.ts +234 -0
- package/src/services/machine-detector.ts +328 -0
- package/src/services/machine-pool.test.ts +405 -0
- package/src/services/machine-pool.ts +316 -0
- package/src/services/manifest-validation.ts +120 -0
- package/src/services/module-build.test.ts +290 -0
- package/src/services/module-build.ts +431 -0
- package/src/services/module-config.test.ts +237 -0
- package/src/services/module-config.ts +298 -0
- package/src/services/module-deploy.ts +862 -0
- package/src/services/module-types-drift.test.ts +73 -0
- package/src/services/module-types-generator.test.ts +288 -0
- package/src/services/module-types-generator.ts +189 -0
- package/src/services/proxmox-state-recovery.ts +140 -0
- package/src/services/schema-validation.ts +155 -0
- package/src/services/secret-schema-loader.test.ts +311 -0
- package/src/services/secret-schema-loader.ts +239 -0
- package/src/services/ssh-key-manager.test.ts +283 -0
- package/src/services/ssh-key-manager.ts +193 -0
- package/src/services/storage-providers/local.ts +105 -0
- package/src/services/storage-providers/s3.ts +182 -0
- package/src/services/storage-providers/types.ts +24 -0
- package/src/services/system-config-schema-types.ts +25 -0
- package/src/services/system-config-validator.test.ts +160 -0
- package/src/services/system-config-validator.ts +74 -0
- package/src/services/system-init.test.ts +153 -0
- package/src/services/system-init.ts +253 -0
- package/src/services/terraform-safety.ts +174 -0
- package/src/services/zone-detector.test.ts +110 -0
- package/src/services/zone-detector.ts +102 -0
- package/src/services/zone-policy.test.ts +97 -0
- package/src/services/zone-policy.ts +126 -0
- package/src/templates/generator.test.ts +645 -0
- package/src/templates/generator.ts +1119 -0
- package/src/templates/types.ts +62 -0
- package/src/test-utils/INTERACTIVE_PROMPTS.md +167 -0
- package/src/test-utils/cli-context-interactive.test.ts +152 -0
- package/src/test-utils/cli-context-server.test.ts +66 -0
- package/src/test-utils/cli-context.test.ts +273 -0
- package/src/test-utils/cli-context.ts +677 -0
- package/src/test-utils/cli-result.test.ts +282 -0
- package/src/test-utils/cli-result.ts +241 -0
- package/src/test-utils/cli.ts +55 -0
- package/src/test-utils/completion-harness.test.ts +126 -0
- package/src/test-utils/completion-harness.ts +82 -0
- package/src/test-utils/database.test.ts +182 -0
- package/src/test-utils/database.ts +126 -0
- package/src/test-utils/filesystem.test.ts +208 -0
- package/src/test-utils/filesystem.ts +142 -0
- package/src/test-utils/fixtures.test.ts +123 -0
- package/src/test-utils/fixtures.ts +160 -0
- package/src/test-utils/golden-diff.ts +197 -0
- package/src/test-utils/index.ts +77 -0
- package/src/test-utils/integration.ts +81 -0
- package/src/test-utils/module-fixtures.ts +468 -0
- package/src/test-utils/modules.test.ts +144 -0
- package/src/test-utils/modules.ts +183 -0
- package/src/test-utils/setup-test-db.ts +90 -0
- package/src/test-utils/value-extractor.test.ts +231 -0
- package/src/test-utils/value-extractor.ts +228 -0
- package/src/types/infrastructure.ts +157 -0
- package/src/utils/shell.test.ts +365 -0
- package/src/utils/shell.ts +159 -0
- package/src/validation/schemas.ts +166 -0
- package/src/variables/ansible-resolver.test.ts +142 -0
- package/src/variables/ansible-resolver.ts +69 -0
- package/src/variables/capability-self-ref.test.ts +220 -0
- package/src/variables/context.test.ts +1265 -0
- package/src/variables/context.ts +624 -0
- package/src/variables/declarative-derivation.test.ts +743 -0
- package/src/variables/declarative-derivation.ts +200 -0
- package/src/variables/parser.test.ts +231 -0
- package/src/variables/parser.ts +76 -0
- package/src/variables/resolver.test.ts +458 -0
- package/src/variables/resolver.ts +282 -0
- package/src/variables/types.ts +59 -0
package/src/cli/index.ts
ADDED
|
@@ -0,0 +1,1583 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI Entry Point
|
|
3
|
+
*
|
|
4
|
+
* Orchestration function (Rule 10.1) - routes commands to handlers
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import * as p from '@clack/prompts';
|
|
8
|
+
import { CLIServerRequestSchema, parseJsonWithValidation } from '../validation/schemas';
|
|
9
|
+
import { COMMANDS, type CommandDef } from './command-registry';
|
|
10
|
+
import { handleCapabilityInfo } from './commands/capability-info';
|
|
11
|
+
import { handleCapabilityList } from './commands/capability-list';
|
|
12
|
+
import { handleCompletion } from './commands/completion';
|
|
13
|
+
import { handleHookRun } from './commands/hook-run';
|
|
14
|
+
import {
|
|
15
|
+
handleIpamIpListReservations,
|
|
16
|
+
handleIpamIpReserve,
|
|
17
|
+
handleIpamIpUnreserve,
|
|
18
|
+
handleIpamListAllocations,
|
|
19
|
+
handleIpamShow,
|
|
20
|
+
handleIpamVmidListReservations,
|
|
21
|
+
handleIpamVmidReserve,
|
|
22
|
+
handleIpamVmidUnreserve,
|
|
23
|
+
} from './commands/ipam';
|
|
24
|
+
import { handleMachineAdd } from './commands/machine-add';
|
|
25
|
+
import { handleMachineEarmark } from './commands/machine-earmark';
|
|
26
|
+
import { handleMachineList } from './commands/machine-list';
|
|
27
|
+
import { handleMachineRemove } from './commands/machine-remove';
|
|
28
|
+
import { handleMachineStatus } from './commands/machine-status';
|
|
29
|
+
import { moduleAudit } from './commands/module-audit';
|
|
30
|
+
import { handleModuleBuild } from './commands/module-build';
|
|
31
|
+
import { handleModuleConfigGet, handleModuleConfigSet } from './commands/module-config';
|
|
32
|
+
import { handleModuleDeploy } from './commands/module-deploy';
|
|
33
|
+
import { handleModuleGenerate } from './commands/module-generate';
|
|
34
|
+
import { handleModuleHealth } from './commands/module-health';
|
|
35
|
+
import { handleModuleImport } from './commands/module-import';
|
|
36
|
+
import { handleModuleList } from './commands/module-list';
|
|
37
|
+
import { handleModuleLogs } from './commands/module-logs';
|
|
38
|
+
import { handleModuleRemove } from './commands/module-remove';
|
|
39
|
+
import { handleModuleShowConfig, handleModuleShowZone } from './commands/module-show';
|
|
40
|
+
import { handleModuleStatus } from './commands/module-status';
|
|
41
|
+
import { handleModuleTypesCheck, handleModuleTypesGenerate } from './commands/module-types';
|
|
42
|
+
import { handleModuleUpgrade } from './commands/module-upgrade';
|
|
43
|
+
import { handlePackage } from './commands/package';
|
|
44
|
+
import { handleSecretList } from './commands/secret-list';
|
|
45
|
+
import { handleSecretSet } from './commands/secret-set';
|
|
46
|
+
import { handleServiceAddDigitalOcean } from './commands/service-add-digitalocean';
|
|
47
|
+
import { handleServiceAddProxmox } from './commands/service-add-proxmox';
|
|
48
|
+
import { handleServiceConfigGet } from './commands/service-config-get';
|
|
49
|
+
import { handleServiceConfigSet } from './commands/service-config-set';
|
|
50
|
+
import { handleServiceList } from './commands/service-list';
|
|
51
|
+
import { handleServiceReconfigure } from './commands/service-reconfigure';
|
|
52
|
+
import { handleServiceRemove } from './commands/service-remove';
|
|
53
|
+
import { handleServiceVerify } from './commands/service-verify';
|
|
54
|
+
import { handleStatus } from './commands/status';
|
|
55
|
+
import { handleSystemConfigGet, handleSystemConfigSet } from './commands/system-config';
|
|
56
|
+
import { handleSystemInit } from './commands/system-init';
|
|
57
|
+
import { handleSystemSecretGet } from './commands/system-secret-get';
|
|
58
|
+
import { handleSystemSecretSet } from './commands/system-secret-set';
|
|
59
|
+
import { handleSystemVaultPassword } from './commands/system-vault-password';
|
|
60
|
+
import { getCompletions } from './completion';
|
|
61
|
+
import { parseArguments, validateFlags } from './parser';
|
|
62
|
+
import type { CommandResult } from './types';
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Look up a command definition from the registry and validate flags.
|
|
66
|
+
* Walks the full subcommand chain (e.g., ipam -> ip -> reserve) to find
|
|
67
|
+
* the leaf command where flags are defined.
|
|
68
|
+
* Returns an error CommandResult if unknown flags are found, or null if valid.
|
|
69
|
+
*/
|
|
70
|
+
function checkFlags(
|
|
71
|
+
command: string,
|
|
72
|
+
subcommand: string | undefined,
|
|
73
|
+
flags: Record<string, string | boolean>,
|
|
74
|
+
args: string[] = [],
|
|
75
|
+
): CommandResult | null {
|
|
76
|
+
// Skip validation for help requests
|
|
77
|
+
if (flags.help || flags.h) return null;
|
|
78
|
+
|
|
79
|
+
const topDef = COMMANDS.find((c) => c.name === command);
|
|
80
|
+
if (!topDef) return null;
|
|
81
|
+
|
|
82
|
+
let commandDef: CommandDef | undefined = subcommand
|
|
83
|
+
? topDef.subcommands?.find((s) => s.name === subcommand)
|
|
84
|
+
: topDef;
|
|
85
|
+
if (!commandDef) return null;
|
|
86
|
+
|
|
87
|
+
// Walk deeper into nested subcommands using args
|
|
88
|
+
// e.g., for "ipam ip reserve", subcommand='ip' and args=['reserve', ...]
|
|
89
|
+
// We need to find the 'reserve' sub-subcommand to get its flags
|
|
90
|
+
for (const arg of args) {
|
|
91
|
+
const deeper: CommandDef | undefined = commandDef?.subcommands?.find((s) => s.name === arg);
|
|
92
|
+
if (!deeper) break;
|
|
93
|
+
commandDef = deeper;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (!commandDef) return null;
|
|
97
|
+
|
|
98
|
+
const error = validateFlags(flags, commandDef);
|
|
99
|
+
if (error) {
|
|
100
|
+
return { success: false, error };
|
|
101
|
+
}
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Display general help message
|
|
107
|
+
*/
|
|
108
|
+
function displayHelp(): CommandResult {
|
|
109
|
+
const helpText = `
|
|
110
|
+
Celilo - Home Lab Orchestration System
|
|
111
|
+
|
|
112
|
+
Usage:
|
|
113
|
+
celilo <command> [subcommand] [args...] [options]
|
|
114
|
+
|
|
115
|
+
Commands:
|
|
116
|
+
status Show system and module status
|
|
117
|
+
capability View registered module capabilities
|
|
118
|
+
package Create distributable .netapp packages from module source
|
|
119
|
+
module Manage modules (import, list, configure, build, generate)
|
|
120
|
+
service Manage container services (Proxmox, Digital Ocean)
|
|
121
|
+
storage Manage backup storage destinations
|
|
122
|
+
backup Create and manage backups
|
|
123
|
+
machine Manage machine pool (bring-your-own-hardware)
|
|
124
|
+
system Manage system configuration
|
|
125
|
+
ipam Manage IP address and VMID allocations and reservations
|
|
126
|
+
completion Generate shell completion scripts (bash/zsh)
|
|
127
|
+
|
|
128
|
+
help, --help, -h Show this help message
|
|
129
|
+
|
|
130
|
+
For command-specific help:
|
|
131
|
+
celilo package --help
|
|
132
|
+
celilo module --help
|
|
133
|
+
celilo secret --help
|
|
134
|
+
celilo service --help
|
|
135
|
+
celilo machine --help
|
|
136
|
+
celilo system --help
|
|
137
|
+
celilo ipam --help
|
|
138
|
+
|
|
139
|
+
Enable tab completion:
|
|
140
|
+
# Bash
|
|
141
|
+
celilo completion bash >> ~/.bashrc && source ~/.bashrc
|
|
142
|
+
# Zsh
|
|
143
|
+
celilo completion zsh >> ~/.zshrc && source ~/.zshrc
|
|
144
|
+
|
|
145
|
+
Examples:
|
|
146
|
+
celilo package ./modules/homebridge
|
|
147
|
+
celilo module import ./modules/homebridge
|
|
148
|
+
celilo module list
|
|
149
|
+
celilo module build caddy
|
|
150
|
+
celilo module secret set homebridge api_key mykey123
|
|
151
|
+
celilo system config set dns.primary 192.168.0.1
|
|
152
|
+
celilo system vault-password
|
|
153
|
+
`;
|
|
154
|
+
|
|
155
|
+
return {
|
|
156
|
+
success: true,
|
|
157
|
+
message: helpText.trim(),
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Display package command help
|
|
163
|
+
*/
|
|
164
|
+
function displayPackageHelp(): CommandResult {
|
|
165
|
+
const helpText = `
|
|
166
|
+
Celilo - Module Packaging
|
|
167
|
+
|
|
168
|
+
Usage:
|
|
169
|
+
celilo package <source-directory> [options]
|
|
170
|
+
|
|
171
|
+
Description:
|
|
172
|
+
Creates a distributable .netapp package from a module source directory.
|
|
173
|
+
The package includes checksums and a signature for integrity verification.
|
|
174
|
+
|
|
175
|
+
Options:
|
|
176
|
+
--output <path> Output path for the package (default: <source-dir>/<module-id>.netapp)
|
|
177
|
+
|
|
178
|
+
Examples:
|
|
179
|
+
celilo package ./modules/homebridge
|
|
180
|
+
celilo package ./my-module --output /tmp/test.netapp
|
|
181
|
+
celilo package ../custom-module
|
|
182
|
+
|
|
183
|
+
Related Commands:
|
|
184
|
+
celilo module import <package.netapp> Import a packaged module
|
|
185
|
+
celilo module audit <module-id> Verify package integrity
|
|
186
|
+
`;
|
|
187
|
+
|
|
188
|
+
return {
|
|
189
|
+
success: true,
|
|
190
|
+
message: helpText.trim(),
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Display capability command help
|
|
196
|
+
*/
|
|
197
|
+
function displayCapabilityHelp(): CommandResult {
|
|
198
|
+
const helpText = `
|
|
199
|
+
Celilo - Capability Management
|
|
200
|
+
|
|
201
|
+
Usage:
|
|
202
|
+
celilo capability <subcommand> [args...]
|
|
203
|
+
|
|
204
|
+
Subcommands:
|
|
205
|
+
list List all registered capabilities
|
|
206
|
+
info <name> Show detailed capability information
|
|
207
|
+
|
|
208
|
+
Description:
|
|
209
|
+
Capabilities are cross-module data contracts. A module can "provide" a capability
|
|
210
|
+
(e.g., dns_registrar) and other modules can "require" it to access shared data
|
|
211
|
+
and secrets.
|
|
212
|
+
|
|
213
|
+
Capabilities are automatically registered when importing modules that declare
|
|
214
|
+
"provides.capabilities" in their manifest.
|
|
215
|
+
|
|
216
|
+
Examples:
|
|
217
|
+
celilo capability list
|
|
218
|
+
celilo capability info dns_registrar
|
|
219
|
+
celilo capability info public_web
|
|
220
|
+
`;
|
|
221
|
+
|
|
222
|
+
return {
|
|
223
|
+
success: true,
|
|
224
|
+
message: helpText.trim(),
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Display module command help
|
|
230
|
+
*/
|
|
231
|
+
function displayModuleHelp(): CommandResult {
|
|
232
|
+
const helpText = `
|
|
233
|
+
Celilo - Module Management
|
|
234
|
+
|
|
235
|
+
Usage:
|
|
236
|
+
celilo module <subcommand> [args...] [options]
|
|
237
|
+
|
|
238
|
+
Subcommands:
|
|
239
|
+
import <path> Import a module from directory or .netapp file
|
|
240
|
+
Options:
|
|
241
|
+
--target <path> Target base directory (default: platform-specific)
|
|
242
|
+
--auto-generate-secrets Auto-generate all secrets without prompting
|
|
243
|
+
|
|
244
|
+
list List all installed modules
|
|
245
|
+
|
|
246
|
+
remove <id> Remove a module and all its data
|
|
247
|
+
|
|
248
|
+
audit <id> Verify module integrity (checksums)
|
|
249
|
+
|
|
250
|
+
config set <id> <key> <value> Set module configuration value
|
|
251
|
+
config get <id> [key] Get module configuration value(s)
|
|
252
|
+
|
|
253
|
+
secret set <id> <key> <value> Set encrypted module secret
|
|
254
|
+
secret list <id> List module secrets
|
|
255
|
+
|
|
256
|
+
show-config <id> Show all config including auto-derived values
|
|
257
|
+
show-zone <id> Show module's network zone and config
|
|
258
|
+
|
|
259
|
+
build <module-id> Execute build scripts (Nix + Ansible)
|
|
260
|
+
|
|
261
|
+
generate <id> Generate infrastructure code for module
|
|
262
|
+
Options:
|
|
263
|
+
--output <path> Output directory (default: <module>/generated)
|
|
264
|
+
|
|
265
|
+
deploy <module-id> Deploy module to infrastructure (auto-generates/builds if needed)
|
|
266
|
+
Options:
|
|
267
|
+
--debug Run hooks with visible browser (Playwright)
|
|
268
|
+
--no-interactive Fail instead of prompting for missing config
|
|
269
|
+
|
|
270
|
+
health [module-id] Run health checks (transitions to VERIFIED on success)
|
|
271
|
+
Options:
|
|
272
|
+
--json Machine-readable JSON output
|
|
273
|
+
--debug Show detailed check output
|
|
274
|
+
|
|
275
|
+
run-hook <id> <hook-name> [k=v...] Run a module hook (e.g., retry on_install after fixing an issue)
|
|
276
|
+
Options:
|
|
277
|
+
--debug Run with visible browser (Playwright hooks)
|
|
278
|
+
|
|
279
|
+
update <path> [path...] Update module code while preserving state (configs, secrets, infra)
|
|
280
|
+
|
|
281
|
+
Examples:
|
|
282
|
+
celilo module import ./modules/homebridge
|
|
283
|
+
celilo module import homebridge.netapp
|
|
284
|
+
celilo module import /abs/path/to/module --target /custom/location
|
|
285
|
+
celilo module list
|
|
286
|
+
celilo module remove homebridge
|
|
287
|
+
celilo module audit homebridge
|
|
288
|
+
celilo module config set homebridge hostname myhost
|
|
289
|
+
celilo module config set homebridge container_ip "192.168.0.110/24"
|
|
290
|
+
celilo module config get homebridge
|
|
291
|
+
celilo module config get homebridge hostname
|
|
292
|
+
celilo module show-config homebridge
|
|
293
|
+
celilo module show-zone homebridge
|
|
294
|
+
celilo module build caddy
|
|
295
|
+
celilo module generate homebridge
|
|
296
|
+
celilo module generate homebridge --output /custom/output
|
|
297
|
+
celilo module deploy homebridge
|
|
298
|
+
celilo module deploy caddy
|
|
299
|
+
celilo module run-hook caddy on_install
|
|
300
|
+
celilo module run-hook namecheap validate_config --debug
|
|
301
|
+
`;
|
|
302
|
+
|
|
303
|
+
return {
|
|
304
|
+
success: true,
|
|
305
|
+
message: helpText.trim(),
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Display service command help
|
|
311
|
+
*/
|
|
312
|
+
function displayServiceHelp(): CommandResult {
|
|
313
|
+
const helpText = `
|
|
314
|
+
Celilo - Container Service Management
|
|
315
|
+
|
|
316
|
+
Usage:
|
|
317
|
+
celilo service <subcommand> [args...] [options]
|
|
318
|
+
|
|
319
|
+
Subcommands:
|
|
320
|
+
add proxmox Configure a Proxmox container service
|
|
321
|
+
add digitalocean Configure a Digital Ocean VPS service
|
|
322
|
+
list List all configured container services
|
|
323
|
+
Options:
|
|
324
|
+
--zone <zone> Filter by network zone
|
|
325
|
+
verify <service-id> Re-verify a container service connection
|
|
326
|
+
reconfigure <service-id> Re-run configuration interview (change template, storage, etc.)
|
|
327
|
+
remove <id> Remove a container service
|
|
328
|
+
Options:
|
|
329
|
+
--force Skip confirmation prompts
|
|
330
|
+
config get <service-id> [key] Get service configuration
|
|
331
|
+
config set <service-id> <key> <value> Set service configuration
|
|
332
|
+
|
|
333
|
+
Description:
|
|
334
|
+
Container services are API-driven infrastructure providers that can automatically
|
|
335
|
+
provision containers/VMs for modules. Celilo prefers container services over
|
|
336
|
+
manually-added machines when selecting infrastructure during module generation.
|
|
337
|
+
|
|
338
|
+
Supported providers:
|
|
339
|
+
- Proxmox: LXC containers on home lab hardware
|
|
340
|
+
- Digital Ocean: Cloud VPS instances
|
|
341
|
+
|
|
342
|
+
Examples:
|
|
343
|
+
# Add container services
|
|
344
|
+
celilo service add proxmox
|
|
345
|
+
celilo service add digitalocean
|
|
346
|
+
|
|
347
|
+
# List all services
|
|
348
|
+
celilo service list
|
|
349
|
+
|
|
350
|
+
# List services for specific zone
|
|
351
|
+
celilo service list --zone dmz
|
|
352
|
+
|
|
353
|
+
# Verify a service connection
|
|
354
|
+
celilo service verify proxmox-home-lab
|
|
355
|
+
|
|
356
|
+
# Get service configuration
|
|
357
|
+
celilo service config get proxmox-home-lab
|
|
358
|
+
celilo service config get proxmox-home-lab name
|
|
359
|
+
celilo service config get proxmox-home-lab zones
|
|
360
|
+
|
|
361
|
+
# Set service configuration
|
|
362
|
+
celilo service config set proxmox-home-lab name "New Service Name"
|
|
363
|
+
celilo service config set proxmox-home-lab zones '["dmz", "app"]'
|
|
364
|
+
celilo service config set proxmox-home-lab providerConfig '{"storage": "local-lvm"}'
|
|
365
|
+
|
|
366
|
+
# Remove service
|
|
367
|
+
celilo service remove <service-id>
|
|
368
|
+
celilo service remove <service-id> --force
|
|
369
|
+
|
|
370
|
+
Related Commands:
|
|
371
|
+
celilo module generate <module-id> Generate infrastructure code (uses services)
|
|
372
|
+
celilo module deploy <module-id> Deploy module to infrastructure
|
|
373
|
+
`;
|
|
374
|
+
|
|
375
|
+
return {
|
|
376
|
+
success: true,
|
|
377
|
+
message: helpText.trim(),
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Display backup command help
|
|
383
|
+
*/
|
|
384
|
+
function displayBackupHelp(): CommandResult {
|
|
385
|
+
const helpText = `
|
|
386
|
+
Celilo - Backup Management
|
|
387
|
+
|
|
388
|
+
Usage:
|
|
389
|
+
celilo backup <subcommand> [args...] [options]
|
|
390
|
+
|
|
391
|
+
Subcommands:
|
|
392
|
+
create [module-id] Create a backup (system state + modules, or specific module)
|
|
393
|
+
Options:
|
|
394
|
+
--force Ignore schedule, back up now
|
|
395
|
+
--storage <id> Use specific storage destination
|
|
396
|
+
--no-interactive Non-interactive mode (for cron)
|
|
397
|
+
|
|
398
|
+
list [module-id] List available backups
|
|
399
|
+
Options:
|
|
400
|
+
--limit <n> Number of backups to show (default: 20)
|
|
401
|
+
|
|
402
|
+
restore <backup-id> Restore from a backup
|
|
403
|
+
Options:
|
|
404
|
+
--yes Skip confirmation prompt
|
|
405
|
+
|
|
406
|
+
delete <backup-id> Delete a specific backup
|
|
407
|
+
Options:
|
|
408
|
+
--force Skip confirmation prompt
|
|
409
|
+
|
|
410
|
+
prune [module-id] Remove old backups per retention policies
|
|
411
|
+
Options:
|
|
412
|
+
--dry-run Show what would be deleted without deleting
|
|
413
|
+
|
|
414
|
+
name <backup-id> [name] Get or set a human-readable name on a backup
|
|
415
|
+
Options:
|
|
416
|
+
--clear Remove the name
|
|
417
|
+
|
|
418
|
+
import <file> <module> Import a local file as a module backup
|
|
419
|
+
Options:
|
|
420
|
+
--name <name> Human-readable name for the backup
|
|
421
|
+
--schema-version <v> Schema version metadata
|
|
422
|
+
--storage <id> Use specific storage destination
|
|
423
|
+
--yes Skip confirmation prompt
|
|
424
|
+
|
|
425
|
+
Description:
|
|
426
|
+
Backups are encrypted with the master key and uploaded to a configured
|
|
427
|
+
storage destination. System state backups capture the Celilo database.
|
|
428
|
+
Module data backups use on_backup hooks defined in module manifests.
|
|
429
|
+
|
|
430
|
+
Any command that accepts a <backup-id> also accepts a backup name.
|
|
431
|
+
Use "celilo backup name" to assign human-readable names.
|
|
432
|
+
|
|
433
|
+
Examples:
|
|
434
|
+
# Create a full backup (system + all due modules)
|
|
435
|
+
celilo backup create
|
|
436
|
+
|
|
437
|
+
# Back up a specific module
|
|
438
|
+
celilo backup create authentik --force
|
|
439
|
+
|
|
440
|
+
# List recent backups
|
|
441
|
+
celilo backup list
|
|
442
|
+
celilo backup list authentik --limit 5
|
|
443
|
+
|
|
444
|
+
# Restore from a backup
|
|
445
|
+
celilo backup restore <backup-id>
|
|
446
|
+
|
|
447
|
+
# Name a backup
|
|
448
|
+
celilo backup name <backup-id> Data from beso before migration
|
|
449
|
+
celilo backup name <backup-id> --clear
|
|
450
|
+
|
|
451
|
+
# Prune old backups
|
|
452
|
+
celilo backup prune --dry-run
|
|
453
|
+
celilo backup prune
|
|
454
|
+
|
|
455
|
+
# Import a local file as a backup
|
|
456
|
+
celilo backup import ./backups/db.sqlite lunacycle --name "pre-migration"
|
|
457
|
+
|
|
458
|
+
Related Commands:
|
|
459
|
+
celilo storage add local Add backup storage destination
|
|
460
|
+
celilo storage list List storage destinations
|
|
461
|
+
`;
|
|
462
|
+
|
|
463
|
+
return {
|
|
464
|
+
success: true,
|
|
465
|
+
message: helpText.trim(),
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* Display storage command help
|
|
471
|
+
*/
|
|
472
|
+
function displayStorageHelp(): CommandResult {
|
|
473
|
+
const helpText = `
|
|
474
|
+
Celilo - Backup Storage Management
|
|
475
|
+
|
|
476
|
+
Usage:
|
|
477
|
+
celilo storage <subcommand> [args...] [options]
|
|
478
|
+
|
|
479
|
+
Subcommands:
|
|
480
|
+
add local Add local filesystem storage
|
|
481
|
+
add s3 Add S3-compatible storage (AWS, MinIO, B2, Wasabi, GCS)
|
|
482
|
+
list List all configured storage destinations
|
|
483
|
+
verify <storage-id> Test storage connectivity and permissions
|
|
484
|
+
set-default <id> Set the default backup storage destination
|
|
485
|
+
remove <storage-id> Remove a storage destination
|
|
486
|
+
Options:
|
|
487
|
+
--force Skip confirmation prompts
|
|
488
|
+
|
|
489
|
+
Description:
|
|
490
|
+
Backup storage destinations are where Celilo sends encrypted backup archives.
|
|
491
|
+
You must configure at least one storage destination before creating backups.
|
|
492
|
+
|
|
493
|
+
Examples:
|
|
494
|
+
# Add local storage
|
|
495
|
+
celilo storage add local
|
|
496
|
+
|
|
497
|
+
# List storage destinations
|
|
498
|
+
celilo storage list
|
|
499
|
+
|
|
500
|
+
# Verify storage connectivity
|
|
501
|
+
celilo storage verify local-backups
|
|
502
|
+
|
|
503
|
+
# Set default destination
|
|
504
|
+
celilo storage set-default local-backups
|
|
505
|
+
|
|
506
|
+
# Remove storage
|
|
507
|
+
celilo storage remove local-backups --force
|
|
508
|
+
|
|
509
|
+
Related Commands:
|
|
510
|
+
celilo backup create Create a backup
|
|
511
|
+
celilo backup list List available backups
|
|
512
|
+
celilo backup restore <id> Restore from a backup
|
|
513
|
+
`;
|
|
514
|
+
|
|
515
|
+
return {
|
|
516
|
+
success: true,
|
|
517
|
+
message: helpText.trim(),
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* Display machine command help
|
|
523
|
+
*/
|
|
524
|
+
function displayMachineHelp(): CommandResult {
|
|
525
|
+
const helpText = `
|
|
526
|
+
Celilo - Machine Pool Management
|
|
527
|
+
|
|
528
|
+
Usage:
|
|
529
|
+
celilo machine <subcommand> [args...] [options]
|
|
530
|
+
|
|
531
|
+
Subcommands:
|
|
532
|
+
add Add a machine to the pool with auto-detection
|
|
533
|
+
Interactive mode (prompts for input):
|
|
534
|
+
celilo machine add
|
|
535
|
+
|
|
536
|
+
Non-interactive mode:
|
|
537
|
+
celilo machine add --ip <ip> --ssh-user <user> --ssh-key-file <path>
|
|
538
|
+
Options:
|
|
539
|
+
--ip <ip> Machine IP address
|
|
540
|
+
--ssh-user <user> SSH username (default: root)
|
|
541
|
+
--ssh-key-file <path> Path to SSH private key
|
|
542
|
+
|
|
543
|
+
list List all machines in the pool
|
|
544
|
+
Options:
|
|
545
|
+
--zone <zone> Filter by network zone
|
|
546
|
+
|
|
547
|
+
status <hostname> Show detailed machine status with live connectivity
|
|
548
|
+
|
|
549
|
+
remove <hostname> Remove a machine from the pool
|
|
550
|
+
Options:
|
|
551
|
+
--force Skip confirmation prompts
|
|
552
|
+
|
|
553
|
+
Description:
|
|
554
|
+
The machine pool contains bring-your-own-hardware: existing machines like
|
|
555
|
+
Raspberry Pis, VPS instances, or bare metal servers that you manually add
|
|
556
|
+
to Celilo.
|
|
557
|
+
|
|
558
|
+
When adding a machine, Celilo automatically:
|
|
559
|
+
- Tests SSH connectivity
|
|
560
|
+
- Detects hostname, CPU, memory, disk
|
|
561
|
+
- Detects network zone from IP address
|
|
562
|
+
- Encrypts and stores SSH key for Ansible deployments
|
|
563
|
+
|
|
564
|
+
Celilo prefers container services over machine pool when selecting
|
|
565
|
+
infrastructure during module generation.
|
|
566
|
+
|
|
567
|
+
Examples:
|
|
568
|
+
# Add machine interactively
|
|
569
|
+
celilo machine add
|
|
570
|
+
|
|
571
|
+
# Add machine non-interactively
|
|
572
|
+
celilo machine add --ip 192.168.1.100 --ssh-user ubuntu --ssh-key-file ~/.ssh/id_ed25519
|
|
573
|
+
|
|
574
|
+
# List all machines
|
|
575
|
+
celilo machine list
|
|
576
|
+
|
|
577
|
+
# List machines in specific zone
|
|
578
|
+
celilo machine list --zone dmz
|
|
579
|
+
|
|
580
|
+
# Show machine status
|
|
581
|
+
celilo machine status pi-01
|
|
582
|
+
|
|
583
|
+
# Earmark machine for a module
|
|
584
|
+
celilo machine earmark 10.0.10.10 caddy
|
|
585
|
+
celilo machine earmark pi-01 --clear
|
|
586
|
+
|
|
587
|
+
# Remove machine (by hostname or IP)
|
|
588
|
+
celilo machine remove pi-01
|
|
589
|
+
celilo machine remove 10.0.10.10 --force
|
|
590
|
+
|
|
591
|
+
Related Commands:
|
|
592
|
+
celilo service list List container services
|
|
593
|
+
celilo module generate <id> Generate infrastructure code (selects infrastructure)
|
|
594
|
+
`;
|
|
595
|
+
|
|
596
|
+
return {
|
|
597
|
+
success: true,
|
|
598
|
+
message: helpText.trim(),
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
/**
|
|
603
|
+
* Display IPAM command help
|
|
604
|
+
*/
|
|
605
|
+
function displayIpamHelp(): CommandResult {
|
|
606
|
+
const helpText = `
|
|
607
|
+
Celilo - IPAM (IP Address Management)
|
|
608
|
+
|
|
609
|
+
Usage:
|
|
610
|
+
celilo ipam <resource> <action> [args...] [options]
|
|
611
|
+
|
|
612
|
+
Resources:
|
|
613
|
+
vmid Manage VMID reservations and allocations
|
|
614
|
+
ip Manage IP address reservations
|
|
615
|
+
list-allocations List all IPAM allocations (VMIDs and IPs)
|
|
616
|
+
|
|
617
|
+
VMID Commands:
|
|
618
|
+
celilo ipam vmid reserve <vmid-or-range> --reason <reason>
|
|
619
|
+
celilo ipam vmid unreserve <vmid-or-range>
|
|
620
|
+
celilo ipam vmid list-reservations
|
|
621
|
+
|
|
622
|
+
IP Commands:
|
|
623
|
+
celilo ipam ip exclude <ip-range> --reason <reason> [--zone <zone>]
|
|
624
|
+
celilo ipam ip include <ip-or-range> [--zone <zone>]
|
|
625
|
+
celilo ipam ip list-exclusions
|
|
626
|
+
|
|
627
|
+
Description:
|
|
628
|
+
IPAM automatically allocates VMIDs and IP addresses during module generation.
|
|
629
|
+
Exclusions prevent IPAM from allocating specific IPs (e.g., DHCP ranges, infrastructure).
|
|
630
|
+
|
|
631
|
+
VMIDs start at 2100 and increment sequentially.
|
|
632
|
+
IPs are allocated from zone subnets (network.{zone}.subnet).
|
|
633
|
+
Zone is auto-detected from the IP address unless overridden with --zone.
|
|
634
|
+
|
|
635
|
+
Examples:
|
|
636
|
+
# Reserve VMID for existing VM
|
|
637
|
+
celilo ipam vmid reserve 2100 --reason "Existing Proxmox VM"
|
|
638
|
+
celilo ipam vmid reserve 2100-2110 --reason "Reserved range"
|
|
639
|
+
|
|
640
|
+
# Exclude IPs from IPAM allocation (zone auto-detected from IP)
|
|
641
|
+
celilo ipam ip exclude 10.0.10.1-10.0.10.9 --reason "Infrastructure"
|
|
642
|
+
celilo ipam ip exclude 192.168.0.1-192.168.0.150 --reason "DHCP managed range"
|
|
643
|
+
|
|
644
|
+
# List allocations and reservations
|
|
645
|
+
celilo ipam list-allocations
|
|
646
|
+
celilo ipam vmid list-reservations
|
|
647
|
+
celilo ipam ip list-reservations
|
|
648
|
+
|
|
649
|
+
# Remove reservations
|
|
650
|
+
celilo ipam vmid unreserve 2100
|
|
651
|
+
celilo ipam ip unreserve 10.0.10.1 --zone dmz
|
|
652
|
+
`;
|
|
653
|
+
|
|
654
|
+
return {
|
|
655
|
+
success: true,
|
|
656
|
+
message: helpText.trim(),
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
/**
|
|
661
|
+
* Display system command help
|
|
662
|
+
*/
|
|
663
|
+
function displaySystemHelp(): CommandResult {
|
|
664
|
+
const helpText = `
|
|
665
|
+
Celilo - System Configuration
|
|
666
|
+
|
|
667
|
+
Usage:
|
|
668
|
+
celilo system <subcommand> [args...]
|
|
669
|
+
|
|
670
|
+
Subcommands:
|
|
671
|
+
init [--accept-defaults] [key=val...] Initialize system with guided setup or defaults
|
|
672
|
+
|
|
673
|
+
config set <key> <value> Set system-wide configuration value
|
|
674
|
+
config get [key] Get system configuration value(s)
|
|
675
|
+
|
|
676
|
+
secret set <key> <value> Set encrypted system-level secret
|
|
677
|
+
secret get [key] Get decrypted system secret value(s)
|
|
678
|
+
|
|
679
|
+
vault-password Get Ansible Vault password for decrypting secrets.yml
|
|
680
|
+
|
|
681
|
+
Description:
|
|
682
|
+
System initialization (init) guides you through first-time setup with interactive
|
|
683
|
+
prompts or applies sensible defaults for testing.
|
|
684
|
+
|
|
685
|
+
System configuration stores values that are shared across all modules, such as
|
|
686
|
+
DNS servers, network settings, and domain information. These values can be
|
|
687
|
+
referenced in module templates using $system:key.path syntax.
|
|
688
|
+
|
|
689
|
+
System secrets store encrypted sensitive values like passwords and tokens.
|
|
690
|
+
Note: Provider-specific credentials (Proxmox, DigitalOcean, etc.) are managed
|
|
691
|
+
via 'celilo service' commands, not system secrets.
|
|
692
|
+
|
|
693
|
+
Examples:
|
|
694
|
+
# Initialize system (first-time setup)
|
|
695
|
+
celilo system init
|
|
696
|
+
celilo system init --accept-defaults
|
|
697
|
+
celilo system init --accept-defaults primary_domain=example.com dns.primary=1.1.1.1
|
|
698
|
+
|
|
699
|
+
# Set DNS configuration
|
|
700
|
+
celilo system config set dns.primary 192.168.0.1
|
|
701
|
+
celilo system config set dns.fallback "8.8.8.8 1.1.1.1"
|
|
702
|
+
|
|
703
|
+
# Set network configuration
|
|
704
|
+
celilo system config set network.bridge vmbr0
|
|
705
|
+
celilo system config set network.dmz.subnet 10.0.10.0/24
|
|
706
|
+
|
|
707
|
+
# Get specific value
|
|
708
|
+
celilo system config get dns.primary
|
|
709
|
+
|
|
710
|
+
# Get all system config
|
|
711
|
+
celilo system config get
|
|
712
|
+
celilo system secret get
|
|
713
|
+
|
|
714
|
+
# Get vault password for Ansible
|
|
715
|
+
celilo system vault-password
|
|
716
|
+
|
|
717
|
+
Using Vault Password:
|
|
718
|
+
# View encrypted Ansible secrets
|
|
719
|
+
ansible-vault view secrets.yml \\
|
|
720
|
+
--vault-password-file=<(celilo system vault-password)
|
|
721
|
+
|
|
722
|
+
# Run Ansible playbook with encrypted secrets
|
|
723
|
+
ansible-playbook playbook.yml \\
|
|
724
|
+
--vault-password-file=<(celilo system vault-password)
|
|
725
|
+
|
|
726
|
+
# Edit encrypted secrets
|
|
727
|
+
ansible-vault edit secrets.yml \\
|
|
728
|
+
--vault-password-file=<(celilo system vault-password)
|
|
729
|
+
`;
|
|
730
|
+
|
|
731
|
+
return {
|
|
732
|
+
success: true,
|
|
733
|
+
message: helpText.trim(),
|
|
734
|
+
};
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
/**
|
|
738
|
+
* Route command to appropriate handler
|
|
739
|
+
*
|
|
740
|
+
* @param argv - Command-line arguments
|
|
741
|
+
* @returns Command result
|
|
742
|
+
*/
|
|
743
|
+
export async function runCli(argv: string[]): Promise<CommandResult> {
|
|
744
|
+
const parsed = parseArguments(argv);
|
|
745
|
+
|
|
746
|
+
// Handle --get-completions for shell completion (must be before other processing)
|
|
747
|
+
if (parsed.flags['get-completions']) {
|
|
748
|
+
try {
|
|
749
|
+
// Arguments: celilo --get-completions word1 word2 ... currentIndex
|
|
750
|
+
// The words array and current index are passed as remaining args
|
|
751
|
+
const words = parsed.args.slice(0, -1); // All args except last
|
|
752
|
+
const currentStr = parsed.args[parsed.args.length - 1]; // Last arg is the index
|
|
753
|
+
const current = Number.parseInt(currentStr || '0', 10);
|
|
754
|
+
|
|
755
|
+
const suggestions = await getCompletions(words, current);
|
|
756
|
+
|
|
757
|
+
// Return suggestions as newline-separated list (for zsh array splitting)
|
|
758
|
+
return {
|
|
759
|
+
success: true,
|
|
760
|
+
message: suggestions.join('\n'),
|
|
761
|
+
};
|
|
762
|
+
} catch (_error) {
|
|
763
|
+
// Silent failure for completion errors
|
|
764
|
+
return { success: true, message: '' };
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
// Handle general help (only if no specific command, or command is 'help')
|
|
769
|
+
if (parsed.command === 'help') {
|
|
770
|
+
return displayHelp();
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
// Handle completion command
|
|
774
|
+
if (parsed.command === 'completion') {
|
|
775
|
+
// Completion is a simple command without subcommands
|
|
776
|
+
// If parser treated first arg as subcommand, prepend it to args
|
|
777
|
+
const completionArgs = parsed.subcommand ? [parsed.subcommand, ...parsed.args] : parsed.args;
|
|
778
|
+
return handleCompletion(completionArgs, parsed.flags);
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
// Handle status command
|
|
782
|
+
if (parsed.command === 'status') {
|
|
783
|
+
return handleStatus();
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
// Route commands
|
|
787
|
+
if (parsed.command === 'package') {
|
|
788
|
+
// Handle package --help
|
|
789
|
+
if (parsed.flags.help || parsed.flags.h) {
|
|
790
|
+
return displayPackageHelp();
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
// Package is a simple command without subcommands
|
|
794
|
+
// If parser treated first arg as subcommand, prepend it to args
|
|
795
|
+
const packageArgs = parsed.subcommand ? [parsed.subcommand, ...parsed.args] : parsed.args;
|
|
796
|
+
return handlePackage(packageArgs, parsed.flags);
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
if (parsed.command === 'hook') {
|
|
800
|
+
if (parsed.flags.help || parsed.flags.h) {
|
|
801
|
+
return {
|
|
802
|
+
success: true,
|
|
803
|
+
message: [
|
|
804
|
+
'Hook commands:',
|
|
805
|
+
'',
|
|
806
|
+
' celilo hook run <module-id> <hook-name> [--debug] [key=value...]',
|
|
807
|
+
'',
|
|
808
|
+
'Options:',
|
|
809
|
+
' --debug Run with visible browser (Playwright hooks)',
|
|
810
|
+
'',
|
|
811
|
+
'Examples:',
|
|
812
|
+
' celilo hook run namecheap validate_config --debug',
|
|
813
|
+
' celilo hook run namecheap container_created vps_ip=138.68.140.177',
|
|
814
|
+
].join('\n'),
|
|
815
|
+
};
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
if (parsed.subcommand === 'run') {
|
|
819
|
+
return handleHookRun(parsed.args, parsed.flags);
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
return {
|
|
823
|
+
success: false,
|
|
824
|
+
error: 'Hook subcommand required\n\nRun "celilo hook --help" for usage',
|
|
825
|
+
};
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
if (parsed.command === 'capability') {
|
|
829
|
+
if (parsed.flags.help || parsed.flags.h) {
|
|
830
|
+
return displayCapabilityHelp();
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
if (!parsed.subcommand) {
|
|
834
|
+
return {
|
|
835
|
+
success: false,
|
|
836
|
+
error: 'Capability subcommand required\n\nRun "celilo capability --help" for usage',
|
|
837
|
+
};
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
const capFlagError = checkFlags('capability', parsed.subcommand, parsed.flags, parsed.args);
|
|
841
|
+
if (capFlagError) return capFlagError;
|
|
842
|
+
|
|
843
|
+
if (parsed.subcommand === 'list') {
|
|
844
|
+
return handleCapabilityList();
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
if (parsed.subcommand === 'info') {
|
|
848
|
+
return handleCapabilityInfo(parsed.args);
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
return {
|
|
852
|
+
success: false,
|
|
853
|
+
error: `Unknown capability subcommand: ${parsed.subcommand}\n\nRun "celilo capability --help" for usage`,
|
|
854
|
+
};
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
if (parsed.command === 'module') {
|
|
858
|
+
// Handle module --help
|
|
859
|
+
if (parsed.flags.help || parsed.flags.h) {
|
|
860
|
+
return displayModuleHelp();
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
if (!parsed.subcommand) {
|
|
864
|
+
return {
|
|
865
|
+
success: false,
|
|
866
|
+
error: 'Module subcommand required\n\nRun "celilo module --help" for usage',
|
|
867
|
+
};
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
const flagError = checkFlags('module', parsed.subcommand, parsed.flags, parsed.args);
|
|
871
|
+
if (flagError) return flagError;
|
|
872
|
+
|
|
873
|
+
switch (parsed.subcommand) {
|
|
874
|
+
case 'import':
|
|
875
|
+
return handleModuleImport(parsed.args, parsed.flags);
|
|
876
|
+
case 'list':
|
|
877
|
+
return handleModuleList();
|
|
878
|
+
case 'status':
|
|
879
|
+
return handleModuleStatus(parsed.args);
|
|
880
|
+
case 'logs':
|
|
881
|
+
return handleModuleLogs(parsed.args, parsed.flags);
|
|
882
|
+
case 'health':
|
|
883
|
+
return handleModuleHealth(parsed.args, parsed.flags);
|
|
884
|
+
case 'remove':
|
|
885
|
+
return handleModuleRemove(parsed.args, parsed.flags);
|
|
886
|
+
case 'update':
|
|
887
|
+
return handleModuleUpgrade(parsed.args, parsed.flags);
|
|
888
|
+
case 'audit':
|
|
889
|
+
return moduleAudit(parsed.args);
|
|
890
|
+
case 'config': {
|
|
891
|
+
// Config requires additional subcommand (set/get)
|
|
892
|
+
const configSubcommand = parsed.args[0];
|
|
893
|
+
if (!configSubcommand) {
|
|
894
|
+
return {
|
|
895
|
+
success: false,
|
|
896
|
+
error: 'Config action required (set or get)\n\nRun "celilo help" for usage',
|
|
897
|
+
};
|
|
898
|
+
}
|
|
899
|
+
const configArgs = parsed.args.slice(1);
|
|
900
|
+
if (configSubcommand === 'set') {
|
|
901
|
+
return handleModuleConfigSet(configArgs);
|
|
902
|
+
}
|
|
903
|
+
if (configSubcommand === 'get') {
|
|
904
|
+
return handleModuleConfigGet(configArgs);
|
|
905
|
+
}
|
|
906
|
+
return {
|
|
907
|
+
success: false,
|
|
908
|
+
error: `Unknown config action: ${configSubcommand}`,
|
|
909
|
+
};
|
|
910
|
+
}
|
|
911
|
+
case 'secret': {
|
|
912
|
+
// Module secret requires additional subcommand (set/list)
|
|
913
|
+
const moduleSecretSubcommand = parsed.args[0];
|
|
914
|
+
if (!moduleSecretSubcommand) {
|
|
915
|
+
return {
|
|
916
|
+
success: false,
|
|
917
|
+
error:
|
|
918
|
+
'Secret action required (set or list)\n\nUsage:\n celilo module secret set <module-id> <key> <value>\n celilo module secret list <module-id>',
|
|
919
|
+
};
|
|
920
|
+
}
|
|
921
|
+
const moduleSecretArgs = parsed.args.slice(1);
|
|
922
|
+
if (moduleSecretSubcommand === 'set') {
|
|
923
|
+
return handleSecretSet(moduleSecretArgs);
|
|
924
|
+
}
|
|
925
|
+
if (moduleSecretSubcommand === 'list') {
|
|
926
|
+
return handleSecretList(moduleSecretArgs);
|
|
927
|
+
}
|
|
928
|
+
return {
|
|
929
|
+
success: false,
|
|
930
|
+
error: `Unknown secret action: ${moduleSecretSubcommand}`,
|
|
931
|
+
};
|
|
932
|
+
}
|
|
933
|
+
case 'types': {
|
|
934
|
+
// Types requires additional subcommand (generate/check)
|
|
935
|
+
const typesSubcommand = parsed.args[0];
|
|
936
|
+
if (!typesSubcommand) {
|
|
937
|
+
return {
|
|
938
|
+
success: false,
|
|
939
|
+
error:
|
|
940
|
+
'Types action required (generate or check)\n\nUsage:\n celilo module types generate <module-dir>\n celilo module types check <module-dir>',
|
|
941
|
+
};
|
|
942
|
+
}
|
|
943
|
+
const typesArgs = parsed.args.slice(1);
|
|
944
|
+
if (typesSubcommand === 'generate') {
|
|
945
|
+
return handleModuleTypesGenerate(typesArgs);
|
|
946
|
+
}
|
|
947
|
+
if (typesSubcommand === 'check') {
|
|
948
|
+
return handleModuleTypesCheck(typesArgs);
|
|
949
|
+
}
|
|
950
|
+
return {
|
|
951
|
+
success: false,
|
|
952
|
+
error: `Unknown types action: ${typesSubcommand}\n\nAvailable: generate, check`,
|
|
953
|
+
};
|
|
954
|
+
}
|
|
955
|
+
case 'generate':
|
|
956
|
+
return handleModuleGenerate(parsed.args, parsed.flags);
|
|
957
|
+
case 'build':
|
|
958
|
+
return handleModuleBuild(parsed.args, parsed.flags);
|
|
959
|
+
case 'deploy':
|
|
960
|
+
return handleModuleDeploy(parsed.args, parsed.flags);
|
|
961
|
+
case 'validate':
|
|
962
|
+
// Alias for `module deploy --preflight`
|
|
963
|
+
return handleModuleDeploy(parsed.args, { ...parsed.flags, preflight: true });
|
|
964
|
+
case 'run-hook':
|
|
965
|
+
return handleHookRun(parsed.args, parsed.flags);
|
|
966
|
+
case 'show-config':
|
|
967
|
+
return handleModuleShowConfig(parsed.args);
|
|
968
|
+
case 'show-zone':
|
|
969
|
+
return handleModuleShowZone(parsed.args);
|
|
970
|
+
default:
|
|
971
|
+
return {
|
|
972
|
+
success: false,
|
|
973
|
+
error: `Unknown module subcommand: ${parsed.subcommand}\n\nRun "celilo help" for usage`,
|
|
974
|
+
};
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
if (parsed.command === 'secret') {
|
|
979
|
+
return {
|
|
980
|
+
success: false,
|
|
981
|
+
error:
|
|
982
|
+
'The top-level "secret" command has been moved.\n\nUse:\n celilo module secret set <module-id> <key> <value>\n celilo module secret list <module-id>\n celilo system secret set <key> <value>\n celilo system secret get <key>',
|
|
983
|
+
};
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
if (parsed.command === 'service') {
|
|
987
|
+
// Handle service --help
|
|
988
|
+
if (parsed.flags.help || parsed.flags.h) {
|
|
989
|
+
return displayServiceHelp();
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
if (!parsed.subcommand) {
|
|
993
|
+
return {
|
|
994
|
+
success: false,
|
|
995
|
+
error: 'Service subcommand required\n\nRun "celilo service --help" for usage',
|
|
996
|
+
};
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
const svcFlagError = checkFlags('service', parsed.subcommand, parsed.flags, parsed.args);
|
|
1000
|
+
if (svcFlagError) return svcFlagError;
|
|
1001
|
+
|
|
1002
|
+
if (parsed.subcommand === 'add') {
|
|
1003
|
+
// Add requires provider type
|
|
1004
|
+
const provider = parsed.args[0];
|
|
1005
|
+
if (!provider) {
|
|
1006
|
+
return {
|
|
1007
|
+
success: false,
|
|
1008
|
+
error:
|
|
1009
|
+
'Provider type required (proxmox or digitalocean)\n\nRun "celilo service --help" for usage',
|
|
1010
|
+
};
|
|
1011
|
+
}
|
|
1012
|
+
const addArgs = parsed.args.slice(1);
|
|
1013
|
+
if (provider === 'proxmox') {
|
|
1014
|
+
return handleServiceAddProxmox(addArgs, parsed.flags);
|
|
1015
|
+
}
|
|
1016
|
+
if (provider === 'digitalocean') {
|
|
1017
|
+
return handleServiceAddDigitalOcean(addArgs, parsed.flags);
|
|
1018
|
+
}
|
|
1019
|
+
return {
|
|
1020
|
+
success: false,
|
|
1021
|
+
error: `Unknown provider: ${provider}\n\nSupported providers: proxmox, digitalocean`,
|
|
1022
|
+
};
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
if (parsed.subcommand === 'list') {
|
|
1026
|
+
return handleServiceList(parsed.args, parsed.flags);
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
if (parsed.subcommand === 'remove') {
|
|
1030
|
+
return handleServiceRemove(parsed.args, parsed.flags);
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
if (parsed.subcommand === 'verify') {
|
|
1034
|
+
return handleServiceVerify(parsed.args, parsed.flags);
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
if (parsed.subcommand === 'reconfigure') {
|
|
1038
|
+
return handleServiceReconfigure(parsed.args, parsed.flags);
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
if (parsed.subcommand === 'config') {
|
|
1042
|
+
const configSubcommand = parsed.args[0];
|
|
1043
|
+
if (!configSubcommand) {
|
|
1044
|
+
return {
|
|
1045
|
+
success: false,
|
|
1046
|
+
error: 'Config action required (set or get)\n\nRun "celilo help" for usage',
|
|
1047
|
+
};
|
|
1048
|
+
}
|
|
1049
|
+
const configArgs = parsed.args.slice(1);
|
|
1050
|
+
if (configSubcommand === 'set') {
|
|
1051
|
+
return handleServiceConfigSet(configArgs);
|
|
1052
|
+
}
|
|
1053
|
+
if (configSubcommand === 'get') {
|
|
1054
|
+
return handleServiceConfigGet(configArgs);
|
|
1055
|
+
}
|
|
1056
|
+
return {
|
|
1057
|
+
success: false,
|
|
1058
|
+
error: `Unknown config action: ${configSubcommand}`,
|
|
1059
|
+
};
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
return {
|
|
1063
|
+
success: false,
|
|
1064
|
+
error: `Unknown service subcommand: ${parsed.subcommand}\n\nRun "celilo service --help" for usage`,
|
|
1065
|
+
};
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
if (parsed.command === 'storage') {
|
|
1069
|
+
if (parsed.flags.help || parsed.flags.h) {
|
|
1070
|
+
return displayStorageHelp();
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
if (!parsed.subcommand) {
|
|
1074
|
+
return {
|
|
1075
|
+
success: false,
|
|
1076
|
+
error: 'Storage subcommand required\n\nRun "celilo storage --help" for usage',
|
|
1077
|
+
};
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
// Skip flag validation for 'add' — flags belong to the sub-subcommand (local/s3)
|
|
1081
|
+
if (parsed.subcommand !== 'add') {
|
|
1082
|
+
const storageFlagError = checkFlags('storage', parsed.subcommand, parsed.flags, parsed.args);
|
|
1083
|
+
if (storageFlagError) return storageFlagError;
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
if (parsed.subcommand === 'add') {
|
|
1087
|
+
const provider = parsed.args[0];
|
|
1088
|
+
if (!provider) {
|
|
1089
|
+
return {
|
|
1090
|
+
success: false,
|
|
1091
|
+
error: 'Provider type required (local or s3)\n\nRun "celilo storage --help" for usage',
|
|
1092
|
+
};
|
|
1093
|
+
}
|
|
1094
|
+
const addArgs = parsed.args.slice(1);
|
|
1095
|
+
if (provider === 'local') {
|
|
1096
|
+
const { handleStorageAddLocal } = await import('./commands/storage-add-local');
|
|
1097
|
+
return handleStorageAddLocal(addArgs, parsed.flags);
|
|
1098
|
+
}
|
|
1099
|
+
if (provider === 's3') {
|
|
1100
|
+
const { handleStorageAddS3 } = await import('./commands/storage-add-s3');
|
|
1101
|
+
return handleStorageAddS3(addArgs, parsed.flags);
|
|
1102
|
+
}
|
|
1103
|
+
return {
|
|
1104
|
+
success: false,
|
|
1105
|
+
error: `Unknown storage provider: ${provider}\n\nSupported providers: local, s3`,
|
|
1106
|
+
};
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
if (parsed.subcommand === 'list') {
|
|
1110
|
+
const { handleStorageList } = await import('./commands/storage-list');
|
|
1111
|
+
return handleStorageList(parsed.args, parsed.flags);
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
if (parsed.subcommand === 'remove') {
|
|
1115
|
+
const { handleStorageRemove } = await import('./commands/storage-remove');
|
|
1116
|
+
return handleStorageRemove(parsed.args, parsed.flags);
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
if (parsed.subcommand === 'verify') {
|
|
1120
|
+
const { handleStorageVerify } = await import('./commands/storage-verify');
|
|
1121
|
+
return handleStorageVerify(parsed.args, parsed.flags);
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
if (parsed.subcommand === 'set-default') {
|
|
1125
|
+
const { handleStorageSetDefault } = await import('./commands/storage-set-default');
|
|
1126
|
+
return handleStorageSetDefault(parsed.args, parsed.flags);
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
return {
|
|
1130
|
+
success: false,
|
|
1131
|
+
error: `Unknown storage subcommand: ${parsed.subcommand}\n\nRun "celilo storage --help" for usage`,
|
|
1132
|
+
};
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
if (parsed.command === 'backup') {
|
|
1136
|
+
if (parsed.flags.help || parsed.flags.h) {
|
|
1137
|
+
return displayBackupHelp();
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
if (!parsed.subcommand) {
|
|
1141
|
+
return {
|
|
1142
|
+
success: false,
|
|
1143
|
+
error: 'Backup subcommand required\n\nRun "celilo backup --help" for usage',
|
|
1144
|
+
};
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
const backupFlagError = checkFlags('backup', parsed.subcommand, parsed.flags, parsed.args);
|
|
1148
|
+
if (backupFlagError) return backupFlagError;
|
|
1149
|
+
|
|
1150
|
+
if (parsed.subcommand === 'create') {
|
|
1151
|
+
const { handleBackupCreate } = await import('./commands/backup-create');
|
|
1152
|
+
return handleBackupCreate(parsed.args, parsed.flags);
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
if (parsed.subcommand === 'list') {
|
|
1156
|
+
const { handleBackupList } = await import('./commands/backup-list');
|
|
1157
|
+
return handleBackupList(parsed.args, parsed.flags);
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
if (parsed.subcommand === 'restore') {
|
|
1161
|
+
const { handleBackupRestore } = await import('./commands/backup-restore');
|
|
1162
|
+
return handleBackupRestore(parsed.args, parsed.flags);
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
if (parsed.subcommand === 'prune') {
|
|
1166
|
+
const { handleBackupPrune } = await import('./commands/backup-prune');
|
|
1167
|
+
return handleBackupPrune(parsed.args, parsed.flags);
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
if (parsed.subcommand === 'delete') {
|
|
1171
|
+
const { handleBackupDelete } = await import('./commands/backup-delete');
|
|
1172
|
+
return handleBackupDelete(parsed.args, parsed.flags);
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
if (parsed.subcommand === 'name') {
|
|
1176
|
+
const { handleBackupName } = await import('./commands/backup-name');
|
|
1177
|
+
return handleBackupName(parsed.args, parsed.flags);
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
if (parsed.subcommand === 'import') {
|
|
1181
|
+
const { handleBackupImport } = await import('./commands/backup-import');
|
|
1182
|
+
return handleBackupImport(parsed.args, parsed.flags);
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
return {
|
|
1186
|
+
success: false,
|
|
1187
|
+
error: `Unknown backup subcommand: ${parsed.subcommand}\n\nRun "celilo backup --help" for usage`,
|
|
1188
|
+
};
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
if (parsed.command === 'machine') {
|
|
1192
|
+
// Handle machine --help
|
|
1193
|
+
if (parsed.flags.help || parsed.flags.h) {
|
|
1194
|
+
return displayMachineHelp();
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
if (!parsed.subcommand) {
|
|
1198
|
+
return {
|
|
1199
|
+
success: false,
|
|
1200
|
+
error: 'Machine subcommand required\n\nRun "celilo machine --help" for usage',
|
|
1201
|
+
};
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
const machFlagError = checkFlags('machine', parsed.subcommand, parsed.flags, parsed.args);
|
|
1205
|
+
if (machFlagError) return machFlagError;
|
|
1206
|
+
|
|
1207
|
+
if (parsed.subcommand === 'add') {
|
|
1208
|
+
return handleMachineAdd(parsed.args, parsed.flags);
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
if (parsed.subcommand === 'list') {
|
|
1212
|
+
return handleMachineList(parsed.args, parsed.flags);
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
if (parsed.subcommand === 'status') {
|
|
1216
|
+
return handleMachineStatus(parsed.args, parsed.flags);
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
if (parsed.subcommand === 'remove') {
|
|
1220
|
+
return handleMachineRemove(parsed.args, parsed.flags);
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
if (parsed.subcommand === 'earmark') {
|
|
1224
|
+
return handleMachineEarmark(parsed.args, parsed.flags);
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
return {
|
|
1228
|
+
success: false,
|
|
1229
|
+
error: `Unknown machine subcommand: ${parsed.subcommand}\n\nRun "celilo machine --help" for usage`,
|
|
1230
|
+
};
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
if (parsed.command === 'system') {
|
|
1234
|
+
// Handle system --help
|
|
1235
|
+
if (parsed.flags.help || parsed.flags.h) {
|
|
1236
|
+
return displaySystemHelp();
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
if (!parsed.subcommand) {
|
|
1240
|
+
return {
|
|
1241
|
+
success: false,
|
|
1242
|
+
error: 'System subcommand required\n\nRun "celilo system --help" for usage',
|
|
1243
|
+
};
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
const sysFlagError = checkFlags('system', parsed.subcommand, parsed.flags, parsed.args);
|
|
1247
|
+
if (sysFlagError) return sysFlagError;
|
|
1248
|
+
|
|
1249
|
+
if (parsed.subcommand === 'init') {
|
|
1250
|
+
return handleSystemInit(parsed.args, parsed.flags);
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
if (parsed.subcommand === 'config') {
|
|
1254
|
+
const configSubcommand = parsed.args[0];
|
|
1255
|
+
if (!configSubcommand) {
|
|
1256
|
+
return {
|
|
1257
|
+
success: false,
|
|
1258
|
+
error: 'Config action required (set or get)\n\nRun "celilo system --help" for usage',
|
|
1259
|
+
};
|
|
1260
|
+
}
|
|
1261
|
+
const configArgs = parsed.args.slice(1);
|
|
1262
|
+
if (configSubcommand === 'set') {
|
|
1263
|
+
return handleSystemConfigSet(configArgs);
|
|
1264
|
+
}
|
|
1265
|
+
if (configSubcommand === 'get') {
|
|
1266
|
+
return handleSystemConfigGet(configArgs);
|
|
1267
|
+
}
|
|
1268
|
+
return {
|
|
1269
|
+
success: false,
|
|
1270
|
+
error: `Unknown config action: ${configSubcommand}`,
|
|
1271
|
+
};
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
if (parsed.subcommand === 'secret') {
|
|
1275
|
+
const secretSubcommand = parsed.args[0];
|
|
1276
|
+
if (!secretSubcommand) {
|
|
1277
|
+
return {
|
|
1278
|
+
success: false,
|
|
1279
|
+
error: 'Secret action required (set or get)\n\nRun "celilo system --help" for usage',
|
|
1280
|
+
};
|
|
1281
|
+
}
|
|
1282
|
+
const secretArgs = parsed.args.slice(1);
|
|
1283
|
+
if (secretSubcommand === 'set') {
|
|
1284
|
+
return handleSystemSecretSet(secretArgs);
|
|
1285
|
+
}
|
|
1286
|
+
if (secretSubcommand === 'get') {
|
|
1287
|
+
return handleSystemSecretGet(secretArgs);
|
|
1288
|
+
}
|
|
1289
|
+
return {
|
|
1290
|
+
success: false,
|
|
1291
|
+
error: `Unknown secret action: ${secretSubcommand}`,
|
|
1292
|
+
};
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
if (parsed.subcommand === 'vault-password') {
|
|
1296
|
+
return handleSystemVaultPassword();
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
return {
|
|
1300
|
+
success: false,
|
|
1301
|
+
error: `Unknown system subcommand: ${parsed.subcommand}\n\nRun "celilo system --help" for usage`,
|
|
1302
|
+
};
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
if (parsed.command === 'ipam') {
|
|
1306
|
+
// Handle ipam --help
|
|
1307
|
+
if (parsed.flags.help || parsed.flags.h) {
|
|
1308
|
+
return displayIpamHelp();
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
if (!parsed.subcommand) {
|
|
1312
|
+
return {
|
|
1313
|
+
success: false,
|
|
1314
|
+
error: 'IPAM subcommand required\n\nRun "celilo ipam --help" for usage',
|
|
1315
|
+
};
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
const ipamFlagError = checkFlags('ipam', parsed.subcommand, parsed.flags, parsed.args);
|
|
1319
|
+
if (ipamFlagError) return ipamFlagError;
|
|
1320
|
+
|
|
1321
|
+
// Handle show command (comprehensive summary)
|
|
1322
|
+
if (parsed.subcommand === 'show') {
|
|
1323
|
+
return handleIpamShow(parsed.args, parsed.flags);
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
// Handle list-allocations (no additional subcommand)
|
|
1327
|
+
if (parsed.subcommand === 'list-allocations') {
|
|
1328
|
+
return handleIpamListAllocations(parsed.args, parsed.flags);
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
// VMID commands
|
|
1332
|
+
if (parsed.subcommand === 'vmid') {
|
|
1333
|
+
const vmidSubcommand = parsed.args[0];
|
|
1334
|
+
if (!vmidSubcommand) {
|
|
1335
|
+
return {
|
|
1336
|
+
success: false,
|
|
1337
|
+
error:
|
|
1338
|
+
'VMID action required (reserve, unreserve, list-reservations)\n\nRun "celilo ipam --help" for usage',
|
|
1339
|
+
};
|
|
1340
|
+
}
|
|
1341
|
+
const vmidArgs = parsed.args.slice(1);
|
|
1342
|
+
if (vmidSubcommand === 'reserve') {
|
|
1343
|
+
return handleIpamVmidReserve(vmidArgs, parsed.flags);
|
|
1344
|
+
}
|
|
1345
|
+
if (vmidSubcommand === 'unreserve') {
|
|
1346
|
+
return handleIpamVmidUnreserve(vmidArgs, parsed.flags);
|
|
1347
|
+
}
|
|
1348
|
+
if (vmidSubcommand === 'list-reservations') {
|
|
1349
|
+
return handleIpamVmidListReservations(vmidArgs, parsed.flags);
|
|
1350
|
+
}
|
|
1351
|
+
return {
|
|
1352
|
+
success: false,
|
|
1353
|
+
error: `Unknown VMID action: ${vmidSubcommand}`,
|
|
1354
|
+
};
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
// IP commands
|
|
1358
|
+
if (parsed.subcommand === 'ip') {
|
|
1359
|
+
const ipSubcommand = parsed.args[0];
|
|
1360
|
+
if (!ipSubcommand) {
|
|
1361
|
+
return {
|
|
1362
|
+
success: false,
|
|
1363
|
+
error:
|
|
1364
|
+
'IP action required (exclude, include, list-exclusions)\n\nRun "celilo ipam --help" for usage',
|
|
1365
|
+
};
|
|
1366
|
+
}
|
|
1367
|
+
const ipArgs = parsed.args.slice(1);
|
|
1368
|
+
if (ipSubcommand === 'exclude') {
|
|
1369
|
+
return handleIpamIpReserve(ipArgs, parsed.flags);
|
|
1370
|
+
}
|
|
1371
|
+
if (ipSubcommand === 'include') {
|
|
1372
|
+
return handleIpamIpUnreserve(ipArgs, parsed.flags);
|
|
1373
|
+
}
|
|
1374
|
+
if (ipSubcommand === 'list-exclusions') {
|
|
1375
|
+
return handleIpamIpListReservations(ipArgs, parsed.flags);
|
|
1376
|
+
}
|
|
1377
|
+
return {
|
|
1378
|
+
success: false,
|
|
1379
|
+
error: `Unknown IP action: ${ipSubcommand}`,
|
|
1380
|
+
};
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
return {
|
|
1384
|
+
success: false,
|
|
1385
|
+
error: `Unknown IPAM subcommand: ${parsed.subcommand}\n\nRun "celilo ipam --help" for usage`,
|
|
1386
|
+
};
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
return {
|
|
1390
|
+
success: false,
|
|
1391
|
+
error: `Unknown command: ${parsed.command}\n\nRun "celilo help" for usage`,
|
|
1392
|
+
};
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
/**
|
|
1396
|
+
* Server Mode - Persistent CLI Process
|
|
1397
|
+
*
|
|
1398
|
+
* Accepts commands via stdin, executes them, and returns results via stdout.
|
|
1399
|
+
* Used by test infrastructure for efficient command execution without spawning
|
|
1400
|
+
* fresh processes.
|
|
1401
|
+
*
|
|
1402
|
+
* Protocol:
|
|
1403
|
+
* - Input (stdin): JSON lines with `{ command: string, id: number }`
|
|
1404
|
+
* - Output (stdout): JSON lines with `{ id: number, success: boolean, message?: string, error?: string, exitCode: number, duration: number }`
|
|
1405
|
+
* - Special command "__exit__" terminates server
|
|
1406
|
+
* - Special command "__ping__" returns pong (health check)
|
|
1407
|
+
*/
|
|
1408
|
+
/**
|
|
1409
|
+
* Parse command string respecting quotes
|
|
1410
|
+
* Handles: system config set dns.fallback "8.8.8.8 1.1.1.1"
|
|
1411
|
+
* Returns: ['system', 'config', 'set', 'dns.fallback', '8.8.8.8 1.1.1.1']
|
|
1412
|
+
*/
|
|
1413
|
+
function parseCommand(command: string): string[] {
|
|
1414
|
+
const args: string[] = [];
|
|
1415
|
+
let current = '';
|
|
1416
|
+
let inQuotes = false;
|
|
1417
|
+
let quoteChar = '';
|
|
1418
|
+
|
|
1419
|
+
for (let i = 0; i < command.length; i++) {
|
|
1420
|
+
const char = command[i];
|
|
1421
|
+
|
|
1422
|
+
if ((char === '"' || char === "'") && !inQuotes) {
|
|
1423
|
+
// Start of quoted string
|
|
1424
|
+
inQuotes = true;
|
|
1425
|
+
quoteChar = char;
|
|
1426
|
+
} else if (char === quoteChar && inQuotes) {
|
|
1427
|
+
// End of quoted string
|
|
1428
|
+
inQuotes = false;
|
|
1429
|
+
quoteChar = '';
|
|
1430
|
+
} else if (char === ' ' && !inQuotes) {
|
|
1431
|
+
// Space outside quotes - word boundary
|
|
1432
|
+
if (current) {
|
|
1433
|
+
args.push(current);
|
|
1434
|
+
current = '';
|
|
1435
|
+
}
|
|
1436
|
+
} else {
|
|
1437
|
+
// Regular character
|
|
1438
|
+
current += char;
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1442
|
+
// Add final argument
|
|
1443
|
+
if (current) {
|
|
1444
|
+
args.push(current);
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1447
|
+
return args;
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
async function serverMode(): Promise<void> {
|
|
1451
|
+
const readline = await import('node:readline');
|
|
1452
|
+
|
|
1453
|
+
// Signal ready
|
|
1454
|
+
console.log(JSON.stringify({ type: 'ready', pid: process.pid }));
|
|
1455
|
+
|
|
1456
|
+
const rl = readline.createInterface({
|
|
1457
|
+
input: process.stdin,
|
|
1458
|
+
output: process.stdout,
|
|
1459
|
+
terminal: false,
|
|
1460
|
+
});
|
|
1461
|
+
|
|
1462
|
+
// Keep process alive - don't exit when stdin ends
|
|
1463
|
+
rl.on('close', () => {
|
|
1464
|
+
// Stdin closed - exit gracefully
|
|
1465
|
+
process.exit(0);
|
|
1466
|
+
});
|
|
1467
|
+
|
|
1468
|
+
for await (const line of rl) {
|
|
1469
|
+
try {
|
|
1470
|
+
const request = parseJsonWithValidation(line, CLIServerRequestSchema, 'CLI server request');
|
|
1471
|
+
|
|
1472
|
+
// Health check
|
|
1473
|
+
if (request.command === '__ping__') {
|
|
1474
|
+
console.log(
|
|
1475
|
+
JSON.stringify({
|
|
1476
|
+
id: request.id,
|
|
1477
|
+
success: true,
|
|
1478
|
+
message: 'pong',
|
|
1479
|
+
exitCode: 0,
|
|
1480
|
+
duration: 0,
|
|
1481
|
+
}),
|
|
1482
|
+
);
|
|
1483
|
+
continue;
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1486
|
+
// Exit command
|
|
1487
|
+
if (request.command === '__exit__') {
|
|
1488
|
+
console.log(
|
|
1489
|
+
JSON.stringify({
|
|
1490
|
+
id: request.id,
|
|
1491
|
+
success: true,
|
|
1492
|
+
message: 'exiting',
|
|
1493
|
+
exitCode: 0,
|
|
1494
|
+
duration: 0,
|
|
1495
|
+
}),
|
|
1496
|
+
);
|
|
1497
|
+
process.exit(0);
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
// Execute command
|
|
1501
|
+
const startTime = Date.now();
|
|
1502
|
+
const argv = ['bun', 'celilo', ...parseCommand(request.command)];
|
|
1503
|
+
const result = await runCli(argv);
|
|
1504
|
+
const duration = Date.now() - startTime;
|
|
1505
|
+
|
|
1506
|
+
// Send response
|
|
1507
|
+
console.log(
|
|
1508
|
+
JSON.stringify({
|
|
1509
|
+
id: request.id,
|
|
1510
|
+
success: result.success,
|
|
1511
|
+
message: result.success ? result.message : undefined,
|
|
1512
|
+
error: !result.success ? result.error : undefined,
|
|
1513
|
+
details: !result.success ? result.details : undefined,
|
|
1514
|
+
exitCode: result.success ? 0 : 1,
|
|
1515
|
+
duration,
|
|
1516
|
+
}),
|
|
1517
|
+
);
|
|
1518
|
+
} catch (error) {
|
|
1519
|
+
// Parse or execution error
|
|
1520
|
+
console.error(
|
|
1521
|
+
JSON.stringify({
|
|
1522
|
+
id: -1,
|
|
1523
|
+
success: false,
|
|
1524
|
+
error: `Server error: ${error}`,
|
|
1525
|
+
exitCode: 1,
|
|
1526
|
+
duration: 0,
|
|
1527
|
+
}),
|
|
1528
|
+
);
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1533
|
+
/**
|
|
1534
|
+
* Main CLI entry point
|
|
1535
|
+
*
|
|
1536
|
+
* Executes CLI and handles output/exit codes.
|
|
1537
|
+
* If CLI_SERVER_MODE=true, runs in persistent server mode for testing.
|
|
1538
|
+
*/
|
|
1539
|
+
export async function main(): Promise<void> {
|
|
1540
|
+
// Check for server mode
|
|
1541
|
+
if (process.env.CLI_SERVER_MODE === 'true') {
|
|
1542
|
+
await serverMode();
|
|
1543
|
+
return;
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
// Normal single-command execution
|
|
1547
|
+
try {
|
|
1548
|
+
const result = await runCli(process.argv);
|
|
1549
|
+
|
|
1550
|
+
if (result.success) {
|
|
1551
|
+
// For completion mode, output raw text without formatting
|
|
1552
|
+
if (process.argv.includes('--get-completions') || process.argv.includes('completion')) {
|
|
1553
|
+
console.log(result.message);
|
|
1554
|
+
process.exit(0);
|
|
1555
|
+
}
|
|
1556
|
+
|
|
1557
|
+
// Handle multi-line messages: split on section boundaries (\n\n) so
|
|
1558
|
+
// lines within a section are logged together (avoiding clack's per-call spacing).
|
|
1559
|
+
const sections = result.message.split('\n\n');
|
|
1560
|
+
p.log.success(sections[0]);
|
|
1561
|
+
for (let i = 1; i < sections.length; i++) {
|
|
1562
|
+
if (sections[i].trim()) {
|
|
1563
|
+
p.log.message(sections[i]);
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1566
|
+
process.exit(0);
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1569
|
+
p.log.error(`Error: ${result.error}`);
|
|
1570
|
+
if (result.details) {
|
|
1571
|
+
console.error('Details:', result.details);
|
|
1572
|
+
}
|
|
1573
|
+
process.exit(1);
|
|
1574
|
+
} catch (error) {
|
|
1575
|
+
console.error('Fatal error:', error);
|
|
1576
|
+
process.exit(1);
|
|
1577
|
+
}
|
|
1578
|
+
}
|
|
1579
|
+
|
|
1580
|
+
// Execute main only when run directly (not when imported by tests)
|
|
1581
|
+
if (import.meta.main) {
|
|
1582
|
+
await main();
|
|
1583
|
+
}
|