@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.
Files changed (267) hide show
  1. package/README.md +1566 -0
  2. package/bin/celilo +16 -0
  3. package/drizzle/0000_complex_puma.sql +179 -0
  4. package/drizzle/0001_dizzy_wolfpack.sql +2 -0
  5. package/drizzle/0002_web_routes.sql +16 -0
  6. package/drizzle/0003_backup_storage.sql +32 -0
  7. package/drizzle/meta/0000_snapshot.json +1151 -0
  8. package/drizzle/meta/0001_snapshot.json +1167 -0
  9. package/drizzle/meta/0002_snapshot.json +1257 -0
  10. package/drizzle/meta/_journal.json +27 -0
  11. package/package.json +64 -0
  12. package/schemas/system_config.json +106 -0
  13. package/src/__integration__/container-services-cli.integration.test.ts +246 -0
  14. package/src/ansible/dependencies.test.ts +309 -0
  15. package/src/ansible/dependencies.ts +896 -0
  16. package/src/ansible/inventory.test.ts +463 -0
  17. package/src/ansible/inventory.ts +445 -0
  18. package/src/ansible/secrets.ts +222 -0
  19. package/src/ansible/validation.test.ts +92 -0
  20. package/src/ansible/validation.ts +272 -0
  21. package/src/api-clients/digitalocean.ts +94 -0
  22. package/src/api-clients/proxmox.ts +655 -0
  23. package/src/capabilities/logging-wrapper.test.ts +217 -0
  24. package/src/capabilities/lookup.test.ts +149 -0
  25. package/src/capabilities/lookup.ts +89 -0
  26. package/src/capabilities/public-web-helpers.test.ts +198 -0
  27. package/src/capabilities/public-web-publish.test.ts +458 -0
  28. package/src/capabilities/registration.test.ts +395 -0
  29. package/src/capabilities/registration.ts +200 -0
  30. package/src/capabilities/route-validation.test.ts +121 -0
  31. package/src/capabilities/route-validation.ts +96 -0
  32. package/src/capabilities/secret-ref.test.ts +313 -0
  33. package/src/capabilities/secret-validation.ts +157 -0
  34. package/src/capabilities/secrets.test.ts +750 -0
  35. package/src/capabilities/secrets.ts +244 -0
  36. package/src/capabilities/validation.test.ts +613 -0
  37. package/src/capabilities/validation.ts +160 -0
  38. package/src/capabilities/well-known.test.ts +238 -0
  39. package/src/capabilities/well-known.ts +222 -0
  40. package/src/cli/cli.test.ts +654 -0
  41. package/src/cli/command-registry.ts +742 -0
  42. package/src/cli/command-tree-parser.test.ts +180 -0
  43. package/src/cli/command-tree-parser.ts +193 -0
  44. package/src/cli/commands/backup-create.ts +137 -0
  45. package/src/cli/commands/backup-delete.ts +74 -0
  46. package/src/cli/commands/backup-import.ts +97 -0
  47. package/src/cli/commands/backup-list.ts +132 -0
  48. package/src/cli/commands/backup-name.ts +73 -0
  49. package/src/cli/commands/backup-prune.ts +98 -0
  50. package/src/cli/commands/backup-restore.ts +122 -0
  51. package/src/cli/commands/capability-info.ts +121 -0
  52. package/src/cli/commands/capability-list.ts +47 -0
  53. package/src/cli/commands/completion.ts +87 -0
  54. package/src/cli/commands/hook-run.ts +176 -0
  55. package/src/cli/commands/ipam.ts +607 -0
  56. package/src/cli/commands/machine-add.ts +235 -0
  57. package/src/cli/commands/machine-earmark.ts +82 -0
  58. package/src/cli/commands/machine-list.ts +77 -0
  59. package/src/cli/commands/machine-remove.ts +90 -0
  60. package/src/cli/commands/machine-status.ts +131 -0
  61. package/src/cli/commands/module-audit.ts +51 -0
  62. package/src/cli/commands/module-build.ts +60 -0
  63. package/src/cli/commands/module-config.ts +170 -0
  64. package/src/cli/commands/module-deploy.ts +71 -0
  65. package/src/cli/commands/module-generate.ts +236 -0
  66. package/src/cli/commands/module-health.ts +108 -0
  67. package/src/cli/commands/module-import.ts +80 -0
  68. package/src/cli/commands/module-list.ts +43 -0
  69. package/src/cli/commands/module-logs.ts +73 -0
  70. package/src/cli/commands/module-remove.ts +162 -0
  71. package/src/cli/commands/module-show.ts +208 -0
  72. package/src/cli/commands/module-status.ts +131 -0
  73. package/src/cli/commands/module-types.ts +189 -0
  74. package/src/cli/commands/module-upgrade.ts +192 -0
  75. package/src/cli/commands/package.ts +68 -0
  76. package/src/cli/commands/secret-list.ts +99 -0
  77. package/src/cli/commands/secret-set.ts +134 -0
  78. package/src/cli/commands/service-add-digitalocean.ts +133 -0
  79. package/src/cli/commands/service-add-proxmox.ts +342 -0
  80. package/src/cli/commands/service-config-get.ts +83 -0
  81. package/src/cli/commands/service-config-set.ts +145 -0
  82. package/src/cli/commands/service-list.ts +74 -0
  83. package/src/cli/commands/service-reconfigure.ts +230 -0
  84. package/src/cli/commands/service-remove.ts +103 -0
  85. package/src/cli/commands/service-verify.ts +240 -0
  86. package/src/cli/commands/status.ts +216 -0
  87. package/src/cli/commands/storage-add-local.ts +106 -0
  88. package/src/cli/commands/storage-add-s3.ts +114 -0
  89. package/src/cli/commands/storage-list.ts +72 -0
  90. package/src/cli/commands/storage-remove.ts +54 -0
  91. package/src/cli/commands/storage-set-default.ts +44 -0
  92. package/src/cli/commands/storage-verify.ts +54 -0
  93. package/src/cli/commands/system-config.ts +168 -0
  94. package/src/cli/commands/system-init.ts +314 -0
  95. package/src/cli/commands/system-secret-get.ts +98 -0
  96. package/src/cli/commands/system-secret-set.ts +76 -0
  97. package/src/cli/commands/system-vault-password.ts +34 -0
  98. package/src/cli/completion.test.ts +37 -0
  99. package/src/cli/completion.ts +482 -0
  100. package/src/cli/fuel-gauge.test.ts +208 -0
  101. package/src/cli/fuel-gauge.ts +405 -0
  102. package/src/cli/generate-zsh-completion.test.ts +95 -0
  103. package/src/cli/generate-zsh-completion.ts +497 -0
  104. package/src/cli/index.ts +1583 -0
  105. package/src/cli/interactive-config.test.ts +201 -0
  106. package/src/cli/interactive-config.ts +62 -0
  107. package/src/cli/parser.test.ts +227 -0
  108. package/src/cli/parser.ts +244 -0
  109. package/src/cli/prompts.test.ts +33 -0
  110. package/src/cli/prompts.ts +121 -0
  111. package/src/cli/types.ts +38 -0
  112. package/src/cli/validators.test.ts +235 -0
  113. package/src/cli/validators.ts +188 -0
  114. package/src/config/env.ts +41 -0
  115. package/src/config/paths.test.ts +172 -0
  116. package/src/config/paths.ts +108 -0
  117. package/src/db/client.ts +190 -0
  118. package/src/db/migrate.ts +30 -0
  119. package/src/db/schema.test.ts +221 -0
  120. package/src/db/schema.ts +434 -0
  121. package/src/hooks/capability-loader-firewall.test.ts +246 -0
  122. package/src/hooks/capability-loader.test.ts +100 -0
  123. package/src/hooks/capability-loader.ts +520 -0
  124. package/src/hooks/define-hook.test.ts +488 -0
  125. package/src/hooks/executor.test.ts +462 -0
  126. package/src/hooks/executor.ts +469 -0
  127. package/src/hooks/logger.test.ts +54 -0
  128. package/src/hooks/logger.ts +95 -0
  129. package/src/hooks/test-fixtures/failing-hook.ts +13 -0
  130. package/src/hooks/test-fixtures/no-default-hook.ts +6 -0
  131. package/src/hooks/test-fixtures/success-hook.ts +20 -0
  132. package/src/hooks/test-fixtures/unbranded-hook.ts +11 -0
  133. package/src/hooks/test-fixtures/void-hook.ts +13 -0
  134. package/src/hooks/types.ts +89 -0
  135. package/src/infrastructure/property-extractor.test.ts +194 -0
  136. package/src/infrastructure/property-extractor.ts +151 -0
  137. package/src/ipam/allocator.test.ts +442 -0
  138. package/src/ipam/allocator.ts +369 -0
  139. package/src/ipam/auto-allocator.test.ts +247 -0
  140. package/src/ipam/auto-allocator.ts +270 -0
  141. package/src/ipam/subnet-parser.test.ts +107 -0
  142. package/src/ipam/subnet-parser.ts +136 -0
  143. package/src/manifest/contracts/index.ts +61 -0
  144. package/src/manifest/contracts/v1.ts +118 -0
  145. package/src/manifest/json-schema-roundtrip.test.ts +99 -0
  146. package/src/manifest/schema.ts +367 -0
  147. package/src/manifest/template-validator.test.ts +231 -0
  148. package/src/manifest/template-validator.ts +322 -0
  149. package/src/manifest/validate.test.ts +1180 -0
  150. package/src/manifest/validate.ts +415 -0
  151. package/src/module/import.test.ts +355 -0
  152. package/src/module/import.ts +676 -0
  153. package/src/module/packaging/audit.ts +169 -0
  154. package/src/module/packaging/build.ts +228 -0
  155. package/src/module/packaging/checksum.ts +41 -0
  156. package/src/module/packaging/extract.ts +234 -0
  157. package/src/module/packaging/signature.ts +47 -0
  158. package/src/secrets/encryption.test.ts +284 -0
  159. package/src/secrets/encryption.ts +162 -0
  160. package/src/secrets/generators.test.ts +112 -0
  161. package/src/secrets/generators.ts +127 -0
  162. package/src/secrets/master-key.test.ts +159 -0
  163. package/src/secrets/master-key.ts +114 -0
  164. package/src/secrets/storage.test.ts +115 -0
  165. package/src/secrets/storage.ts +106 -0
  166. package/src/secrets/vault.test.ts +35 -0
  167. package/src/secrets/vault.ts +42 -0
  168. package/src/services/backup-create.ts +532 -0
  169. package/src/services/backup-metadata.ts +198 -0
  170. package/src/services/backup-restore.ts +229 -0
  171. package/src/services/backup-retention.ts +84 -0
  172. package/src/services/backup-storage.ts +281 -0
  173. package/src/services/build-stream.test.ts +122 -0
  174. package/src/services/build-stream.ts +201 -0
  175. package/src/services/config-interview.ts +694 -0
  176. package/src/services/container-service.test.ts +298 -0
  177. package/src/services/container-service.ts +401 -0
  178. package/src/services/cross-module-data-manager.test.ts +405 -0
  179. package/src/services/cross-module-data-manager.ts +412 -0
  180. package/src/services/deploy-ansible.ts +88 -0
  181. package/src/services/deploy-planner.ts +153 -0
  182. package/src/services/deploy-preflight.ts +274 -0
  183. package/src/services/deploy-ssh.ts +131 -0
  184. package/src/services/deploy-terraform.test.ts +55 -0
  185. package/src/services/deploy-terraform.ts +445 -0
  186. package/src/services/deploy-validation.ts +311 -0
  187. package/src/services/dns-auto-register.ts +211 -0
  188. package/src/services/health-runner.ts +184 -0
  189. package/src/services/infrastructure-selector.test.ts +485 -0
  190. package/src/services/infrastructure-selector.ts +245 -0
  191. package/src/services/infrastructure-variable-resolver.test.ts +751 -0
  192. package/src/services/infrastructure-variable-resolver.ts +234 -0
  193. package/src/services/machine-detector.ts +328 -0
  194. package/src/services/machine-pool.test.ts +405 -0
  195. package/src/services/machine-pool.ts +316 -0
  196. package/src/services/manifest-validation.ts +120 -0
  197. package/src/services/module-build.test.ts +290 -0
  198. package/src/services/module-build.ts +431 -0
  199. package/src/services/module-config.test.ts +237 -0
  200. package/src/services/module-config.ts +298 -0
  201. package/src/services/module-deploy.ts +862 -0
  202. package/src/services/module-types-drift.test.ts +73 -0
  203. package/src/services/module-types-generator.test.ts +288 -0
  204. package/src/services/module-types-generator.ts +189 -0
  205. package/src/services/proxmox-state-recovery.ts +140 -0
  206. package/src/services/schema-validation.ts +155 -0
  207. package/src/services/secret-schema-loader.test.ts +311 -0
  208. package/src/services/secret-schema-loader.ts +239 -0
  209. package/src/services/ssh-key-manager.test.ts +283 -0
  210. package/src/services/ssh-key-manager.ts +193 -0
  211. package/src/services/storage-providers/local.ts +105 -0
  212. package/src/services/storage-providers/s3.ts +182 -0
  213. package/src/services/storage-providers/types.ts +24 -0
  214. package/src/services/system-config-schema-types.ts +25 -0
  215. package/src/services/system-config-validator.test.ts +160 -0
  216. package/src/services/system-config-validator.ts +74 -0
  217. package/src/services/system-init.test.ts +153 -0
  218. package/src/services/system-init.ts +253 -0
  219. package/src/services/terraform-safety.ts +174 -0
  220. package/src/services/zone-detector.test.ts +110 -0
  221. package/src/services/zone-detector.ts +102 -0
  222. package/src/services/zone-policy.test.ts +97 -0
  223. package/src/services/zone-policy.ts +126 -0
  224. package/src/templates/generator.test.ts +645 -0
  225. package/src/templates/generator.ts +1119 -0
  226. package/src/templates/types.ts +62 -0
  227. package/src/test-utils/INTERACTIVE_PROMPTS.md +167 -0
  228. package/src/test-utils/cli-context-interactive.test.ts +152 -0
  229. package/src/test-utils/cli-context-server.test.ts +66 -0
  230. package/src/test-utils/cli-context.test.ts +273 -0
  231. package/src/test-utils/cli-context.ts +677 -0
  232. package/src/test-utils/cli-result.test.ts +282 -0
  233. package/src/test-utils/cli-result.ts +241 -0
  234. package/src/test-utils/cli.ts +55 -0
  235. package/src/test-utils/completion-harness.test.ts +126 -0
  236. package/src/test-utils/completion-harness.ts +82 -0
  237. package/src/test-utils/database.test.ts +182 -0
  238. package/src/test-utils/database.ts +126 -0
  239. package/src/test-utils/filesystem.test.ts +208 -0
  240. package/src/test-utils/filesystem.ts +142 -0
  241. package/src/test-utils/fixtures.test.ts +123 -0
  242. package/src/test-utils/fixtures.ts +160 -0
  243. package/src/test-utils/golden-diff.ts +197 -0
  244. package/src/test-utils/index.ts +77 -0
  245. package/src/test-utils/integration.ts +81 -0
  246. package/src/test-utils/module-fixtures.ts +468 -0
  247. package/src/test-utils/modules.test.ts +144 -0
  248. package/src/test-utils/modules.ts +183 -0
  249. package/src/test-utils/setup-test-db.ts +90 -0
  250. package/src/test-utils/value-extractor.test.ts +231 -0
  251. package/src/test-utils/value-extractor.ts +228 -0
  252. package/src/types/infrastructure.ts +157 -0
  253. package/src/utils/shell.test.ts +365 -0
  254. package/src/utils/shell.ts +159 -0
  255. package/src/validation/schemas.ts +166 -0
  256. package/src/variables/ansible-resolver.test.ts +142 -0
  257. package/src/variables/ansible-resolver.ts +69 -0
  258. package/src/variables/capability-self-ref.test.ts +220 -0
  259. package/src/variables/context.test.ts +1265 -0
  260. package/src/variables/context.ts +624 -0
  261. package/src/variables/declarative-derivation.test.ts +743 -0
  262. package/src/variables/declarative-derivation.ts +200 -0
  263. package/src/variables/parser.test.ts +231 -0
  264. package/src/variables/parser.ts +76 -0
  265. package/src/variables/resolver.test.ts +458 -0
  266. package/src/variables/resolver.ts +282 -0
  267. package/src/variables/types.ts +59 -0
package/README.md ADDED
@@ -0,0 +1,1566 @@
1
+ # Celilo Backend
2
+
3
+ Phase 0 implementation of Celilo's backend services.
4
+
5
+ ## Phase 0 Scope
6
+
7
+ CLI-only interface for:
8
+ - Module import and validation
9
+ - Manifest validation
10
+ - Variable resolution
11
+ - Template generation
12
+ - Secret management
13
+ - **Zero-configuration modules** (Phase 0 Part 4):
14
+ - Automatic hostname/zone assignment from well-known capabilities
15
+ - Automatic VMID/IP allocation (IPAM)
16
+ - Automatic network config derivation from zones
17
+ - Automatic VM resource defaults from manifests
18
+
19
+ ## Project Structure
20
+
21
+ ```
22
+ celilo/backend/
23
+ ├── src/
24
+ │ ├── db/
25
+ │ │ ├── schema.ts # Drizzle ORM schema
26
+ │ │ ├── schema.test.ts # Schema tests
27
+ │ │ ├── client.ts # Database connection
28
+ │ │ └── migrate.ts # Migration runner
29
+ │ ├── manifest/
30
+ │ │ ├── schema.ts # Zod schemas for manifest validation
31
+ │ │ ├── validate.ts # Manifest validation logic
32
+ │ │ └── validate.test.ts # Validation tests
33
+ │ ├── module/
34
+ │ │ ├── import.ts # Module import logic
35
+ │ │ └── import.test.ts # Import tests
36
+ │ ├── variables/
37
+ │ │ ├── types.ts # Variable resolution types
38
+ │ │ ├── parser.ts # Variable parsing logic
39
+ │ │ ├── parser.test.ts # Parser tests
40
+ │ │ ├── resolver.ts # Variable resolution logic
41
+ │ │ ├── resolver.test.ts # Resolver tests
42
+ │ │ ├── context.ts # Resolution context builder
43
+ │ │ └── context.test.ts # Context tests
44
+ │ ├── secrets/
45
+ │ │ ├── master-key.ts # Master key generation and management
46
+ │ │ ├── master-key.test.ts # Master key tests
47
+ │ │ ├── encryption.ts # AES-256-GCM encryption/decryption
48
+ │ │ └── encryption.test.ts # Encryption tests
49
+ │ ├── templates/
50
+ │ │ ├── types.ts # Template generation types
51
+ │ │ ├── generator.ts # Template generation logic
52
+ │ │ └── generator.test.ts # Generation tests
53
+ │ └── index.ts # Entry point
54
+ ├── drizzle/ # Generated migrations
55
+ ├── package.json
56
+ ├── tsconfig.json
57
+ ├── biome.json # Linting/formatting config
58
+ └── drizzle.config.ts # Drizzle Kit config
59
+ ```
60
+
61
+ ## Database Schema
62
+
63
+ ### Tables
64
+
65
+ **`modules`** - Module metadata and manifest data
66
+ - `id` (TEXT, PK) - Module identifier
67
+ - `name` (TEXT) - Display name
68
+ - `version` (TEXT) - Semantic version
69
+ - `description` (TEXT) - Optional description
70
+ - `state` (TEXT) - Lifecycle state (IMPORTED, VALIDATED, CONFIGURED, etc.)
71
+ - `manifest_data` (JSON) - Full manifest content
72
+ - `source_path` (TEXT) - Original import path
73
+ - `imported_at` (TIMESTAMP) - Import timestamp
74
+ - `updated_at` (TIMESTAMP) - Last update timestamp
75
+ - `error_message` (TEXT) - Error details if state is ERROR
76
+
77
+ **`module_configs`** - User configuration key-value pairs
78
+ - `id` (INTEGER, PK, AUTO)
79
+ - `module_id` (TEXT, FK → modules.id) - Module reference
80
+ - `key` (TEXT) - Configuration key
81
+ - `value` (TEXT) - Configuration value
82
+ - `created_at` (TIMESTAMP)
83
+ - `updated_at` (TIMESTAMP)
84
+
85
+ **`capabilities`** - Registered capabilities from modules
86
+ - `id` (INTEGER, PK, AUTO)
87
+ - `module_id` (TEXT, FK → modules.id) - Provider module
88
+ - `capability_name` (TEXT) - Capability identifier (e.g., dns_external)
89
+ - `version` (TEXT) - Capability version
90
+ - `data` (JSON) - Capability data (nameserver, zone, etc.)
91
+ - `registered_at` (TIMESTAMP)
92
+
93
+ **`secrets`** - Encrypted secrets per module
94
+ - `id` (INTEGER, PK, AUTO)
95
+ - `module_id` (TEXT, FK → modules.id) - Module reference
96
+ - `name` (TEXT) - Secret name
97
+ - `encrypted_value` (TEXT) - AES-256-GCM encrypted value
98
+ - `iv` (TEXT) - Initialization vector
99
+ - `auth_tag` (TEXT) - Authentication tag
100
+ - `created_at` (TIMESTAMP)
101
+ - `updated_at` (TIMESTAMP)
102
+
103
+ All tables use CASCADE DELETE when module is removed.
104
+
105
+ ## Dependencies
106
+
107
+ ### Required
108
+
109
+ - **Bun** (v1.0+) - JavaScript runtime
110
+ - **Ansible** (v2.9+) - Required for Ansible Vault secret encryption
111
+ - **Terraform** (v1.0+) - Required for validating generated Terraform code
112
+
113
+ See [../SETUP.md](../SETUP.md) for installation instructions.
114
+
115
+ ## Setup
116
+
117
+ ```bash
118
+ # Install dependencies
119
+ bun install
120
+
121
+ # Generate migrations (if schema changed)
122
+ bunx drizzle-kit generate
123
+
124
+ # Run migrations
125
+ bun run db:migrate
126
+
127
+ # Run tests
128
+ bun test # Unit tests (216 tests)
129
+ bun run test:integration # Integration tests (56 tests)
130
+
131
+ # Lint and format
132
+ bun run lint:fix
133
+ bun run format
134
+ ```
135
+
136
+ ## Development
137
+
138
+ ### Common Development Workflows
139
+
140
+ **Full development cycle**:
141
+ ```bash
142
+ # 1. Make code changes
143
+ vim src/module/import.ts
144
+
145
+ # 2. Run unit tests (< 1s)
146
+ bun test src/module/import.test.ts
147
+
148
+ # 3. Run all unit tests (includes zero-config integration tests)
149
+ bun test
150
+
151
+ # 4. Run CLI-based integration tests (10-20s)
152
+ bun run test:integration
153
+
154
+ # 5. Before commit: Run all tests
155
+ bun test && bun run test:integration
156
+
157
+ # 6. Before major release: Run slow tests (3-5min)
158
+ bun run test:integration-slow
159
+ ```
160
+
161
+ **Working with the CLI**:
162
+ ```bash
163
+ # Use the wrapper script (works from any directory)
164
+ ../../celilo module list
165
+
166
+ # Or run directly from backend
167
+ bun run src/cli/index.ts module list
168
+
169
+ # Or use bun link for system-wide command
170
+ bun link
171
+ celilo module list # From anywhere
172
+ ```
173
+
174
+ **Testing module changes**:
175
+ ```bash
176
+ # 1. Import test module
177
+ bun run src/cli/index.ts module import ../../modules/test-module
178
+
179
+ # 2. Configure
180
+ bun run src/cli/index.ts module config set test-module hostname test-app
181
+ bun run src/cli/index.ts module config set test-module container_ip 10.0.20.100
182
+
183
+ # 3. Generate
184
+ bun run src/cli/index.ts module generate test-module
185
+
186
+ # 4. Inspect generated files
187
+ ls -la /tmp/celilo/modules/test-module/generated/
188
+
189
+ # 5. Clean up
190
+ bun run src/cli/index.ts module remove test-module
191
+ ```
192
+
193
+ **Working with secrets**:
194
+ ```bash
195
+ # Set module secret
196
+ bun run src/cli/index.ts secret set homebridge api_key test-key-123
197
+
198
+ # Get Ansible Vault password (for inspecting secrets.yml)
199
+ bun run src/cli/index.ts system vault-password
200
+
201
+ # View encrypted Ansible secrets
202
+ ansible-vault view /tmp/celilo/modules/homebridge/generated/ansible/inventory/secrets.yml \
203
+ --vault-password-file=<(bun run src/cli/index.ts system vault-password)
204
+ ```
205
+
206
+ **Zero-Configuration Workflow** (Phase 0 Part 4):
207
+ ```bash
208
+ # One-time system setup: Configure network zones
209
+ celilo system config set network.dmz.subnet "10.0.10.0/24"
210
+ celilo system config set network.dmz.gateway "10.0.10.1"
211
+ celilo system config set network.dmz.vlan "10"
212
+ celilo system config set network.dmz.bridge "vmbr0"
213
+
214
+ # Import module with well-known capability (e.g., Caddy with public_web)
215
+ celilo module import ./modules/caddy
216
+
217
+ # Configure ONLY app-specific settings (infrastructure auto-configured!)
218
+ celilo module config set caddy domain "example.com"
219
+
220
+ # Generate - everything else is automatic!
221
+ celilo module generate caddy
222
+
223
+ # Debug: Show all configuration (user + auto-derived)
224
+ celilo module show-config caddy
225
+
226
+ # Debug: Show zone and network settings
227
+ celilo module show-zone caddy
228
+
229
+ # IPAM Management: Reserve VMIDs/IPs for existing infrastructure
230
+ celilo ipam vmid reserve 2100 --reason "Existing Proxmox VM"
231
+ celilo ipam ip reserve 10.0.10.1-10.0.10.9 --zone dmz --reason "Infrastructure"
232
+
233
+ # IPAM Management: List allocations and reservations
234
+ celilo ipam list-allocations
235
+ celilo ipam vmid list-reservations
236
+ celilo ipam ip list-reservations
237
+ ```
238
+
239
+ **What Gets Auto-Configured**:
240
+ - ✅ **Hostname** - From well-known capabilities (e.g., `public_web` → `www`)
241
+ - ✅ **Zone** - From well-known capabilities (e.g., `public_web` → `dmz`)
242
+ - ✅ **VMID** - Auto-allocated from 2100+ (IPAM)
243
+ - ✅ **Container IP** - Auto-allocated from zone subnet (IPAM)
244
+ - ✅ **Gateway** - From `network.{zone}.gateway` system config
245
+ - ✅ **VLAN** - From `network.{zone}.vlan` system config
246
+ - ✅ **Subnet** - From `network.{zone}.subnet` system config
247
+ - ✅ **Bridge** - From `network.{zone}.bridge` system config
248
+ - ✅ **VM Resources** - cores, memory, disk, storage from manifest defaults
249
+
250
+ See [../../design/MODULE_DEVELOPMENT_GUIDE.md](../../design/MODULE_DEVELOPMENT_GUIDE.md) for complete zero-config documentation.
251
+
252
+ ### Database Location
253
+
254
+ **Default locations**:
255
+ - **macOS**: `~/Library/Application Support/celilo/celilo.db`
256
+ - **Linux**: `/var/lib/celilo/celilo.db`
257
+ - **Development** (`ENVIRONMENT=dev`): `./celilo-data/celilo.db`
258
+
259
+ **Override**: Set `CELILO_DB_PATH` environment variable to use a custom location.
260
+
261
+ **Override base directory**: Set `CELILO_DATA_DIR` to change where all Celilo data (including database) is stored.
262
+
263
+ ### Testing Strategy
264
+
265
+ **Three test tiers** (see [TESTING_STRATEGY.md](../../design/TESTING_STRATEGY.md) for details):
266
+
267
+ **Tier 1: Unit Tests** (< 1 second)
268
+ ```bash
269
+ bun test # Run all unit tests (494 tests)
270
+ bun test schema.test.ts # Run specific test file
271
+ bun test --watch # Watch mode for active development
272
+ ```
273
+
274
+ **Tier 2: Fast Integration Tests** (10-20 seconds)
275
+ ```bash
276
+ bun run test:integration # All integration tests (run via bun test)
277
+
278
+ # Coverage:
279
+ # - Module import/validation via CLI
280
+ # - Configuration management
281
+ # - Template generation
282
+ # - Ansible/Terraform generation
283
+ # - Well-known capability auto-assignment
284
+ # - IPAM auto-allocation
285
+ # - Zone-based networking
286
+ # - VM resource defaults
287
+
288
+ # What gets skipped:
289
+ # - Nix builds
290
+ # - Docker builds
291
+ # - Slow module builds (Caddy)
292
+ ```
293
+
294
+ **Tier 3: Slow Integration Tests** (3-5 minutes)
295
+ ```bash
296
+ bun run test:integration-slow # Full builds + end-to-end
297
+
298
+ # Includes everything from fast tests PLUS:
299
+ # - Full Nix builds (Caddy with modules)
300
+ # - Docker builds
301
+ # - Complete module packaging
302
+ ```
303
+
304
+ **Before committing** (MANDATORY per Rule 7.4):
305
+ ```bash
306
+ bun test && bun run test:integration
307
+ ```
308
+
309
+ **Before releasing**:
310
+ ```bash
311
+ bun test && bun run test:integration && bun run test:integration-slow
312
+ ```
313
+
314
+ ### Database Studio
315
+
316
+ View database contents with Drizzle Studio:
317
+
318
+ ```bash
319
+ bun run db:studio
320
+ ```
321
+
322
+ Opens web interface at http://localhost:4983
323
+
324
+ ### Debugging
325
+
326
+ **Enable verbose output**:
327
+ ```bash
328
+ # Set log level
329
+ export CONDUCTOR_LOG_LEVEL=debug
330
+ bun run src/cli/index.ts module import ./path
331
+
332
+ # Or use Bun's debugger
333
+ bun --inspect run src/cli/index.ts module import ./path
334
+ ```
335
+
336
+ **Inspect database**:
337
+ ```bash
338
+ # Use Drizzle Studio
339
+ bun run db:studio
340
+
341
+ # Or sqlite3 directly
342
+ sqlite3 celilo.db
343
+ sqlite> .tables
344
+ sqlite> SELECT * FROM modules;
345
+ sqlite> SELECT * FROM module_configs WHERE module_id = 'homebridge';
346
+ ```
347
+
348
+ **Check generated files**:
349
+ ```bash
350
+ # Generated files location
351
+ ls -la /tmp/celilo/modules/<module-id>/generated/
352
+
353
+ # View Terraform
354
+ cat /tmp/celilo/modules/<module-id>/generated/terraform/main.tf
355
+
356
+ # View Ansible
357
+ cat /tmp/celilo/modules/<module-id>/generated/ansible/playbook.yml
358
+
359
+ # Check encrypted secrets
360
+ ansible-vault view /tmp/celilo/modules/<module-id>/generated/ansible/inventory/secrets.yml \
361
+ --vault-password-file=<(bun run src/cli/index.ts system vault-password)
362
+ ```
363
+
364
+ ## Architecture Decisions
365
+
366
+ ### Why Drizzle ORM?
367
+
368
+ - Type-safe schema-first approach
369
+ - Excellent TypeScript inference
370
+ - Lightweight (no heavy runtime)
371
+ - SQL-like API (easier for developers familiar with SQL)
372
+
373
+ ### Why SQLite?
374
+
375
+ - Single-file database (easy backup/restore)
376
+ - No separate server process needed
377
+ - Sufficient for home lab scale
378
+ - ACID compliant with WAL mode
379
+
380
+ ### Foreign Key Cascades
381
+
382
+ All child tables (configs, capabilities, secrets) cascade delete when parent module is removed. This ensures no orphaned data.
383
+
384
+ ### JSON Columns
385
+
386
+ `manifest_data` and capability `data` use JSON columns for flexibility. These are validated with Zod schemas in application code.
387
+
388
+ ## Manifest Validation
389
+
390
+ ### Schema (`src/manifest/schema.ts`)
391
+
392
+ Zod schemas for validating module manifest structure:
393
+
394
+ - **ModuleManifestSchema** - Complete manifest validation
395
+ - **VariableDeclareSchema** - Variable declarations
396
+ - **VariableUseSchema** - Variable usage from capabilities
397
+ - **CapabilityRequirementSchema** - Required capabilities
398
+ - **CapabilityProviderSchema** - Provided capabilities
399
+ - **LifecycleHookSchema** - Lifecycle hooks (on_install, health_check, etc.)
400
+
401
+ ### Validation Functions (`src/manifest/validate.ts`)
402
+
403
+ **`validateManifest(yamlContent: string): ValidationResult`**
404
+ - Parses YAML and validates against schema
405
+ - Returns success with typed manifest or errors with path/message
406
+ - Policy function - no side effects
407
+
408
+ **`validateCapabilityRequirements(manifest, availableCapabilities): ValidationError | null`**
409
+ - Checks that all required capabilities exist in system
410
+ - Caller provides list of available capabilities (from database)
411
+
412
+ **`validateVariableSources(manifest): ValidationError | null`**
413
+ - Validates that capability references in variables match required capabilities
414
+ - Ensures consistency between `requires.capabilities` and `variables.uses`
415
+
416
+ ### Example Usage
417
+
418
+ ```typescript
419
+ import { validateManifest } from './manifest/validate';
420
+
421
+ const yamlContent = await readFile('./modules/homebridge/manifest.yml', 'utf-8');
422
+ const result = validateManifest(yamlContent);
423
+
424
+ if (!result.success) {
425
+ console.error('Validation errors:', result.errors);
426
+ return;
427
+ }
428
+
429
+ const manifest = result.data;
430
+ console.log(`Module: ${manifest.name} v${manifest.version}`);
431
+ ```
432
+
433
+ ## Module Import
434
+
435
+ ### Import Functions (`src/module/import.ts`)
436
+
437
+ Following Rule 10.1, functions are separated by responsibility:
438
+
439
+ **Policy Functions** (validation, no side effects):
440
+ - **`validateModuleDirectory(sourcePath)`** - Checks directory exists and has manifest.yml
441
+ - **`readModuleManifest(sourcePath)`** - Reads and validates manifest from YAML
442
+
443
+ **Execution Functions** (perform I/O):
444
+ - **`copyModuleFiles(sourcePath, targetPath)`** - Recursively copies all module files
445
+ - **`insertModuleToDb(manifest, targetPath, db?)`** - Inserts module record to database
446
+ - **`moduleExists(moduleId, db?)`** - Checks if module already exists
447
+
448
+ **Orchestration Function**:
449
+ - **`importModule(options)`** - Main entry point, coordinates all steps
450
+
451
+ ### Import Process
452
+
453
+ 1. **Validate** directory structure and manifest
454
+ 2. **Check** module doesn't already exist
455
+ 3. **Copy** files to target location (`/tmp/celilo/modules/{module-id}/`)
456
+ 4. **Insert** module record to database with state `IMPORTED`
457
+
458
+ ### Example Usage
459
+
460
+ ```typescript
461
+ import { importModule } from './module/import';
462
+
463
+ const result = await importModule({
464
+ sourcePath: './modules/homebridge',
465
+ targetBasePath: '/data/celilo/modules',
466
+ });
467
+
468
+ if (!result.success) {
469
+ console.error('Import failed:', result.error);
470
+ return;
471
+ }
472
+
473
+ console.log(`Imported module: ${result.moduleId}`);
474
+ console.log(`Files copied to: ${result.targetPath}`);
475
+ ```
476
+
477
+ ### Dependency Injection
478
+
479
+ Functions accept optional `db` parameter for testing (Rule 2.3):
480
+
481
+ ```typescript
482
+ // Production - uses global singleton
483
+ const exists = moduleExists('homebridge');
484
+
485
+ // Testing - inject test database
486
+ const testDb = createDbClient({ path: './test.db' });
487
+ const exists = moduleExists('homebridge', testDb);
488
+ ```
489
+
490
+ ## Variable Resolution
491
+
492
+ ### System Overview (`src/variables/`)
493
+
494
+ The variable resolution system follows Rule 10.1 with clear separation of concerns:
495
+
496
+ **Parser** (`parser.ts`) - Policy function:
497
+ - Extracts variable references from template strings
498
+ - Pattern: `$type:path.to.value`
499
+ - Validates variable format
500
+
501
+ **Resolver** (`resolver.ts`) - Policy + Orchestration:
502
+ - `resolveVariable()` - Resolves single variable from context
503
+ - `resolveTemplate()` - Resolves all variables in template
504
+
505
+ **Context** (`context.ts`) - Execution function:
506
+ - `buildResolutionContext()` - Fetches data from database
507
+ - `buildContextFromData()` - Creates context from explicit data (testing)
508
+
509
+ ### Variable Types
510
+
511
+ **`$self:path`** - Module's own configuration
512
+ - Example: `$self:container_ip` → `192.168.0.50`
513
+ - Source: `module_configs` table
514
+
515
+ **`$system:path`** - System-wide configuration
516
+ - Example: `$system:management.ip` → `192.168.0.10`
517
+ - Source: Hardcoded defaults (Phase 0), database (Phase 1+)
518
+
519
+ **`$secret:name`** - Encrypted secrets
520
+ - Example: `$secret:api_key` → `decrypted_value`
521
+ - Source: `secrets` table (Phase 0: plaintext, Phase 0.5: encrypted)
522
+
523
+ **`$capability:name.path`** - Capability provider data
524
+ - Example: `$capability:dns_external.nameserver` → `ns1.example.com`
525
+ - Source: `capabilities` table
526
+ - Format: `capability_name.data.path`
527
+
528
+ ### Example Usage
529
+
530
+ ```typescript
531
+ import { buildResolutionContext, resolveTemplate } from './variables';
532
+
533
+ // Build context from database
534
+ const context = await buildResolutionContext('homebridge');
535
+
536
+ // Resolve template
537
+ const template = `
538
+ hostname: $self:hostname
539
+ ip: $self:container_ip
540
+ gateway: $system:management.ip
541
+ dns: $capability:dns_external.nameserver
542
+ api_key: $secret:api_key
543
+ `;
544
+
545
+ const result = resolveTemplate(template, context);
546
+
547
+ if (result.success) {
548
+ console.log(result.content);
549
+ // hostname: homebridge
550
+ // ip: 192.168.0.50
551
+ // gateway: 192.168.0.10
552
+ // dns: ns1.example.com
553
+ // api_key: secret123
554
+ } else {
555
+ console.error('Resolution errors:', result.errors);
556
+ }
557
+ ```
558
+
559
+ ### Resolution Process
560
+
561
+ 1. **Parse** - Extract all variable references from template
562
+ 2. **Resolve** - For each variable:
563
+ - Determine type (`self`, `system`, `secret`, `capability`)
564
+ - Lookup value from appropriate data source in context
565
+ - Handle nested paths for capabilities
566
+ 3. **Replace** - Substitute all variables with resolved values
567
+ 4. **Return** - Success with content or errors with details
568
+
569
+ ### Testing
570
+
571
+ All functions are testable without database:
572
+
573
+ ```typescript
574
+ import { buildContextFromData, resolveTemplate } from './variables';
575
+
576
+ const context = buildContextFromData('test-module', {
577
+ selfConfig: { ip: '192.168.0.50' },
578
+ secrets: { key: 'secret' },
579
+ capabilities: { dns: { server: 'ns1' } },
580
+ });
581
+
582
+ const result = resolveTemplate('ip: $self:ip', context);
583
+ // result.success === true
584
+ // result.content === 'ip: 192.168.0.50'
585
+ ```
586
+
587
+ ## Secret Encryption
588
+
589
+ ### System Overview (`src/secrets/`)
590
+
591
+ AES-256-GCM encryption for module secrets following Rule 10.1 separation:
592
+
593
+ **Master Key** (`master-key.ts`):
594
+ - `generateMasterKey()` - Policy: Generates 32-byte key
595
+ - `isValidMasterKey()` - Policy: Validates key format
596
+ - `writeMasterKey()` - Execution: Writes to file with 0600 permissions
597
+ - `readMasterKey()` - Execution: Reads from file
598
+ - `getOrCreateMasterKey()` - Orchestration: Ensures key exists
599
+ - `masterKeyExists()` - Check if key file exists
600
+
601
+ **Encryption** (`encryption.ts`):
602
+ - `encryptSecret()` - Policy: AES-256-GCM encryption with random IV
603
+ - `decryptSecret()` - Policy: Decrypts with key verification
604
+ - `isValidEncryptedSecret()` - Policy: Validates encrypted data format
605
+
606
+ ### Encryption Details
607
+
608
+ **Algorithm**: AES-256-GCM (Authenticated Encryption with Associated Data)
609
+ - **Key**: 256 bits (32 bytes)
610
+ - **IV**: 128 bits (16 bytes) - randomly generated per encryption
611
+ - **Auth Tag**: 128 bits (16 bytes) - ensures integrity
612
+
613
+ **Storage Format**:
614
+ ```typescript
615
+ {
616
+ encryptedValue: string, // Hex-encoded ciphertext
617
+ iv: string, // Hex-encoded initialization vector
618
+ authTag: string // Hex-encoded authentication tag
619
+ }
620
+ ```
621
+
622
+ ### Master Key Management
623
+
624
+ **Location**:
625
+ - Development: `/tmp/celilo/master.key` (configurable via `CELILO_MASTER_KEY_PATH`)
626
+ - Production: `/etc/celilo/master.key`
627
+
628
+ **Generation**:
629
+ ```typescript
630
+ import { getOrCreateMasterKey } from './secrets/master-key';
631
+
632
+ // Automatically generates if missing
633
+ const masterKey = await getOrCreateMasterKey();
634
+ ```
635
+
636
+ **File Permissions**: 0600 (owner read/write only)
637
+
638
+ ### Example Usage
639
+
640
+ ```typescript
641
+ import { getOrCreateMasterKey } from './secrets/master-key';
642
+ import { encryptSecret, decryptSecret } from './secrets/encryption';
643
+
644
+ // Get master key (generates if missing)
645
+ const masterKey = await getOrCreateMasterKey();
646
+
647
+ // Encrypt a secret
648
+ const encrypted = encryptSecret('my-api-key-12345', masterKey);
649
+ console.log(encrypted);
650
+ // {
651
+ // encryptedValue: '4a3b2c1d...',
652
+ // iv: 'f1e2d3c4...',
653
+ // authTag: 'a9b8c7d6...'
654
+ // }
655
+
656
+ // Decrypt the secret
657
+ const plaintext = decryptSecret(encrypted, masterKey);
658
+ console.log(plaintext); // 'my-api-key-12345'
659
+ ```
660
+
661
+ ### Security Features
662
+
663
+ 1. **Authenticated Encryption**: GCM mode provides both confidentiality and integrity
664
+ 2. **Random IVs**: Each encryption uses unique IV, same plaintext → different ciphertext
665
+ 3. **Key Validation**: Master key length enforced (32 bytes)
666
+ 4. **Tamper Detection**: Auth tag verification prevents corrupted/modified ciphertext
667
+ 5. **Fail-Fast**: Invalid keys or corrupted data throw clear errors
668
+
669
+ ### Error Handling
670
+
671
+ ```typescript
672
+ try {
673
+ const decrypted = decryptSecret(encrypted, masterKey);
674
+ } catch (error) {
675
+ // Possible errors:
676
+ // - "Master key must be 32 bytes"
677
+ // - "Invalid encrypted secret: missing required fields"
678
+ // - "Invalid IV length: expected 16, got N"
679
+ // - "Failed to decrypt secret: [reason]" (wrong key, corrupted data)
680
+ }
681
+ ```
682
+
683
+ ### Testing
684
+
685
+ All functions are pure (no global state):
686
+
687
+ ```typescript
688
+ import { generateMasterKey, encryptSecret, decryptSecret } from './secrets';
689
+
690
+ const masterKey = generateMasterKey();
691
+ const encrypted = encryptSecret('test-secret', masterKey);
692
+ const decrypted = decryptSecret(encrypted, masterKey);
693
+ // decrypted === 'test-secret'
694
+ ```
695
+
696
+ ## Ansible Vault Integration
697
+
698
+ ### System Overview (`src/ansible/`, `src/secrets/vault.ts`)
699
+
700
+ Celilo uses Ansible Vault to encrypt secrets in generated Ansible configurations, ensuring no plaintext secrets are written to disk.
701
+
702
+ **Key Components**:
703
+ - **`vault.ts`** - Derives vault password from master key (deterministic)
704
+ - **`ansible/secrets.ts`** - Generates and encrypts secrets.yml file
705
+ - **`ansible-resolver.ts`** - Converts `$secret:` variables to Jinja2 `{{ }}` syntax
706
+
707
+ ### Encryption Flow
708
+
709
+ 1. **Decrypt secrets** from database (using master key)
710
+ 2. **Format as YAML** with all module secrets
711
+ 3. **Derive vault password** from master key (SHA-256)
712
+ 4. **Encrypt with ansible-vault** (AES-256)
713
+ 5. **Write encrypted file** to `ansible/inventory/secrets.yml`
714
+
715
+ ### Vault Password Derivation
716
+
717
+ ```typescript
718
+ import { deriveVaultPassword, getVaultPassword } from './secrets/vault';
719
+
720
+ // Deterministic derivation from master key
721
+ const masterKey = await getOrCreateMasterKey();
722
+ const vaultPassword = deriveVaultPassword(masterKey);
723
+ // Same master key always produces same vault password
724
+
725
+ // Or use orchestration function
726
+ const vaultPassword = await getVaultPassword();
727
+ ```
728
+
729
+ **Algorithm**: `SHA-256(concat("ansible-vault:", masterKey))`
730
+
731
+ **Properties**:
732
+ - Deterministic (same master key → same vault password)
733
+ - Domain-separated (prevents key reuse attacks)
734
+ - Standard hash output (64 hex characters)
735
+
736
+ ### Generated Ansible Structure
737
+
738
+ **Playbook** (`playbook.yml`):
739
+ ```yaml
740
+ ---
741
+ - name: Deploy Module
742
+ hosts: module-host
743
+ become: true
744
+
745
+ vars_files:
746
+ - inventory/secrets.yml # Encrypted with ansible-vault
747
+
748
+ roles:
749
+ - module-role
750
+ ```
751
+
752
+ **Secrets File** (`inventory/secrets.yml`):
753
+ ```
754
+ $ANSIBLE_VAULT;1.1;AES256
755
+ 35646166616130633832363334383234306139626264373935623630393937313639623334356138
756
+ 6561383035303565323364373934306632336461626562630a386237663365633039396631623639
757
+ ...
758
+ ```
759
+
760
+ **Templates** (e.g., `templates/config.json`):
761
+ ```json
762
+ {
763
+ "api_key": "{{ api_key }}",
764
+ "password": "{{ db_password }}"
765
+ }
766
+ ```
767
+
768
+ ### Variable Resolution
769
+
770
+ Ansible templates use different resolution from Terraform:
771
+
772
+ **Terraform** (resolved at generation time):
773
+ - `$self:hostname` → `"myhost"`
774
+ - `$system:dns.primary` → `"192.168.0.1"`
775
+ - `$secret:api_key` → `"actual_secret_value"` (**plaintext in file!**)
776
+
777
+ **Ansible** (resolved at runtime):
778
+ - `$self:hostname` → `"myhost"` (resolved at generation)
779
+ - `$system:dns.primary` → `"192.168.0.1"` (resolved at generation)
780
+ - `$secret:api_key` → `{{ api_key }}` (Jinja2 variable, resolved from encrypted secrets.yml)
781
+
782
+ ### CLI Usage
783
+
784
+ **Get vault password**:
785
+ ```bash
786
+ # Display vault password
787
+ celilo system vault-password
788
+
789
+ # Use with ansible-vault
790
+ ansible-vault view inventory/secrets.yml \
791
+ --vault-password-file=<(celilo system vault-password)
792
+
793
+ # Edit encrypted secrets
794
+ ansible-vault edit inventory/secrets.yml \
795
+ --vault-password-file=<(celilo system vault-password)
796
+ ```
797
+
798
+ **Run Ansible playbook**:
799
+ ```bash
800
+ cd /tmp/celilo/modules/homebridge/generated/ansible
801
+
802
+ # Option 1: Process substitution (recommended)
803
+ ansible-playbook playbook.yml \
804
+ --vault-password-file=<(celilo system vault-password)
805
+
806
+ # Option 2: Environment variable
807
+ export ANSIBLE_VAULT_PASSWORD=$(celilo system vault-password)
808
+ echo "$ANSIBLE_VAULT_PASSWORD" | ansible-playbook playbook.yml --vault-password-file=/dev/stdin
809
+ ```
810
+
811
+ ### Security Considerations
812
+
813
+ **Benefits**:
814
+ - ✅ No plaintext secrets on disk
815
+ - ✅ Industry-standard encryption (Ansible Vault AES-256)
816
+ - ✅ Deterministic (same master key → same vault password)
817
+ - ✅ Works with standard Ansible tooling
818
+ - ✅ Secrets encrypted independently from celilo database
819
+
820
+ **Requirements**:
821
+ - ⚠️ **Ansible must be installed** - Generation fails without ansible-vault
822
+ - ⚠️ Master key must be protected (file permissions 0600)
823
+ - ⚠️ Vault password derivation is deterministic (anyone with master key can decrypt)
824
+
825
+ **Threat Model**:
826
+ - **Protects against**: Accidental secret exposure (logs, commits, backups)
827
+ - **Does NOT protect against**: Attacker with master key access
828
+ - **Assumption**: Master key is stored securely with restricted file permissions
829
+
830
+ ### Implementation Details
831
+
832
+ **Files**:
833
+ - `src/secrets/vault.ts` - Vault password derivation
834
+ - `src/ansible/secrets.ts` - Secrets file generation and encryption
835
+ - `src/variables/ansible-resolver.ts` - Jinja2 variable conversion
836
+ - `src/templates/generator.ts` - Integration with template generation
837
+
838
+ **Functions**:
839
+ - `deriveVaultPassword(masterKey)` - Deterministic password derivation
840
+ - `generateAnsibleSecrets(moduleId, outputPath, db)` - Full generation pipeline
841
+ - `encryptWithAnsibleVault(yamlContent, password)` - Ansible Vault wrapper
842
+ - `convertSecretsToJinja(content, context)` - Variable conversion for Ansible
843
+
844
+ ### Testing
845
+
846
+ **Unit Tests** (13 tests):
847
+ ```bash
848
+ bun test src/secrets/vault.test.ts
849
+ bun test src/variables/ansible-resolver.test.ts
850
+ ```
851
+
852
+ **Integration Tests**:
853
+ ```bash
854
+ bun run test:integration
855
+ # Validates:
856
+ # - Secrets encrypted with ansible-vault
857
+ # - Ansible templates use Jinja2 variables
858
+ # - Vault password command works
859
+ # - Generated secrets can be decrypted
860
+ ```
861
+
862
+ **Manual Verification**:
863
+ ```bash
864
+ # 1. Generate module
865
+ celilo module generate homebridge
866
+
867
+ # 2. Check secrets file is encrypted
868
+ head /tmp/celilo/modules/homebridge/generated/ansible/inventory/secrets.yml
869
+ # Should start with: $ANSIBLE_VAULT;1.1;AES256
870
+
871
+ # 3. Decrypt and verify
872
+ ansible-vault view /tmp/celilo/modules/homebridge/generated/ansible/inventory/secrets.yml \
873
+ --vault-password-file=<(celilo system vault-password)
874
+ # Should show YAML with secret values
875
+
876
+ # 4. Check templates use Jinja2
877
+ cat /tmp/celilo/modules/homebridge/generated/ansible/roles/homebridge/templates/config.json
878
+ # Should contain {{ variable_name }}, not plaintext secrets
879
+ ```
880
+
881
+ ## Template Generation
882
+
883
+ ### System Overview (`src/templates/`)
884
+
885
+ Template generation following Rule 10.1 separation:
886
+
887
+ **Policy Functions**:
888
+ - `isTemplateFile(filename)` - Checks if file has template extension (`.tpl`, `.j2`)
889
+ - `getOutputFilename(template)` - Removes template extension from filename
890
+
891
+ **Execution Functions**:
892
+ - `discoverTemplateFiles(baseDir)` - Recursively finds template files
893
+ - `readTemplateFiles(modulePath, paths)` - Reads template content from disk
894
+ - `writeGeneratedFiles(outputPath, files)` - Writes generated files to disk
895
+
896
+ **Orchestration Function**:
897
+ - `generateTemplates(options)` - Main entry point, coordinates entire generation process
898
+
899
+ ### Template Directories
900
+
901
+ Celilo looks for templates in:
902
+ - `terraform/` - Terraform configuration templates
903
+ - `ansible/` - Ansible playbook templates
904
+
905
+ Both directories are searched recursively.
906
+
907
+ ### Template Extensions
908
+
909
+ - **`.tpl`** - Generic template files (Terraform style)
910
+ - **`.j2`** - Jinja2 template files (Ansible style)
911
+
912
+ Output files have extension removed:
913
+ - `main.tf.tpl` → `main.tf`
914
+ - `playbook.yml.j2` → `playbook.yml`
915
+
916
+ ### Generation Process
917
+
918
+ 1. **Discover** - Find all template files in `terraform/` and `ansible/` directories
919
+ 2. **Build Context** - Load module config, secrets, and capabilities from database
920
+ 3. **Read** - Load template content from files
921
+ 4. **Resolve** - Replace variables using variable resolution system
922
+ 5. **Write** - Save generated files to output directory
923
+ 6. **Return** - Success with file list or detailed error messages
924
+
925
+ ### Example Usage
926
+
927
+ ```typescript
928
+ import { generateTemplates } from './templates/generator';
929
+
930
+ const result = await generateTemplates({
931
+ moduleId: 'homebridge',
932
+ modulePath: '/data/modules/homebridge',
933
+ outputPath: '/data/modules/homebridge/generated',
934
+ });
935
+
936
+ if (result.success) {
937
+ console.log(`Generated ${result.files.length} files:`);
938
+ for (const file of result.files) {
939
+ console.log(` - ${file.path}`);
940
+ }
941
+ } else {
942
+ console.error('Generation failed:', result.error);
943
+ }
944
+ ```
945
+
946
+ ### Module Structure
947
+
948
+ ```
949
+ modules/homebridge/
950
+ ├── manifest.yml
951
+ ├── terraform/
952
+ │ ├── main.tf.tpl # → generated/terraform/main.tf
953
+ │ ├── variables.tf.tpl # → generated/terraform/variables.tf
954
+ │ └── outputs.tf.tpl # → generated/terraform/outputs.tf
955
+ └── ansible/
956
+ ├── playbook.yml.tpl # → generated/ansible/playbook.yml
957
+ └── roles/
958
+ └── homebridge/
959
+ └── tasks/
960
+ └── main.yml.tpl # → generated/ansible/roles/homebridge/tasks/main.yml
961
+ ```
962
+
963
+ ### Template Example
964
+
965
+ **Input** (`terraform/main.tf.tpl`):
966
+ ```hcl
967
+ resource "proxmox_lxc" "container" {
968
+ hostname = "$self:hostname"
969
+ cores = $self:cores
970
+ memory = $self:memory
971
+
972
+ network {
973
+ name = "eth0"
974
+ bridge = "vmbr0"
975
+ ip = "$self:container_ip/24"
976
+ gw = "$system:management.ip"
977
+ }
978
+
979
+ provisioner "ansible" {
980
+ playbook = "./ansible/playbook.yml"
981
+ }
982
+ }
983
+ ```
984
+
985
+ **Output** (`generated/terraform/main.tf`):
986
+ ```hcl
987
+ resource "proxmox_lxc" "container" {
988
+ hostname = "homebridge"
989
+ cores = 2
990
+ memory = 2048
991
+
992
+ network {
993
+ name = "eth0"
994
+ bridge = "vmbr0"
995
+ ip = "192.168.0.50/24"
996
+ gw = "192.168.0.10"
997
+ }
998
+
999
+ provisioner "ansible" {
1000
+ playbook = "./ansible/playbook.yml"
1001
+ }
1002
+ }
1003
+ ```
1004
+
1005
+ ### Error Handling
1006
+
1007
+ Clear error messages for common failures:
1008
+
1009
+ ```typescript
1010
+ // Module path doesn't exist
1011
+ { success: false, error: "Module path does not exist: /path" }
1012
+
1013
+ // No templates found
1014
+ { success: false, error: "No template files found in module" }
1015
+
1016
+ // Variable resolution failed
1017
+ {
1018
+ success: false,
1019
+ error: "Failed to resolve variables in templates:\n" +
1020
+ "terraform/main.tf:\n" +
1021
+ " $self:missing_var: Self variable 'missing_var' not found in module configuration"
1022
+ }
1023
+
1024
+ // File write failed
1025
+ { success: false, error: "Failed to write generated files", details: Error }
1026
+ ```
1027
+
1028
+ ### Integration
1029
+
1030
+ Template generation integrates with:
1031
+ - **Variable Resolution** - Resolves all `$self:`, `$system:`, `$secret:`, `$capability:` variables
1032
+ - **Database** - Loads module configuration and capability data
1033
+ - **File System** - Reads templates and writes generated code
1034
+
1035
+ ## Next Steps
1036
+
1037
+ Phase 0 Part 1.7:
1038
+ 1. ✅ Database layer
1039
+ 2. ✅ Manifest validation
1040
+ 3. ✅ Module import logic
1041
+ 4. ✅ Variable resolution system
1042
+ 5. ✅ Secret encryption (AES-256-GCM)
1043
+ 6. ✅ Template generation (File I/O + variable resolution)
1044
+ 7. CLI interface (Commands + user interaction)
1045
+
1046
+ ## CLI Interface
1047
+
1048
+ ### System Overview (`src/cli/`)
1049
+
1050
+ The CLI interface provides command-line tools for all Phase 0 operations following Rule 10.1 separation:
1051
+
1052
+ **Parser** (`parser.ts`) - Policy functions:
1053
+ - `parseArguments()` - Parses command-line arguments into structured format
1054
+ - `validateRequiredArgs()` - Validates argument count
1055
+ - `getArg()`, `getFlag()`, `hasFlag()` - Type-safe accessors
1056
+
1057
+ **Commands** (`commands/`) - Orchestration functions:
1058
+ - `module-import.ts` - Import modules from directory
1059
+ - `module-list.ts` - List installed modules
1060
+ - `module-config.ts` - Get/set module configuration
1061
+ - `module-generate.ts` - Generate templates for modules
1062
+ - `secret-set.ts` - Set encrypted secrets
1063
+
1064
+ **Entry Point** (`index.ts`) - Main orchestrator:
1065
+ - `runCli()` - Routes commands to handlers
1066
+ - `main()` - Entry point with error handling and exit codes
1067
+
1068
+ ### Commands
1069
+
1070
+ **Module Import**
1071
+ ```bash
1072
+ celilo module import <path> [--target <path>]
1073
+
1074
+ # Example
1075
+ celilo module import ./modules/homebridge
1076
+ celilo module import ./modules/homebridge --target /data/celilo/modules
1077
+ ```
1078
+
1079
+ **Module List**
1080
+ ```bash
1081
+ celilo module list
1082
+
1083
+ # Output
1084
+ Installed modules:
1085
+
1086
+ homebridge (v1.0.0) - IMPORTED
1087
+ Bridge for HomeKit accessories
1088
+
1089
+ pihole (v2.1.0) - CONFIGURED
1090
+ ```
1091
+
1092
+ **Module Configuration**
1093
+ ```bash
1094
+ # Set configuration value
1095
+ celilo module config set <module-id> <key> <value>
1096
+
1097
+ # Get specific value
1098
+ celilo module config get <module-id> [key]
1099
+
1100
+ # Examples
1101
+ celilo module config set homebridge hostname mybridge
1102
+ celilo module config set homebridge container_ip 192.168.0.50
1103
+ celilo module config get homebridge hostname
1104
+ celilo module config get homebridge # Get all config
1105
+ ```
1106
+
1107
+ **Module Generate**
1108
+ ```bash
1109
+ celilo module generate <module-id> [--output <path>]
1110
+
1111
+ # Examples
1112
+ celilo module generate homebridge
1113
+ celilo module generate homebridge --output ./generated
1114
+ ```
1115
+
1116
+ **Secret Management**
1117
+ ```bash
1118
+ celilo secret set <module-id> <name> <value>
1119
+
1120
+ # Example
1121
+ celilo secret set homebridge api_key mykey123
1122
+ ```
1123
+
1124
+ **System Configuration**
1125
+ ```bash
1126
+ # Set system-wide config
1127
+ celilo system config set <key> <value>
1128
+ celilo system config get [key]
1129
+
1130
+ # Get Ansible Vault password
1131
+ celilo system vault-password
1132
+
1133
+ # Examples
1134
+ celilo system config set dns.primary 192.168.0.1
1135
+ celilo system config get dns.primary
1136
+ ansible-vault view secrets.yml --vault-password-file=<(celilo system vault-password)
1137
+ ```
1138
+
1139
+ **Help**
1140
+ ```bash
1141
+ celilo help
1142
+ celilo --help
1143
+ celilo -h
1144
+ celilo module --help # Command-specific help
1145
+ ```
1146
+
1147
+ ### Argument Parsing
1148
+
1149
+ The CLI uses a simple argument parser without external dependencies:
1150
+
1151
+ **Format**: `celilo <command> [subcommand] [args...] [--flags]`
1152
+
1153
+ **Parsing Rules**:
1154
+ - First argument is the command (`module`, `secret`, `help`)
1155
+ - Second argument is the subcommand if it doesn't start with `--`
1156
+ - Remaining arguments are positional args or flags
1157
+ - Flags start with `--` and can be boolean (`--verbose`) or string (`--target /path`)
1158
+
1159
+ **Examples**:
1160
+ ```bash
1161
+ # Command only
1162
+ celilo help
1163
+
1164
+ # Command + subcommand
1165
+ celilo module list
1166
+
1167
+ # Command + subcommand + args
1168
+ celilo module import ./path
1169
+
1170
+ # Command + subcommand + args + flags
1171
+ celilo module generate homebridge --output ./out
1172
+
1173
+ # Nested subcommand
1174
+ celilo module config set homebridge hostname myhost
1175
+ ```
1176
+
1177
+ ### Error Handling
1178
+
1179
+ All commands return structured results:
1180
+ ```typescript
1181
+ interface CommandSuccess {
1182
+ success: true;
1183
+ message: string;
1184
+ data?: unknown;
1185
+ }
1186
+
1187
+ interface CommandError {
1188
+ success: false;
1189
+ error: string;
1190
+ details?: unknown;
1191
+ }
1192
+ ```
1193
+
1194
+ **Exit Codes**:
1195
+ - `0` - Success
1196
+ - `1` - Error (validation, not found, etc.)
1197
+
1198
+ **Error Messages** include usage hints:
1199
+ ```
1200
+ Error: Missing required arguments. Expected 1, got 0
1201
+
1202
+ Usage: celilo module import <path> [--target <path>]
1203
+ ```
1204
+
1205
+ ### Testing
1206
+
1207
+ All CLI components have comprehensive tests:
1208
+ - **`parser.test.ts`** - Argument parsing (28 tests)
1209
+ - **`cli.test.ts`** - Integration tests (23 tests)
1210
+
1211
+ Tests use environment variables to configure database and master key paths for isolation.
1212
+
1213
+ ### Entry Point
1214
+
1215
+ The main entry point is `src/cli/index.ts`, which exports `main()`:
1216
+ ```typescript
1217
+ export async function main(): Promise<void> {
1218
+ const result = await runCli(process.argv);
1219
+ // Handle output and exit codes
1220
+ }
1221
+ ```
1222
+
1223
+ **Recommended:** Use the top-level wrapper script (from anywhere):
1224
+ ```bash
1225
+ /path/to/celilo/celilo module list
1226
+ /path/to/celilo/celilo system config get
1227
+ ```
1228
+
1229
+ Or run directly from backend directory:
1230
+ ```bash
1231
+ bun run src/cli/index.ts module list
1232
+ bun run src/cli/index.ts module import ./path
1233
+ ```
1234
+
1235
+ Or build a binary:
1236
+ ```bash
1237
+ bun build src/cli/index.ts --compile --outfile celilo
1238
+ ./celilo module list
1239
+ ```
1240
+
1241
+ ### Architecture
1242
+
1243
+ **CLI Orchestration**:
1244
+ ```
1245
+ User Input
1246
+
1247
+ parseArguments() [Policy - parse and validate]
1248
+
1249
+ runCli() [Orchestration - route to handler]
1250
+
1251
+ handleModuleXxx() [Orchestration - coordinate operations]
1252
+
1253
+ importModule() [Execution - database, filesystem]
1254
+ generateTemplates()
1255
+ etc.
1256
+ ```
1257
+
1258
+ **Separation of Concerns**:
1259
+ - **Parser** - Pure policy functions, no side effects
1260
+ - **Handlers** - Orchestrate multiple operations
1261
+ - **Core modules** - Execute actual work (import, generate, etc.)
1262
+ - **Entry point** - Handle errors and exit codes
1263
+
1264
+ All commands integrate with existing modules:
1265
+ - **Database** - Module metadata, configuration, secrets
1266
+ - **Manifest validation** - Parse and validate manifests
1267
+ - **Module import** - Copy files and insert to database
1268
+ - **Variable resolution** - Resolve template variables
1269
+ - **Secret encryption** - Encrypt/decrypt with master key
1270
+ - **Template generation** - Generate Terraform/Ansible code
1271
+
1272
+ ## Troubleshooting
1273
+
1274
+ ### Common Errors and Solutions
1275
+
1276
+ #### "Module ID must use kebab-case"
1277
+
1278
+ **Error**:
1279
+ ```
1280
+ Error: Manifest validation failed
1281
+ Module ID must use kebab-case (lowercase letters, numbers, hyphens between segments)
1282
+ ```
1283
+
1284
+ **Cause**: Module ID uses underscores, uppercase, or invalid characters.
1285
+
1286
+ **Solution**: Use kebab-case for all module IDs:
1287
+ ```yaml
1288
+ # ❌ WRONG
1289
+ metadata:
1290
+ name: dns_external # Underscores
1291
+
1292
+ # ✅ CORRECT
1293
+ metadata:
1294
+ name: dns-external # Kebab-case
1295
+ ```
1296
+
1297
+ **Pattern**: `/^[a-z0-9]+(-[a-z0-9]+)*$/`
1298
+
1299
+ ---
1300
+
1301
+ #### "Variable resolution failed: Self variable not found"
1302
+
1303
+ **Error**:
1304
+ ```
1305
+ Failed to resolve variables in templates:
1306
+ terraform/main.tf:
1307
+ $self:container_ip: Self variable 'container_ip' not found in module configuration
1308
+ ```
1309
+
1310
+ **Cause**: Template references variable that isn't configured.
1311
+
1312
+ **Solution**: Set the missing variable:
1313
+ ```bash
1314
+ celilo module config set <module-id> container_ip 10.0.20.100
1315
+ ```
1316
+
1317
+ Or check for typos in template:
1318
+ ```hcl
1319
+ # Check spelling
1320
+ ip = "$self:container_ip" # Not 'containerIP', 'container-ip', etc.
1321
+ ```
1322
+
1323
+ ---
1324
+
1325
+ #### "ENOENT: no such file or directory" (path with spaces)
1326
+
1327
+ **Error**:
1328
+ ```
1329
+ Error: ENOENT: no such file or directory, open '/Users/user/Application Support/celilo/...'
1330
+ ```
1331
+
1332
+ **Cause**: Unquoted path in shell command with spaces.
1333
+
1334
+ **Solution**: Always quote paths in shell commands:
1335
+ ```typescript
1336
+ // ❌ WRONG
1337
+ execSync(`cd ${modulePath} && make build`);
1338
+
1339
+ // ✅ CORRECT
1340
+ execSync(`cd "${modulePath}" && make build`);
1341
+
1342
+ // ✅ BEST
1343
+ import { shellEscape } from '@/utils/shell';
1344
+ execSync(`cd ${shellEscape(modulePath)} && make build`);
1345
+ ```
1346
+
1347
+ ---
1348
+
1349
+ #### "Drizzle where clause returns all rows"
1350
+
1351
+ **Error**: Query returns all modules instead of filtering by ID.
1352
+
1353
+ **Cause**: Using JavaScript operators instead of Drizzle operators.
1354
+
1355
+ **Solution**: Use Drizzle operator functions:
1356
+ ```typescript
1357
+ import { eq } from 'drizzle-orm';
1358
+
1359
+ // ❌ WRONG - returns ALL rows!
1360
+ db.select().from(modules).where(modules.id === 'homebridge');
1361
+
1362
+ // ✅ CORRECT - filters correctly
1363
+ db.select().from(modules).where(eq(modules.id, 'homebridge'));
1364
+ ```
1365
+
1366
+ **Always use**: `eq()`, `ne()`, `gt()`, `lt()`, `and()`, `or()`, etc.
1367
+
1368
+ ---
1369
+
1370
+ #### "ansible-vault: command not found"
1371
+
1372
+ **Error**:
1373
+ ```
1374
+ Error: Failed to encrypt secrets with ansible-vault
1375
+ ansible-vault: command not found
1376
+ ```
1377
+
1378
+ **Cause**: Ansible is not installed or not in PATH.
1379
+
1380
+ **Solution**: Install Ansible:
1381
+ ```bash
1382
+ # macOS
1383
+ brew install ansible
1384
+
1385
+ # Ubuntu/Debian
1386
+ sudo apt install ansible
1387
+
1388
+ # Verify
1389
+ ansible-vault --version
1390
+ ```
1391
+
1392
+ ---
1393
+
1394
+ #### "Test passes alone, fails in suite"
1395
+
1396
+ **Error**: Test passes when run individually, fails when run with others.
1397
+
1398
+ **Cause**: State leakage between tests (shared database, temp files, environment).
1399
+
1400
+ **Solution**: Add proper setup/teardown:
1401
+ ```typescript
1402
+ import { describe, test, beforeEach, afterEach } from 'bun:test';
1403
+
1404
+ describe('Feature Tests', () => {
1405
+ let testDb: Database;
1406
+ let tempDir: string;
1407
+
1408
+ beforeEach(async () => {
1409
+ testDb = await setupTestDatabase(); // Fresh database
1410
+ tempDir = await createTempDirectory(); // Fresh directory
1411
+ });
1412
+
1413
+ afterEach(async () => {
1414
+ await cleanupTestDatabase(testDb); // MUST await!
1415
+ await removeTempDirectory(tempDir); // MUST await!
1416
+ });
1417
+
1418
+ test('isolated test', async () => {
1419
+ // Test runs with clean state
1420
+ });
1421
+ });
1422
+ ```
1423
+
1424
+ ---
1425
+
1426
+ #### "Cannot find module '@/something'"
1427
+
1428
+ **Error**:
1429
+ ```
1430
+ Error: Cannot find module '@/module/import'
1431
+ ```
1432
+
1433
+ **Cause**: TypeScript path aliases not resolved.
1434
+
1435
+ **Solution**: Check `tsconfig.json` has correct paths:
1436
+ ```json
1437
+ {
1438
+ "compilerOptions": {
1439
+ "baseUrl": ".",
1440
+ "paths": {
1441
+ "@/*": ["src/*"]
1442
+ }
1443
+ }
1444
+ }
1445
+ ```
1446
+
1447
+ And run with Bun (which respects tsconfig paths):
1448
+ ```bash
1449
+ bun run src/cli/index.ts # ✅ Works
1450
+ node src/cli/index.ts # ❌ Doesn't resolve @/ aliases
1451
+ ```
1452
+
1453
+ ---
1454
+
1455
+ #### "Master key permission denied"
1456
+
1457
+ **Error**:
1458
+ ```
1459
+ Error: EACCES: permission denied, open '/etc/celilo/master.key'
1460
+ ```
1461
+
1462
+ **Cause**: Master key file has wrong permissions or wrong owner.
1463
+
1464
+ **Solution**: Fix permissions:
1465
+ ```bash
1466
+ # Check current permissions
1467
+ ls -la /tmp/celilo/master.key
1468
+
1469
+ # Fix permissions (owner read/write only)
1470
+ chmod 600 /tmp/celilo/master.key
1471
+
1472
+ # Or regenerate (WARNING: invalidates all encrypted secrets!)
1473
+ rm /tmp/celilo/master.key
1474
+ celilo system vault-password # Generates new key
1475
+ ```
1476
+
1477
+ ---
1478
+
1479
+ #### "Migration already exists"
1480
+
1481
+ **Error**:
1482
+ ```
1483
+ Error: Migration 0005_add_capability_secrets.sql already exists
1484
+ ```
1485
+
1486
+ **Cause**: Schema changed but migration not generated or conflicting migration number.
1487
+
1488
+ **Solution**: Check for duplicates:
1489
+ ```bash
1490
+ # List migrations
1491
+ ls drizzle/
1492
+
1493
+ # Remove duplicate or conflicting migration
1494
+ rm drizzle/0005_duplicate.sql
1495
+
1496
+ # Regenerate
1497
+ bunx drizzle-kit generate
1498
+ ```
1499
+
1500
+ ---
1501
+
1502
+ ### Debug Checklist
1503
+
1504
+ When debugging issues, check:
1505
+
1506
+ 1. **Test isolation**:
1507
+ - [ ] `beforeEach`/`afterEach` properly clean up
1508
+ - [ ] Tests can run in any order
1509
+ - [ ] Tests use isolated database/filesystem
1510
+
1511
+ 2. **Path handling**:
1512
+ - [ ] All shell commands quote paths
1513
+ - [ ] Tested with paths containing spaces
1514
+ - [ ] No hardcoded absolute paths
1515
+
1516
+ 3. **Drizzle queries**:
1517
+ - [ ] Using operator functions (`eq()`, not `===`)
1518
+ - [ ] Importing operators from `'drizzle-orm'`
1519
+ - [ ] Not using callback form in `where()`
1520
+
1521
+ 4. **Identifier naming**:
1522
+ - [ ] Module IDs use kebab-case
1523
+ - [ ] No underscores or uppercase
1524
+ - [ ] Pattern: `/^[a-z0-9]+(-[a-z0-9]+)*$/`
1525
+
1526
+ 5. **Variable resolution**:
1527
+ - [ ] Using colon syntax (`$self:var`, not `$self.var`)
1528
+ - [ ] Simple syntax for standalone values
1529
+ - [ ] Braced syntax for concatenation (`${self:disk}G`)
1530
+
1531
+ 6. **Secrets**:
1532
+ - [ ] Master key exists and has 0600 permissions
1533
+ - [ ] Ansible installed for vault encryption
1534
+ - [ ] Secrets encrypted in database
1535
+
1536
+ ---
1537
+
1538
+ ### Getting Help
1539
+
1540
+ **Documentation**:
1541
+ - [CLAUDE.md](../../CLAUDE.md) - Engineering standards
1542
+ - [TESTING_STRATEGY.md](../../design/TESTING_STRATEGY.md) - Testing approach
1543
+ - [IDENTIFIER_NAMING_CONVENTIONS.md](../../design/IDENTIFIER_NAMING_CONVENTIONS.md) - Naming rules
1544
+ - [TEMPLATE_VARIABLE_SYNTAX.md](../../design/TEMPLATE_VARIABLE_SYNTAX.md) - Variable syntax
1545
+
1546
+ **Debugging Tools**:
1547
+ - Drizzle Studio: `bun run db:studio`
1548
+ - Test watch mode: `bun test --watch`
1549
+ - Verbose logs: `export CONDUCTOR_LOG_LEVEL=debug`
1550
+
1551
+ ---
1552
+
1553
+ ## Next Steps
1554
+
1555
+ Phase 0 Part 1.7:
1556
+ 1. ✅ Database layer
1557
+ 2. ✅ Manifest validation
1558
+ 3. ✅ Module import logic
1559
+ 4. ✅ Variable resolution system
1560
+ 5. ✅ Secret encryption (AES-256-GCM)
1561
+ 6. ✅ Template generation (File I/O + variable resolution)
1562
+ 7. ✅ CLI interface (Commands + user interaction)
1563
+
1564
+ **Phase 0 Part 1 is complete!**
1565
+
1566
+ Next: Phase 0 Part 2 - Web UI (React + tRPC)