@cicore/cli 1.0.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 (337) hide show
  1. package/bin/ci.js +13 -0
  2. package/dist/commands/addon/api-actions.d.ts +45 -0
  3. package/dist/commands/addon/api-actions.d.ts.map +1 -0
  4. package/dist/commands/addon/api-actions.js +281 -0
  5. package/dist/commands/addon/api-actions.js.map +1 -0
  6. package/dist/commands/addon/build.d.ts +11 -0
  7. package/dist/commands/addon/build.d.ts.map +1 -0
  8. package/dist/commands/addon/build.js +182 -0
  9. package/dist/commands/addon/build.js.map +1 -0
  10. package/dist/commands/addon/create.d.ts +11 -0
  11. package/dist/commands/addon/create.d.ts.map +1 -0
  12. package/dist/commands/addon/create.js +1186 -0
  13. package/dist/commands/addon/create.js.map +1 -0
  14. package/dist/commands/addon/delete.d.ts +13 -0
  15. package/dist/commands/addon/delete.d.ts.map +1 -0
  16. package/dist/commands/addon/delete.js +83 -0
  17. package/dist/commands/addon/delete.js.map +1 -0
  18. package/dist/commands/addon/deploy.d.ts +27 -0
  19. package/dist/commands/addon/deploy.d.ts.map +1 -0
  20. package/dist/commands/addon/deploy.js +459 -0
  21. package/dist/commands/addon/deploy.js.map +1 -0
  22. package/dist/commands/addon/dev-deploy.d.ts +31 -0
  23. package/dist/commands/addon/dev-deploy.d.ts.map +1 -0
  24. package/dist/commands/addon/dev-deploy.js +128 -0
  25. package/dist/commands/addon/dev-deploy.js.map +1 -0
  26. package/dist/commands/addon/dev.d.ts +36 -0
  27. package/dist/commands/addon/dev.d.ts.map +1 -0
  28. package/dist/commands/addon/dev.js +323 -0
  29. package/dist/commands/addon/dev.js.map +1 -0
  30. package/dist/commands/addon/extract-classes.d.ts +23 -0
  31. package/dist/commands/addon/extract-classes.d.ts.map +1 -0
  32. package/dist/commands/addon/extract-classes.js +281 -0
  33. package/dist/commands/addon/extract-classes.js.map +1 -0
  34. package/dist/commands/addon/generate-safelist.d.ts +24 -0
  35. package/dist/commands/addon/generate-safelist.d.ts.map +1 -0
  36. package/dist/commands/addon/generate-safelist.js +276 -0
  37. package/dist/commands/addon/generate-safelist.js.map +1 -0
  38. package/dist/commands/addon/index.d.ts +19 -0
  39. package/dist/commands/addon/index.d.ts.map +1 -0
  40. package/dist/commands/addon/index.js +296 -0
  41. package/dist/commands/addon/index.js.map +1 -0
  42. package/dist/commands/addon/init-repo.d.ts +25 -0
  43. package/dist/commands/addon/init-repo.d.ts.map +1 -0
  44. package/dist/commands/addon/init-repo.js +171 -0
  45. package/dist/commands/addon/init-repo.js.map +1 -0
  46. package/dist/commands/addon/install.d.ts +23 -0
  47. package/dist/commands/addon/install.d.ts.map +1 -0
  48. package/dist/commands/addon/install.js +84 -0
  49. package/dist/commands/addon/install.js.map +1 -0
  50. package/dist/commands/addon/list.d.ts +10 -0
  51. package/dist/commands/addon/list.d.ts.map +1 -0
  52. package/dist/commands/addon/list.js +102 -0
  53. package/dist/commands/addon/list.js.map +1 -0
  54. package/dist/commands/addon/manifest-refresh.d.ts +17 -0
  55. package/dist/commands/addon/manifest-refresh.d.ts.map +1 -0
  56. package/dist/commands/addon/manifest-refresh.js +48 -0
  57. package/dist/commands/addon/manifest-refresh.js.map +1 -0
  58. package/dist/commands/addon/migrate.d.ts +40 -0
  59. package/dist/commands/addon/migrate.d.ts.map +1 -0
  60. package/dist/commands/addon/migrate.js +236 -0
  61. package/dist/commands/addon/migrate.js.map +1 -0
  62. package/dist/commands/addon/publish.d.ts +33 -0
  63. package/dist/commands/addon/publish.d.ts.map +1 -0
  64. package/dist/commands/addon/publish.js +236 -0
  65. package/dist/commands/addon/publish.js.map +1 -0
  66. package/dist/commands/addon/scaffold-quality.d.ts +21 -0
  67. package/dist/commands/addon/scaffold-quality.d.ts.map +1 -0
  68. package/dist/commands/addon/scaffold-quality.js +90 -0
  69. package/dist/commands/addon/scaffold-quality.js.map +1 -0
  70. package/dist/commands/addon/sign.d.ts +9 -0
  71. package/dist/commands/addon/sign.d.ts.map +1 -0
  72. package/dist/commands/addon/sign.js +83 -0
  73. package/dist/commands/addon/sign.js.map +1 -0
  74. package/dist/commands/addon/toggle.d.ts +6 -0
  75. package/dist/commands/addon/toggle.d.ts.map +1 -0
  76. package/dist/commands/addon/toggle.js +46 -0
  77. package/dist/commands/addon/toggle.js.map +1 -0
  78. package/dist/commands/agent/index.d.ts +34 -0
  79. package/dist/commands/agent/index.d.ts.map +1 -0
  80. package/dist/commands/agent/index.js +564 -0
  81. package/dist/commands/agent/index.js.map +1 -0
  82. package/dist/commands/brand/index.d.ts +54 -0
  83. package/dist/commands/brand/index.d.ts.map +1 -0
  84. package/dist/commands/brand/index.js +367 -0
  85. package/dist/commands/brand/index.js.map +1 -0
  86. package/dist/commands/build/index.d.ts +53 -0
  87. package/dist/commands/build/index.d.ts.map +1 -0
  88. package/dist/commands/build/index.js +726 -0
  89. package/dist/commands/build/index.js.map +1 -0
  90. package/dist/commands/cache/flush-local.d.ts +31 -0
  91. package/dist/commands/cache/flush-local.d.ts.map +1 -0
  92. package/dist/commands/cache/flush-local.js +161 -0
  93. package/dist/commands/cache/flush-local.js.map +1 -0
  94. package/dist/commands/cache/index.d.ts +14 -0
  95. package/dist/commands/cache/index.d.ts.map +1 -0
  96. package/dist/commands/cache/index.js +453 -0
  97. package/dist/commands/cache/index.js.map +1 -0
  98. package/dist/commands/check/index.d.ts +8 -0
  99. package/dist/commands/check/index.d.ts.map +1 -0
  100. package/dist/commands/check/index.js +1316 -0
  101. package/dist/commands/check/index.js.map +1 -0
  102. package/dist/commands/cloudflare/index.d.ts +8 -0
  103. package/dist/commands/cloudflare/index.d.ts.map +1 -0
  104. package/dist/commands/cloudflare/index.js +453 -0
  105. package/dist/commands/cloudflare/index.js.map +1 -0
  106. package/dist/commands/core/create.d.ts +12 -0
  107. package/dist/commands/core/create.d.ts.map +1 -0
  108. package/dist/commands/core/create.js +206 -0
  109. package/dist/commands/core/create.js.map +1 -0
  110. package/dist/commands/core/delete.d.ts +11 -0
  111. package/dist/commands/core/delete.d.ts.map +1 -0
  112. package/dist/commands/core/delete.js +64 -0
  113. package/dist/commands/core/delete.js.map +1 -0
  114. package/dist/commands/core/env.d.ts +12 -0
  115. package/dist/commands/core/env.d.ts.map +1 -0
  116. package/dist/commands/core/env.js +95 -0
  117. package/dist/commands/core/env.js.map +1 -0
  118. package/dist/commands/core/health.d.ts +6 -0
  119. package/dist/commands/core/health.d.ts.map +1 -0
  120. package/dist/commands/core/health.js +215 -0
  121. package/dist/commands/core/health.js.map +1 -0
  122. package/dist/commands/core/index.d.ts +15 -0
  123. package/dist/commands/core/index.d.ts.map +1 -0
  124. package/dist/commands/core/index.js +86 -0
  125. package/dist/commands/core/index.js.map +1 -0
  126. package/dist/commands/core/list.d.ts +11 -0
  127. package/dist/commands/core/list.d.ts.map +1 -0
  128. package/dist/commands/core/list.js +58 -0
  129. package/dist/commands/core/list.js.map +1 -0
  130. package/dist/commands/core/rebuild.d.ts +13 -0
  131. package/dist/commands/core/rebuild.d.ts.map +1 -0
  132. package/dist/commands/core/rebuild.js +119 -0
  133. package/dist/commands/core/rebuild.js.map +1 -0
  134. package/dist/commands/db/index.d.ts +23 -0
  135. package/dist/commands/db/index.d.ts.map +1 -0
  136. package/dist/commands/db/index.js +355 -0
  137. package/dist/commands/db/index.js.map +1 -0
  138. package/dist/commands/db/promote-silo.d.ts +320 -0
  139. package/dist/commands/db/promote-silo.d.ts.map +1 -0
  140. package/dist/commands/db/promote-silo.js +930 -0
  141. package/dist/commands/db/promote-silo.js.map +1 -0
  142. package/dist/commands/db/relocate.d.ts +41 -0
  143. package/dist/commands/db/relocate.d.ts.map +1 -0
  144. package/dist/commands/db/relocate.js +482 -0
  145. package/dist/commands/db/relocate.js.map +1 -0
  146. package/dist/commands/db/rollback-silo.d.ts +44 -0
  147. package/dist/commands/db/rollback-silo.d.ts.map +1 -0
  148. package/dist/commands/db/rollback-silo.js +402 -0
  149. package/dist/commands/db/rollback-silo.js.map +1 -0
  150. package/dist/commands/deploy/index.d.ts +26 -0
  151. package/dist/commands/deploy/index.d.ts.map +1 -0
  152. package/dist/commands/deploy/index.js +107 -0
  153. package/dist/commands/deploy/index.js.map +1 -0
  154. package/dist/commands/devops/index.d.ts +6 -0
  155. package/dist/commands/devops/index.d.ts.map +1 -0
  156. package/dist/commands/devops/index.js +220 -0
  157. package/dist/commands/devops/index.js.map +1 -0
  158. package/dist/commands/domain/index.d.ts +8 -0
  159. package/dist/commands/domain/index.d.ts.map +1 -0
  160. package/dist/commands/domain/index.js +386 -0
  161. package/dist/commands/domain/index.js.map +1 -0
  162. package/dist/commands/image/index.d.ts +8 -0
  163. package/dist/commands/image/index.d.ts.map +1 -0
  164. package/dist/commands/image/index.js +308 -0
  165. package/dist/commands/image/index.js.map +1 -0
  166. package/dist/commands/install/factory-reset.d.ts +21 -0
  167. package/dist/commands/install/factory-reset.d.ts.map +1 -0
  168. package/dist/commands/install/factory-reset.js +83 -0
  169. package/dist/commands/install/factory-reset.js.map +1 -0
  170. package/dist/commands/install/index.d.ts +17 -0
  171. package/dist/commands/install/index.d.ts.map +1 -0
  172. package/dist/commands/install/index.js +44 -0
  173. package/dist/commands/install/index.js.map +1 -0
  174. package/dist/commands/install/install.d.ts +35 -0
  175. package/dist/commands/install/install.d.ts.map +1 -0
  176. package/dist/commands/install/install.js +171 -0
  177. package/dist/commands/install/install.js.map +1 -0
  178. package/dist/commands/login/index.d.ts +15 -0
  179. package/dist/commands/login/index.d.ts.map +1 -0
  180. package/dist/commands/login/index.js +58 -0
  181. package/dist/commands/login/index.js.map +1 -0
  182. package/dist/commands/nginx/index.d.ts +11 -0
  183. package/dist/commands/nginx/index.d.ts.map +1 -0
  184. package/dist/commands/nginx/index.js +580 -0
  185. package/dist/commands/nginx/index.js.map +1 -0
  186. package/dist/commands/server/bootstrap.d.ts +25 -0
  187. package/dist/commands/server/bootstrap.d.ts.map +1 -0
  188. package/dist/commands/server/bootstrap.js +260 -0
  189. package/dist/commands/server/bootstrap.js.map +1 -0
  190. package/dist/commands/server/index.d.ts +8 -0
  191. package/dist/commands/server/index.d.ts.map +1 -0
  192. package/dist/commands/server/index.js +2524 -0
  193. package/dist/commands/server/index.js.map +1 -0
  194. package/dist/commands/setup/index.d.ts +34 -0
  195. package/dist/commands/setup/index.d.ts.map +1 -0
  196. package/dist/commands/setup/index.js +423 -0
  197. package/dist/commands/setup/index.js.map +1 -0
  198. package/dist/commands/ssl/index.d.ts +8 -0
  199. package/dist/commands/ssl/index.d.ts.map +1 -0
  200. package/dist/commands/ssl/index.js +275 -0
  201. package/dist/commands/ssl/index.js.map +1 -0
  202. package/dist/commands/superadmin/index.d.ts +16 -0
  203. package/dist/commands/superadmin/index.d.ts.map +1 -0
  204. package/dist/commands/superadmin/index.js +81 -0
  205. package/dist/commands/superadmin/index.js.map +1 -0
  206. package/dist/commands/tenant/index.d.ts +6 -0
  207. package/dist/commands/tenant/index.d.ts.map +1 -0
  208. package/dist/commands/tenant/index.js +192 -0
  209. package/dist/commands/tenant/index.js.map +1 -0
  210. package/dist/index.d.ts +11 -0
  211. package/dist/index.d.ts.map +1 -0
  212. package/dist/index.js +107 -0
  213. package/dist/index.js.map +1 -0
  214. package/dist/lib/addon-sign.d.ts +23 -0
  215. package/dist/lib/addon-sign.d.ts.map +1 -0
  216. package/dist/lib/addon-sign.js +39 -0
  217. package/dist/lib/addon-sign.js.map +1 -0
  218. package/dist/lib/addon-sign.test.d.ts +2 -0
  219. package/dist/lib/addon-sign.test.d.ts.map +1 -0
  220. package/dist/lib/addon-sign.test.js +27 -0
  221. package/dist/lib/addon-sign.test.js.map +1 -0
  222. package/dist/lib/cdn.d.ts +25 -0
  223. package/dist/lib/cdn.d.ts.map +1 -0
  224. package/dist/lib/cdn.js +131 -0
  225. package/dist/lib/cdn.js.map +1 -0
  226. package/dist/lib/cloudflare.d.ts +133 -0
  227. package/dist/lib/cloudflare.d.ts.map +1 -0
  228. package/dist/lib/cloudflare.js +435 -0
  229. package/dist/lib/cloudflare.js.map +1 -0
  230. package/dist/lib/config.d.ts +96 -0
  231. package/dist/lib/config.d.ts.map +1 -0
  232. package/dist/lib/config.js +132 -0
  233. package/dist/lib/config.js.map +1 -0
  234. package/dist/lib/env.d.ts +8 -0
  235. package/dist/lib/env.d.ts.map +1 -0
  236. package/dist/lib/env.js +64 -0
  237. package/dist/lib/env.js.map +1 -0
  238. package/dist/lib/hosts.d.ts +194 -0
  239. package/dist/lib/hosts.d.ts.map +1 -0
  240. package/dist/lib/hosts.js +183 -0
  241. package/dist/lib/hosts.js.map +1 -0
  242. package/dist/lib/logger.d.ts +68 -0
  243. package/dist/lib/logger.d.ts.map +1 -0
  244. package/dist/lib/logger.js +130 -0
  245. package/dist/lib/logger.js.map +1 -0
  246. package/dist/lib/nginx-config.d.ts +78 -0
  247. package/dist/lib/nginx-config.d.ts.map +1 -0
  248. package/dist/lib/nginx-config.js +736 -0
  249. package/dist/lib/nginx-config.js.map +1 -0
  250. package/dist/lib/ops/addon-dev.d.ts +93 -0
  251. package/dist/lib/ops/addon-dev.d.ts.map +1 -0
  252. package/dist/lib/ops/addon-dev.js +237 -0
  253. package/dist/lib/ops/addon-dev.js.map +1 -0
  254. package/dist/lib/ops/addon-quality.d.ts +38 -0
  255. package/dist/lib/ops/addon-quality.d.ts.map +1 -0
  256. package/dist/lib/ops/addon-quality.js +338 -0
  257. package/dist/lib/ops/addon-quality.js.map +1 -0
  258. package/dist/lib/ops/addon-routes.d.ts +49 -0
  259. package/dist/lib/ops/addon-routes.d.ts.map +1 -0
  260. package/dist/lib/ops/addon-routes.js +189 -0
  261. package/dist/lib/ops/addon-routes.js.map +1 -0
  262. package/dist/lib/ops/addon.d.ts +120 -0
  263. package/dist/lib/ops/addon.d.ts.map +1 -0
  264. package/dist/lib/ops/addon.js +260 -0
  265. package/dist/lib/ops/addon.js.map +1 -0
  266. package/dist/lib/ops/cdn.d.ts +87 -0
  267. package/dist/lib/ops/cdn.d.ts.map +1 -0
  268. package/dist/lib/ops/cdn.js +170 -0
  269. package/dist/lib/ops/cdn.js.map +1 -0
  270. package/dist/lib/ops/cf.d.ts +36 -0
  271. package/dist/lib/ops/cf.d.ts.map +1 -0
  272. package/dist/lib/ops/cf.js +114 -0
  273. package/dist/lib/ops/cf.js.map +1 -0
  274. package/dist/lib/ops/compose.d.ts +95 -0
  275. package/dist/lib/ops/compose.d.ts.map +1 -0
  276. package/dist/lib/ops/compose.js +165 -0
  277. package/dist/lib/ops/compose.js.map +1 -0
  278. package/dist/lib/ops/core.d.ts +117 -0
  279. package/dist/lib/ops/core.d.ts.map +1 -0
  280. package/dist/lib/ops/core.js +322 -0
  281. package/dist/lib/ops/core.js.map +1 -0
  282. package/dist/lib/ops/db.d.ts +116 -0
  283. package/dist/lib/ops/db.d.ts.map +1 -0
  284. package/dist/lib/ops/db.js +351 -0
  285. package/dist/lib/ops/db.js.map +1 -0
  286. package/dist/lib/ops/dns.d.ts +111 -0
  287. package/dist/lib/ops/dns.d.ts.map +1 -0
  288. package/dist/lib/ops/dns.js +306 -0
  289. package/dist/lib/ops/dns.js.map +1 -0
  290. package/dist/lib/ops/image.d.ts +94 -0
  291. package/dist/lib/ops/image.d.ts.map +1 -0
  292. package/dist/lib/ops/image.js +159 -0
  293. package/dist/lib/ops/image.js.map +1 -0
  294. package/dist/lib/ops/nginx.d.ts +114 -0
  295. package/dist/lib/ops/nginx.d.ts.map +1 -0
  296. package/dist/lib/ops/nginx.js +388 -0
  297. package/dist/lib/ops/nginx.js.map +1 -0
  298. package/dist/lib/ops/redis.d.ts +7 -0
  299. package/dist/lib/ops/redis.d.ts.map +1 -0
  300. package/dist/lib/ops/redis.js +35 -0
  301. package/dist/lib/ops/redis.js.map +1 -0
  302. package/dist/lib/ops/ssh.d.ts +127 -0
  303. package/dist/lib/ops/ssh.d.ts.map +1 -0
  304. package/dist/lib/ops/ssh.js +269 -0
  305. package/dist/lib/ops/ssh.js.map +1 -0
  306. package/dist/lib/prompts.d.ts +46 -0
  307. package/dist/lib/prompts.d.ts.map +1 -0
  308. package/dist/lib/prompts.js +113 -0
  309. package/dist/lib/prompts.js.map +1 -0
  310. package/dist/lib/sast.d.ts +43 -0
  311. package/dist/lib/sast.d.ts.map +1 -0
  312. package/dist/lib/sast.js +79 -0
  313. package/dist/lib/sast.js.map +1 -0
  314. package/dist/lib/sast.test.d.ts +2 -0
  315. package/dist/lib/sast.test.d.ts.map +1 -0
  316. package/dist/lib/sast.test.js +33 -0
  317. package/dist/lib/sast.test.js.map +1 -0
  318. package/dist/lib/shell.d.ts +61 -0
  319. package/dist/lib/shell.d.ts.map +1 -0
  320. package/dist/lib/shell.js +183 -0
  321. package/dist/lib/shell.js.map +1 -0
  322. package/dist/lib/ssh-config.d.ts +37 -0
  323. package/dist/lib/ssh-config.d.ts.map +1 -0
  324. package/dist/lib/ssh-config.js +122 -0
  325. package/dist/lib/ssh-config.js.map +1 -0
  326. package/dist/lib/tenant-scope.d.ts +38 -0
  327. package/dist/lib/tenant-scope.d.ts.map +1 -0
  328. package/dist/lib/tenant-scope.js +129 -0
  329. package/dist/lib/tenant-scope.js.map +1 -0
  330. package/dist/lib/tenant-scope.test.d.ts +2 -0
  331. package/dist/lib/tenant-scope.test.d.ts.map +1 -0
  332. package/dist/lib/tenant-scope.test.js +223 -0
  333. package/dist/lib/tenant-scope.test.js.map +1 -0
  334. package/package.json +58 -0
  335. package/templates/bootstrap/.env.template +54 -0
  336. package/templates/bootstrap/docker-compose.yml +145 -0
  337. package/templates/vhost.conf.tmpl +446 -0
@@ -0,0 +1,114 @@
1
+ /**
2
+ * CiCore CLI — Nginx domain map primitives
3
+ *
4
+ * VuCore's multi-core architecture routes incoming requests to a specific
5
+ * core via nginx `map` blocks in `vucore-global-router.conf`. Two maps
6
+ * matter for per-core onboarding:
7
+ *
8
+ * map $host $target_core {
9
+ * # {{DOMAIN_CORE_MAP}} - Placeholder for dynamic domain entries
10
+ * default core1;
11
+ * }
12
+ *
13
+ * map $target_core $core_path {
14
+ * default /var/www/cores/core1;
15
+ * # {{CORE_PATH_MAP}} - Placeholder for dynamic core entries
16
+ * }
17
+ *
18
+ * Each `vu setup <core>` invocation needs to:
19
+ * 1. Add `"<domain>" "<core>";` to DOMAIN_CORE_MAP
20
+ * 2. Add `"<core>" /var/www/cores/<core>;` to CORE_PATH_MAP
21
+ * 3. Reload nginx (no restart — bind-mounted config picks up SIGHUP)
22
+ *
23
+ * Edits insert *above* the placeholder comment so the marker stays
24
+ * visible for the next edit. Idempotent — re-running with the same
25
+ * domain is a no-op. Atomic — config is written to a temp file then
26
+ * `mv`'d, so nginx never sees a partial file mid-edit.
27
+ *
28
+ * After every config change `nginx -t` validates syntax before reload
29
+ * is attempted; an invalid config rolls back via the temp-file pattern.
30
+ */
31
+ import type { HostConfig } from '../hosts.js';
32
+ import { type ExecResult } from './ssh.js';
33
+ /**
34
+ * Add (or confirm) `<domain> → <core>` routing in the global router.
35
+ *
36
+ * Two-part operation:
37
+ * 1. Insert `"<domain>" "<core>";` into the DOMAIN_CORE_MAP block
38
+ * 2. Ensure the core's path entry exists in CORE_PATH_MAP (idempotent)
39
+ *
40
+ * Both inserts are skipped if their target line already exists, so a
41
+ * repeated `vu setup` against the same core+domain is a no-op for nginx.
42
+ *
43
+ * Returns `{ success, errorMessage }` from the worst step. On success,
44
+ * the caller should follow up with {@link nginxReload}.
45
+ */
46
+ export declare function nginxDomainAdd(cfg: HostConfig, domain: string, core: string): Promise<ExecResult>;
47
+ /**
48
+ * Ensure a core has a CORE_PATH_MAP entry, regardless of whether any
49
+ * domain points at it. Useful for prepping a core (e.g. `vu setup core2`
50
+ * without `--domain`) before its public domain is decided.
51
+ */
52
+ export declare function nginxEnsureCorePath(cfg: HostConfig, core: string): Promise<ExecResult>;
53
+ /**
54
+ * Remove a domain → core entry from DOMAIN_CORE_MAP. Idempotent: if the
55
+ * entry isn't there, the op succeeds without writing. Does not touch
56
+ * CORE_PATH_MAP because other domains may still point at the core.
57
+ */
58
+ export declare function nginxDomainRemove(cfg: HostConfig, domain: string): Promise<ExecResult>;
59
+ /**
60
+ * List the domains currently mapped to `core` in the DOMAIN_CORE_MAP block.
61
+ * Reconcile uses this to find orphans: a domain nginx still routes to a brand's
62
+ * core but that the control-plane no longer lists (e.g. a detached domain whose
63
+ * cp_domains row is gone). Parsing is scoped to the DOMAIN_CORE_MAP block so a
64
+ * `"<core>" /var/www/...` line in CORE_PATH_MAP or a token in an unrelated map
65
+ * (CORS, tenant→brand) can't be mistaken for a routing entry.
66
+ */
67
+ export declare function nginxListDomainsForCore(cfg: HostConfig, core: string): Promise<string[]>;
68
+ /**
69
+ * Remove a per-domain vhost file (`<domain_underscored>.conf`). Idempotent —
70
+ * `rm -f` succeeds whether or not the file is there. The map entry is removed
71
+ * separately via {@link nginxDomainRemove}; caller reloads after.
72
+ */
73
+ export declare function nginxRemoveVhost(cfg: HostConfig, domain: string): Promise<ExecResult>;
74
+ /**
75
+ * Validate nginx configuration syntax inside the nginx container.
76
+ * Returns the typical `nginx: configuration file ... test is successful`
77
+ * output on success, error message on failure.
78
+ */
79
+ export declare function nginxValidate(cfg: HostConfig): Promise<ExecResult>;
80
+ /**
81
+ * Trigger a hot reload of nginx (`nginx -s reload`). Existing connections
82
+ * drain on the old worker; new ones use the new config.
83
+ *
84
+ * Always validates with `nginx -t` first — a syntax error during reload
85
+ * could leave the master process in a degraded state.
86
+ */
87
+ export declare function nginxReload(cfg: HostConfig): Promise<ExecResult>;
88
+ /**
89
+ * Render the bundled vhost template for a given core+domain and write it
90
+ * atomically to `${cfg.nginxConfigDir}/${domain_underscored}.conf`.
91
+ *
92
+ * Placeholder substitution:
93
+ * {{DOMAIN}} → e.g. `cicore.com.tr`
94
+ * {{DOMAIN_REGEX}} → dots regex-escaped, for nginx `server_name ~^…$`
95
+ * {{CORE}} → e.g. `vucore`
96
+ * {{SSL_CERT_NAME}} → basename in `/etc/nginx/ssl/` (caller's choice;
97
+ * behind a Cloudflare Tunnel the cert CN need not
98
+ * match the served domain, so a shared cert works)
99
+ *
100
+ * The rendered vhost uses `$target_core` / `$core_path` map lookups
101
+ * defined in `vucore-global-router.conf` — there are no hardcoded core
102
+ * paths in the output, so adding a new core only requires (1) a global
103
+ * router map entry (handled by {@link nginxDomainAdd}) and (2) a vhost
104
+ * generated by this function.
105
+ *
106
+ * Atomic: writes to `<file>.tmp.$$`, chmod 644, then `sudo -n mv` into
107
+ * place. Idempotent: a re-run with identical inputs is a no-op (we read
108
+ * the current file first; if it byte-matches the rendered output we
109
+ * skip the write so file mtime stays meaningful for backups).
110
+ *
111
+ * Caller follows up with {@link nginxValidate} + {@link nginxReload}.
112
+ */
113
+ export declare function nginxWriteVhost(cfg: HostConfig, core: string, domain: string, sslCertName: string): Promise<ExecResult>;
114
+ //# sourceMappingURL=nginx.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nginx.d.ts","sourceRoot":"","sources":["../../../src/lib/ops/nginx.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAMH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAE7C,OAAO,EAA8B,KAAK,UAAU,EAAE,MAAM,UAAU,CAAA;AAmCtE;;;;;;;;;;;;GAYG;AACH,wBAAsB,cAAc,CAClC,GAAG,EAAE,UAAU,EACf,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,UAAU,CAAC,CA0CrB;AAED;;;;GAIG;AACH,wBAAsB,mBAAmB,CAAC,GAAG,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAM5F;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAO5F;AAED;;;;;;;GAOG;AACH,wBAAsB,uBAAuB,CAAC,GAAG,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAsB9F;AAED;;;;GAIG;AACH,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAM3F;AAED;;;;GAIG;AACH,wBAAsB,aAAa,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,CAExE;AAED;;;;;;GAMG;AACH,wBAAsB,WAAW,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,CAYtE;AA8JD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAsB,eAAe,CACnC,GAAG,EAAE,UAAU,EACf,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,UAAU,CAAC,CAgErB"}
@@ -0,0 +1,388 @@
1
+ /**
2
+ * CiCore CLI — Nginx domain map primitives
3
+ *
4
+ * VuCore's multi-core architecture routes incoming requests to a specific
5
+ * core via nginx `map` blocks in `vucore-global-router.conf`. Two maps
6
+ * matter for per-core onboarding:
7
+ *
8
+ * map $host $target_core {
9
+ * # {{DOMAIN_CORE_MAP}} - Placeholder for dynamic domain entries
10
+ * default core1;
11
+ * }
12
+ *
13
+ * map $target_core $core_path {
14
+ * default /var/www/cores/core1;
15
+ * # {{CORE_PATH_MAP}} - Placeholder for dynamic core entries
16
+ * }
17
+ *
18
+ * Each `vu setup <core>` invocation needs to:
19
+ * 1. Add `"<domain>" "<core>";` to DOMAIN_CORE_MAP
20
+ * 2. Add `"<core>" /var/www/cores/<core>;` to CORE_PATH_MAP
21
+ * 3. Reload nginx (no restart — bind-mounted config picks up SIGHUP)
22
+ *
23
+ * Edits insert *above* the placeholder comment so the marker stays
24
+ * visible for the next edit. Idempotent — re-running with the same
25
+ * domain is a no-op. Atomic — config is written to a temp file then
26
+ * `mv`'d, so nginx never sees a partial file mid-edit.
27
+ *
28
+ * After every config change `nginx -t` validates syntax before reload
29
+ * is attempted; an invalid config rolls back via the temp-file pattern.
30
+ */
31
+ import { promises as fs } from 'node:fs';
32
+ import path from 'node:path';
33
+ import { fileURLToPath } from 'node:url';
34
+ import { log } from '../logger.js';
35
+ import { runRemote, runRemoteScript } from './ssh.js';
36
+ const GLOBAL_ROUTER_FILE = 'vucore-global-router.conf';
37
+ /**
38
+ * Per-domain vhost template. Bundled under `cli/templates/` and shipped
39
+ * via the package.json `files` whitelist. From a compiled `dist/lib/ops/
40
+ * nginx.js` we walk up three levels to the package root, then into
41
+ * `templates/`. Resolved once at module load so we don't re-stat on
42
+ * every call.
43
+ */
44
+ const VHOST_TEMPLATE_PATH = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..', '..', 'templates', 'vhost.conf.tmpl');
45
+ // Placeholder comments inserted by the original setup; we anchor inserts
46
+ // to these. If a host's config has had the placeholders stripped, the ops
47
+ // fail loudly rather than silently appending to the wrong location.
48
+ const PH_DOMAIN_CORE_MAP = '# {{DOMAIN_CORE_MAP}}';
49
+ const PH_CORE_PATH_MAP = '# {{CORE_PATH_MAP}}';
50
+ /** Identifier rule for core names — same as Postgres (a–z, A–Z, 0–9, _). */
51
+ const CORE_NAME_RE = /^[a-zA-Z_][a-zA-Z0-9_-]{0,62}$/;
52
+ /** Domain rule — letters, digits, dots, hyphens. Disallows shell metas. */
53
+ const DOMAIN_RE = /^[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+$/;
54
+ // ─────────────────────────────────────────────────────────────────────────
55
+ // Public primitives
56
+ // ─────────────────────────────────────────────────────────────────────────
57
+ /**
58
+ * Add (or confirm) `<domain> → <core>` routing in the global router.
59
+ *
60
+ * Two-part operation:
61
+ * 1. Insert `"<domain>" "<core>";` into the DOMAIN_CORE_MAP block
62
+ * 2. Ensure the core's path entry exists in CORE_PATH_MAP (idempotent)
63
+ *
64
+ * Both inserts are skipped if their target line already exists, so a
65
+ * repeated `vu setup` against the same core+domain is a no-op for nginx.
66
+ *
67
+ * Returns `{ success, errorMessage }` from the worst step. On success,
68
+ * the caller should follow up with {@link nginxReload}.
69
+ */
70
+ export async function nginxDomainAdd(cfg, domain, core) {
71
+ assertDomain(domain);
72
+ assertCoreName(core);
73
+ log.info(`[nginx] map ${domain} → ${core} on ${cfg.brandName}`);
74
+ // Step 1: DOMAIN_CORE_MAP entry — insert above the placeholder.
75
+ const domainEntry = ` "${domain}" "${core}";`;
76
+ const domainStep = await insertAboveMarker(cfg, GLOBAL_ROUTER_FILE, PH_DOMAIN_CORE_MAP, domainEntry, {
77
+ // Skip if any existing line maps this exact domain (regardless of core)
78
+ // — caller should remove first if remapping is intended.
79
+ skipIfPresent: new RegExp(`^\\s*"${escapeRegex(domain)}"\\s+"[^"]+";`, 'm'),
80
+ });
81
+ if (!domainStep.success)
82
+ return domainStep;
83
+ // Step 1b: the API subdomain (api.<domain>) routes to the SAME core. The
84
+ // frontend's apiBase is https://api.<domain>; without this map entry the api
85
+ // host falls through to the default core and every API call hits the wrong
86
+ // (or missing) core → app errors. (Surfaced 2026-05-24 on multinodex + lab;
87
+ // was being added by hand. Now automatic so `ci setup` is reproducible.)
88
+ const apiDomain = `api.${domain}`;
89
+ const apiStep = await insertAboveMarker(cfg, GLOBAL_ROUTER_FILE, PH_DOMAIN_CORE_MAP, ` "${apiDomain}" "${core}";`, { skipIfPresent: new RegExp(`^\\s*"${escapeRegex(apiDomain)}"\\s+"[^"]+";`, 'm') });
90
+ if (!apiStep.success)
91
+ return apiStep;
92
+ // Step 2: CORE_PATH_MAP entry for this core, if not already there.
93
+ // Same insertion pattern.
94
+ const pathEntry = ` "${core}" /var/www/cores/${core};`;
95
+ return insertAboveMarker(cfg, GLOBAL_ROUTER_FILE, PH_CORE_PATH_MAP, pathEntry, {
96
+ skipIfPresent: new RegExp(`^\\s*"${escapeRegex(core)}"\\s+/var/www/cores/`, 'm'),
97
+ });
98
+ }
99
+ /**
100
+ * Ensure a core has a CORE_PATH_MAP entry, regardless of whether any
101
+ * domain points at it. Useful for prepping a core (e.g. `vu setup core2`
102
+ * without `--domain`) before its public domain is decided.
103
+ */
104
+ export async function nginxEnsureCorePath(cfg, core) {
105
+ assertCoreName(core);
106
+ const pathEntry = ` "${core}" /var/www/cores/${core};`;
107
+ return insertAboveMarker(cfg, GLOBAL_ROUTER_FILE, PH_CORE_PATH_MAP, pathEntry, {
108
+ skipIfPresent: new RegExp(`^\\s*"${escapeRegex(core)}"\\s+/var/www/cores/`, 'm'),
109
+ });
110
+ }
111
+ /**
112
+ * Remove a domain → core entry from DOMAIN_CORE_MAP. Idempotent: if the
113
+ * entry isn't there, the op succeeds without writing. Does not touch
114
+ * CORE_PATH_MAP because other domains may still point at the core.
115
+ */
116
+ export async function nginxDomainRemove(cfg, domain) {
117
+ assertDomain(domain);
118
+ log.info(`[nginx] unmap ${domain} on ${cfg.brandName}`);
119
+ // Match the whole line (with optional leading whitespace) and the trailing
120
+ // newline so removal leaves no blank line behind.
121
+ const pattern = new RegExp(`^\\s*"${escapeRegex(domain)}"\\s+"[^"]+";\\s*\\n?`, 'gm');
122
+ return rewriteFile(cfg, GLOBAL_ROUTER_FILE, content => content.replace(pattern, ''));
123
+ }
124
+ /**
125
+ * List the domains currently mapped to `core` in the DOMAIN_CORE_MAP block.
126
+ * Reconcile uses this to find orphans: a domain nginx still routes to a brand's
127
+ * core but that the control-plane no longer lists (e.g. a detached domain whose
128
+ * cp_domains row is gone). Parsing is scoped to the DOMAIN_CORE_MAP block so a
129
+ * `"<core>" /var/www/...` line in CORE_PATH_MAP or a token in an unrelated map
130
+ * (CORS, tenant→brand) can't be mistaken for a routing entry.
131
+ */
132
+ export async function nginxListDomainsForCore(cfg, core) {
133
+ assertCoreName(core);
134
+ const fullPath = `${cfg.nginxConfigDir}/${GLOBAL_ROUTER_FILE}`;
135
+ const read = await runRemote(cfg, `cat ${shellQuote(fullPath)}`, { silent: true });
136
+ if (!read.success) {
137
+ log.warn(`[nginx] could not read ${GLOBAL_ROUTER_FILE} to list domains for ${core}: ${read.stderr.trim()}`);
138
+ return [];
139
+ }
140
+ const content = read.stdout;
141
+ const markerIdx = content.indexOf(PH_DOMAIN_CORE_MAP);
142
+ if (markerIdx === -1)
143
+ return [];
144
+ const blockStart = content.lastIndexOf('{', markerIdx);
145
+ const blockEnd = content.indexOf('}', markerIdx);
146
+ const block = content.slice(blockStart === -1 ? 0 : blockStart, blockEnd === -1 ? content.length : blockEnd);
147
+ const domains = [];
148
+ const lineRe = /"([^"]+)"\s+"([^"]+)";/g;
149
+ let m;
150
+ while ((m = lineRe.exec(block)) !== null) {
151
+ if (m[2] === core)
152
+ domains.push(m[1]);
153
+ }
154
+ return domains;
155
+ }
156
+ /**
157
+ * Remove a per-domain vhost file (`<domain_underscored>.conf`). Idempotent —
158
+ * `rm -f` succeeds whether or not the file is there. The map entry is removed
159
+ * separately via {@link nginxDomainRemove}; caller reloads after.
160
+ */
161
+ export async function nginxRemoveVhost(cfg, domain) {
162
+ assertDomain(domain);
163
+ const vhostFilename = `${domain.replace(/\./g, '_')}.conf`;
164
+ const fullPath = `${cfg.nginxConfigDir}/${vhostFilename}`;
165
+ log.info(`[nginx] remove vhost ${vhostFilename} on ${cfg.brandName}`);
166
+ return runRemoteScript(cfg, `set -e\nsudo -n rm -f ${shellQuote(fullPath)}\n`, { silent: true });
167
+ }
168
+ /**
169
+ * Validate nginx configuration syntax inside the nginx container.
170
+ * Returns the typical `nginx: configuration file ... test is successful`
171
+ * output on success, error message on failure.
172
+ */
173
+ export async function nginxValidate(cfg) {
174
+ return runRemote(cfg, `docker exec ${cfg.containerNames.nginx} nginx -t`, { silent: true });
175
+ }
176
+ /**
177
+ * Trigger a hot reload of nginx (`nginx -s reload`). Existing connections
178
+ * drain on the old worker; new ones use the new config.
179
+ *
180
+ * Always validates with `nginx -t` first — a syntax error during reload
181
+ * could leave the master process in a degraded state.
182
+ */
183
+ export async function nginxReload(cfg) {
184
+ log.info(`[nginx] validate + reload on ${cfg.brandName}`);
185
+ const validate = await nginxValidate(cfg);
186
+ if (!validate.success) {
187
+ return Object.freeze({
188
+ stdout: validate.stdout,
189
+ stderr: `nginx -t failed; refusing to reload:\n${validate.stderr}`,
190
+ exitCode: validate.exitCode,
191
+ success: false,
192
+ });
193
+ }
194
+ return runRemote(cfg, `docker exec ${cfg.containerNames.nginx} nginx -s reload`);
195
+ }
196
+ /**
197
+ * Read a config file, insert `entry` on the line immediately above the
198
+ * first occurrence of `marker`, write atomically. If `marker` isn't
199
+ * found, the op fails — never falls back to appending.
200
+ *
201
+ * Idempotent when `skipIfPresent` matches existing content.
202
+ */
203
+ async function insertAboveMarker(cfg, filename, marker, entry, opts = {}) {
204
+ return rewriteFile(cfg, filename, content => {
205
+ const markerIdx = content.indexOf(marker);
206
+ if (markerIdx === -1) {
207
+ throw new Error(`Marker '${marker}' not found in ${filename}. ` +
208
+ `The global router config may have been hand-edited; restore it from git or re-run setup.`);
209
+ }
210
+ // Idempotency check is scoped to the block the marker lives in — from the
211
+ // enclosing `{` up to the marker — NOT the whole file. A domain/core token
212
+ // can legitimately appear in unrelated maps (CORS, tenant→brand, etc.); a
213
+ // whole-file test would false-positive there and skip the real insert into
214
+ // THIS block. (Bug surfaced 2026-05-24: multinodex.com present in the CORS
215
+ // map made the DOMAIN_CORE_MAP insert a silent no-op → broken routing.)
216
+ if (opts.skipIfPresent) {
217
+ const braceIdx = content.lastIndexOf('{', markerIdx);
218
+ const blockScope = content.slice(braceIdx === -1 ? 0 : braceIdx, markerIdx);
219
+ if (opts.skipIfPresent.test(blockScope)) {
220
+ log.debug(`[nginx] entry already present in target block — skip insert`);
221
+ return content; // no-op
222
+ }
223
+ }
224
+ // Find the start of the line containing the marker so we can insert
225
+ // entry+newline before the entire marker line.
226
+ let lineStart = markerIdx;
227
+ while (lineStart > 0 && content[lineStart - 1] !== '\n') {
228
+ lineStart--;
229
+ }
230
+ return content.slice(0, lineStart) + entry + '\n' + content.slice(lineStart);
231
+ });
232
+ }
233
+ /**
234
+ * Read the named config file from the server, apply `transform` to its
235
+ * contents, write atomically (temp file + mv) if changed. Skips the write
236
+ * when transform is a no-op so file mtime stays meaningful for backups.
237
+ *
238
+ * Atomicity: writes to `<file>.tmp.<pid>`, then `mv` into place. If the
239
+ * write or mv fails midway, the original file is untouched. The temp file
240
+ * is `chmod 644` (matching the original's typical mode).
241
+ *
242
+ * The transform runs locally on the file content — efficient for typical
243
+ * configs (<100KB) and avoids fragile in-place sed regex on the server.
244
+ */
245
+ async function rewriteFile(cfg, filename, transform) {
246
+ const fullPath = `${cfg.nginxConfigDir}/${filename}`;
247
+ // Read current content.
248
+ const readResult = await runRemote(cfg, `cat ${shellQuote(fullPath)}`, { silent: true });
249
+ if (!readResult.success) {
250
+ return Object.freeze({
251
+ stdout: '',
252
+ stderr: `Failed to read ${fullPath}: ${readResult.stderr}`,
253
+ exitCode: readResult.exitCode,
254
+ success: false,
255
+ });
256
+ }
257
+ let nextContent;
258
+ try {
259
+ nextContent = transform(readResult.stdout);
260
+ }
261
+ catch (error) {
262
+ const message = error instanceof Error ? error.message : String(error);
263
+ return Object.freeze({
264
+ stdout: '',
265
+ stderr: message,
266
+ exitCode: 1,
267
+ success: false,
268
+ });
269
+ }
270
+ if (nextContent === readResult.stdout) {
271
+ log.debug(`[nginx] ${filename} unchanged — skip write`);
272
+ return Object.freeze({ stdout: '', stderr: '', exitCode: 0, success: true });
273
+ }
274
+ // Atomic write via temp file + mv. Sudo path is needed because nginx
275
+ // config files are typically owned by root (nginx container init).
276
+ // The "sudo -n" is non-interactive — relies on NOPASSWD sudoers setup.
277
+ const script = `set -e\n` +
278
+ `tmp="${fullPath}.tmp.$$"\n` +
279
+ `cat > "$tmp" <<'__VU_NGINX_EOF__'\n` +
280
+ nextContent +
281
+ (nextContent.endsWith('\n') ? '' : '\n') +
282
+ `__VU_NGINX_EOF__\n` +
283
+ `sudo -n chmod 644 "$tmp"\n` +
284
+ `sudo -n mv "$tmp" ${shellQuote(fullPath)}\n`;
285
+ return runRemoteScript(cfg, script, { silent: true });
286
+ }
287
+ // ─────────────────────────────────────────────────────────────────────────
288
+ // Validation helpers
289
+ // ─────────────────────────────────────────────────────────────────────────
290
+ function assertDomain(domain) {
291
+ if (!DOMAIN_RE.test(domain)) {
292
+ throw new Error(`Invalid domain '${domain}'. Must be a valid hostname (letters, digits, hyphens, dots).`);
293
+ }
294
+ }
295
+ function assertCoreName(core) {
296
+ if (!CORE_NAME_RE.test(core)) {
297
+ throw new Error(`Invalid core name '${core}'. Must match /^[a-zA-Z_][a-zA-Z0-9_-]{0,62}$/.`);
298
+ }
299
+ }
300
+ function escapeRegex(s) {
301
+ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
302
+ }
303
+ function shellQuote(s) {
304
+ return `'${s.replace(/'/g, `'\\''`)}'`;
305
+ }
306
+ // ─────────────────────────────────────────────────────────────────────────
307
+ // Per-domain vhost rendering
308
+ // ─────────────────────────────────────────────────────────────────────────
309
+ /**
310
+ * Render the bundled vhost template for a given core+domain and write it
311
+ * atomically to `${cfg.nginxConfigDir}/${domain_underscored}.conf`.
312
+ *
313
+ * Placeholder substitution:
314
+ * {{DOMAIN}} → e.g. `cicore.com.tr`
315
+ * {{DOMAIN_REGEX}} → dots regex-escaped, for nginx `server_name ~^…$`
316
+ * {{CORE}} → e.g. `vucore`
317
+ * {{SSL_CERT_NAME}} → basename in `/etc/nginx/ssl/` (caller's choice;
318
+ * behind a Cloudflare Tunnel the cert CN need not
319
+ * match the served domain, so a shared cert works)
320
+ *
321
+ * The rendered vhost uses `$target_core` / `$core_path` map lookups
322
+ * defined in `vucore-global-router.conf` — there are no hardcoded core
323
+ * paths in the output, so adding a new core only requires (1) a global
324
+ * router map entry (handled by {@link nginxDomainAdd}) and (2) a vhost
325
+ * generated by this function.
326
+ *
327
+ * Atomic: writes to `<file>.tmp.$$`, chmod 644, then `sudo -n mv` into
328
+ * place. Idempotent: a re-run with identical inputs is a no-op (we read
329
+ * the current file first; if it byte-matches the rendered output we
330
+ * skip the write so file mtime stays meaningful for backups).
331
+ *
332
+ * Caller follows up with {@link nginxValidate} + {@link nginxReload}.
333
+ */
334
+ export async function nginxWriteVhost(cfg, core, domain, sslCertName) {
335
+ assertCoreName(core);
336
+ assertDomain(domain);
337
+ if (!sslCertName || !/^[a-zA-Z0-9._-]+$/.test(sslCertName)) {
338
+ return Object.freeze({
339
+ stdout: '',
340
+ stderr: `Invalid sslCertName '${sslCertName}'. Must be a filename-safe basename.`,
341
+ exitCode: 1,
342
+ success: false,
343
+ });
344
+ }
345
+ log.info(`[nginx] write vhost ${domain} → core=${core} (ssl=${sslCertName}) on ${cfg.brandName}`);
346
+ // Load + render template.
347
+ let tmpl;
348
+ try {
349
+ tmpl = await fs.readFile(VHOST_TEMPLATE_PATH, 'utf8');
350
+ }
351
+ catch (error) {
352
+ const message = error instanceof Error ? error.message : String(error);
353
+ return Object.freeze({
354
+ stdout: '',
355
+ stderr: `Failed to read vhost template at ${VHOST_TEMPLATE_PATH}: ${message}`,
356
+ exitCode: 1,
357
+ success: false,
358
+ });
359
+ }
360
+ const domainRegex = domain.replace(/\./g, '\\.');
361
+ const rendered = tmpl
362
+ .replace(/\{\{DOMAIN_REGEX\}\}/g, domainRegex)
363
+ .replace(/\{\{DOMAIN\}\}/g, domain)
364
+ .replace(/\{\{CORE\}\}/g, core)
365
+ .replace(/\{\{SSL_CERT_NAME\}\}/g, sslCertName);
366
+ const vhostFilename = `${domain.replace(/\./g, '_')}.conf`;
367
+ const fullPath = `${cfg.nginxConfigDir}/${vhostFilename}`;
368
+ // Idempotency check: read existing file (if any); skip write when
369
+ // contents match byte-for-byte. `cat || true` so a missing file
370
+ // doesn't fail the read step — we just treat absent as empty.
371
+ const readResult = await runRemote(cfg, `cat ${shellQuote(fullPath)} 2>/dev/null || true`, { silent: true });
372
+ if (readResult.success && readResult.stdout === rendered) {
373
+ log.debug(`[nginx] vhost ${vhostFilename} already up to date — skip`);
374
+ return Object.freeze({ stdout: '', stderr: '', exitCode: 0, success: true });
375
+ }
376
+ // Atomic write: heredoc into a `.tmp.$$` file, chmod, then mv. Uses
377
+ // `sudo -n` (NOPASSWD) because nginx conf.d is typically root-owned.
378
+ const script = `set -e\n` +
379
+ `tmp="${fullPath}.tmp.$$"\n` +
380
+ `cat > "$tmp" <<'__VU_NGINX_VHOST_EOF__'\n` +
381
+ rendered +
382
+ (rendered.endsWith('\n') ? '' : '\n') +
383
+ `__VU_NGINX_VHOST_EOF__\n` +
384
+ `sudo -n chmod 644 "$tmp"\n` +
385
+ `sudo -n mv "$tmp" ${shellQuote(fullPath)}\n`;
386
+ return runRemoteScript(cfg, script, { silent: true });
387
+ }
388
+ //# sourceMappingURL=nginx.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nginx.js","sourceRoot":"","sources":["../../../src/lib/ops/nginx.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import type { HostConfig } from '../hosts.js';
2
+ /**
3
+ * Bir brand'in paylaşımlı freeze-status Redis key'ini sil (F1 write-through).
4
+ * brandId UUID guard (shell'e yalnız hex+dash). Best-effort — hata yutulur.
5
+ */
6
+ export declare function invalidateBrandStatus(cfg: HostConfig, brandId: string): Promise<void>;
7
+ //# sourceMappingURL=redis.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redis.d.ts","sourceRoot":"","sources":["../../../src/lib/ops/redis.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAO7C;;;GAGG;AACH,wBAAsB,qBAAqB,CAAC,GAAG,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAY3F"}
@@ -0,0 +1,35 @@
1
+ // ─────────────────────────────────────────────────────────────────────────
2
+ // Redis ops — F1 freeze-status invalidation (izole, yol B).
3
+ // Plan: docs/planning/2026-06-02-f1-redis-freeze-invalidation.md
4
+ //
5
+ // Orchestrator (relocate/promote-silo/rollback-silo) bir brand'in freeze
6
+ // status'unu raw SQL ile değiştirdiğinde, PHP-FPM worker'larının paylaştığı
7
+ // freeze-status Redis key'ini siler → anlık cross-worker invalidation (PHP
8
+ // TenantDbResolver::brandStatusById ile AYNI key). Best-effort: asla throw
9
+ // etmez — Redis hızlandırıcı, DB otorite; başarısızsa PHP TTL self-heal eder.
10
+ // ─────────────────────────────────────────────────────────────────────────
11
+ import { runRemoteScript } from './ssh.js';
12
+ import { log } from '../logger.js';
13
+ // PHP TenantDbResolver::FREEZE_REDIS_PREFIX + REDIS_DB_CACHE ile birebir aynı olmalı.
14
+ const FREEZE_PREFIX = 'cp:brand_status:';
15
+ const REDIS_CACHE_DB = 1;
16
+ /**
17
+ * Bir brand'in paylaşımlı freeze-status Redis key'ini sil (F1 write-through).
18
+ * brandId UUID guard (shell'e yalnız hex+dash). Best-effort — hata yutulur.
19
+ */
20
+ export async function invalidateBrandStatus(cfg, brandId) {
21
+ const container = cfg.containerNames?.redis;
22
+ if (!container)
23
+ return;
24
+ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(brandId))
25
+ return;
26
+ const script = `docker exec ${container} redis-cli -n ${REDIS_CACHE_DB} DEL '${FREEZE_PREFIX}${brandId}' >/dev/null 2>&1 || true`;
27
+ try {
28
+ // allowInDryRun YOK: dry-run'da status değişmediği için invalidation da gereksiz.
29
+ await runRemoteScript(cfg, script, { silent: true });
30
+ }
31
+ catch (e) {
32
+ log.debug?.('[redis] freeze invalidate skipped: ' + (e instanceof Error ? e.message : String(e)));
33
+ }
34
+ }
35
+ //# sourceMappingURL=redis.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redis.js","sourceRoot":"","sources":["../../../src/lib/ops/redis.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,4DAA4D;AAC5D,iEAAiE;AACjE,EAAE;AACF,yEAAyE;AACzE,4EAA4E;AAC5E,2EAA2E;AAC3E,2EAA2E;AAC3E,8EAA8E;AAC9E,4EAA4E;AAE5E,OAAO,EAAE,eAAe,EAAE,MAAM,UAAU,CAAA;AAE1C,OAAO,EAAE,GAAG,EAAE,MAAM,cAAc,CAAA;AAElC,sFAAsF;AACtF,MAAM,aAAa,GAAG,kBAAkB,CAAA;AACxC,MAAM,cAAc,GAAG,CAAC,CAAA;AAExB;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,GAAe,EAAE,OAAe;IAC1E,MAAM,SAAS,GAAG,GAAG,CAAC,cAAc,EAAE,KAAK,CAAA;IAC3C,IAAI,CAAC,SAAS;QAAE,OAAM;IACtB,IAAI,CAAC,iEAAiE,CAAC,IAAI,CAAC,OAAO,CAAC;QAAE,OAAM;IAC5F,MAAM,MAAM,GACV,eAAe,SAAS,iBAAiB,cAAc,SAAS,aAAa,GAAG,OAAO,2BAA2B,CAAA;IACpH,IAAI,CAAC;QACH,kFAAkF;QAClF,MAAM,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAA;IACtD,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,GAAG,CAAC,KAAK,EAAE,CAAC,qCAAqC,GAAG,CAAC,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;IACnG,CAAC;AACH,CAAC"}