@lamalibre/create-lamaste 4.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 (270) hide show
  1. package/LICENSE.md +118 -0
  2. package/README.md +114 -0
  3. package/bin/create-lamaste.js +15 -0
  4. package/package.json +54 -0
  5. package/scripts/bundle-vendor.js +110 -0
  6. package/scripts/lamaste-cert-info +62 -0
  7. package/scripts/lamaste-certbot +96 -0
  8. package/scripts/lamaste-priv +693 -0
  9. package/src/index.js +646 -0
  10. package/src/lib/accounts.js +86 -0
  11. package/src/lib/binaries.js +137 -0
  12. package/src/lib/cert-help-page.js +160 -0
  13. package/src/lib/env.js +132 -0
  14. package/src/lib/http-redirect.js +38 -0
  15. package/src/lib/ownership.js +231 -0
  16. package/src/lib/relay-services.js +103 -0
  17. package/src/lib/secrets.js +19 -0
  18. package/src/lib/service-config.js +311 -0
  19. package/src/lib/summary.js +131 -0
  20. package/src/lib/wrappers.js +63 -0
  21. package/src/tasks/gatekeeper.js +233 -0
  22. package/src/tasks/harden.js +293 -0
  23. package/src/tasks/mtls.js +193 -0
  24. package/src/tasks/nginx.js +265 -0
  25. package/src/tasks/node.js +110 -0
  26. package/src/tasks/panel.js +408 -0
  27. package/src/tasks/redeploy.js +438 -0
  28. package/vendor/docs/00-introduction/how-it-works.md +447 -0
  29. package/vendor/docs/00-introduction/quickstart.md +472 -0
  30. package/vendor/docs/00-introduction/what-is-lamaste.md +112 -0
  31. package/vendor/docs/01-concepts/authentication.md +464 -0
  32. package/vendor/docs/01-concepts/authorization.md +242 -0
  33. package/vendor/docs/01-concepts/certificates.md +452 -0
  34. package/vendor/docs/01-concepts/dns-and-domains.md +343 -0
  35. package/vendor/docs/01-concepts/mtls.md +490 -0
  36. package/vendor/docs/01-concepts/nginx-reverse-proxy.md +540 -0
  37. package/vendor/docs/01-concepts/security-model.md +600 -0
  38. package/vendor/docs/01-concepts/tickets.md +414 -0
  39. package/vendor/docs/01-concepts/tunneling.md +367 -0
  40. package/vendor/docs/02-guides/agent-setup.md +194 -0
  41. package/vendor/docs/02-guides/certificate-management.md +341 -0
  42. package/vendor/docs/02-guides/cloud-provisioning.md +406 -0
  43. package/vendor/docs/02-guides/desktop-app-setup.md +226 -0
  44. package/vendor/docs/02-guides/disaster-recovery.md +464 -0
  45. package/vendor/docs/02-guides/first-tunnel.md +230 -0
  46. package/vendor/docs/02-guides/installation.md +393 -0
  47. package/vendor/docs/02-guides/managing-users.md +255 -0
  48. package/vendor/docs/02-guides/onboarding.md +244 -0
  49. package/vendor/docs/02-guides/standalone-plugin-with-tickets.md +375 -0
  50. package/vendor/docs/02-guides/static-sites.md +364 -0
  51. package/vendor/docs/03-architecture/e2e-three-vm-sequences.md +457 -0
  52. package/vendor/docs/03-architecture/installer-flow.md +151 -0
  53. package/vendor/docs/03-architecture/installer.md +445 -0
  54. package/vendor/docs/03-architecture/management-flow.md +288 -0
  55. package/vendor/docs/03-architecture/nginx-configuration.md +830 -0
  56. package/vendor/docs/03-architecture/onboarding-flow.md +164 -0
  57. package/vendor/docs/03-architecture/overview.md +541 -0
  58. package/vendor/docs/03-architecture/panel-client.md +416 -0
  59. package/vendor/docs/03-architecture/panel-server.md +695 -0
  60. package/vendor/docs/03-architecture/state-management.md +529 -0
  61. package/vendor/docs/03-architecture/system-overview.md +167 -0
  62. package/vendor/docs/04-api-reference/certificates.md +1141 -0
  63. package/vendor/docs/04-api-reference/gatekeeper.md +592 -0
  64. package/vendor/docs/04-api-reference/onboarding.md +473 -0
  65. package/vendor/docs/04-api-reference/overview.md +404 -0
  66. package/vendor/docs/04-api-reference/services.md +267 -0
  67. package/vendor/docs/04-api-reference/settings.md +343 -0
  68. package/vendor/docs/04-api-reference/sites.md +716 -0
  69. package/vendor/docs/04-api-reference/system.md +268 -0
  70. package/vendor/docs/04-api-reference/tickets.md +618 -0
  71. package/vendor/docs/04-api-reference/tunnels.md +417 -0
  72. package/vendor/docs/04-api-reference/users.md +333 -0
  73. package/vendor/docs/05-operations/backup-and-restore.md +342 -0
  74. package/vendor/docs/05-operations/monitoring.md +285 -0
  75. package/vendor/docs/05-operations/uninstalling.md +344 -0
  76. package/vendor/docs/05-operations/upgrades.md +270 -0
  77. package/vendor/docs/06-reference/config-files.md +1080 -0
  78. package/vendor/docs/06-reference/glossary.md +528 -0
  79. package/vendor/docs/06-reference/installer-flags.md +260 -0
  80. package/vendor/docs/06-reference/ports-and-services.md +244 -0
  81. package/vendor/docs/06-reference/troubleshooting.md +665 -0
  82. package/vendor/docs/_index.json +233 -0
  83. package/vendor/docs/gatekeeper-design.md +953 -0
  84. package/vendor/docs/package.json +15 -0
  85. package/vendor/docs/todo/lib-cli-desktop-refactor.md +641 -0
  86. package/vendor/gatekeeper/dist/boot.d.ts +1 -0
  87. package/vendor/gatekeeper/dist/boot.d.ts.map +1 -0
  88. package/vendor/gatekeeper/dist/boot.js +17 -0
  89. package/vendor/gatekeeper/dist/boot.js.map +1 -0
  90. package/vendor/gatekeeper/dist/cli/commands/access.d.ts +2 -0
  91. package/vendor/gatekeeper/dist/cli/commands/access.d.ts.map +1 -0
  92. package/vendor/gatekeeper/dist/cli/commands/access.js +33 -0
  93. package/vendor/gatekeeper/dist/cli/commands/access.js.map +1 -0
  94. package/vendor/gatekeeper/dist/cli/commands/grants.d.ts +2 -0
  95. package/vendor/gatekeeper/dist/cli/commands/grants.d.ts.map +1 -0
  96. package/vendor/gatekeeper/dist/cli/commands/grants.js +110 -0
  97. package/vendor/gatekeeper/dist/cli/commands/grants.js.map +1 -0
  98. package/vendor/gatekeeper/dist/cli/commands/groups.d.ts +2 -0
  99. package/vendor/gatekeeper/dist/cli/commands/groups.d.ts.map +1 -0
  100. package/vendor/gatekeeper/dist/cli/commands/groups.js +98 -0
  101. package/vendor/gatekeeper/dist/cli/commands/groups.js.map +1 -0
  102. package/vendor/gatekeeper/dist/cli/index.d.ts +3 -0
  103. package/vendor/gatekeeper/dist/cli/index.d.ts.map +1 -0
  104. package/vendor/gatekeeper/dist/cli/index.js +45 -0
  105. package/vendor/gatekeeper/dist/cli/index.js.map +1 -0
  106. package/vendor/gatekeeper/dist/index.d.ts +9 -0
  107. package/vendor/gatekeeper/dist/index.d.ts.map +1 -0
  108. package/vendor/gatekeeper/dist/index.js +13 -0
  109. package/vendor/gatekeeper/dist/index.js.map +1 -0
  110. package/vendor/gatekeeper/dist/lib/authz.d.ts +16 -0
  111. package/vendor/gatekeeper/dist/lib/authz.d.ts.map +1 -0
  112. package/vendor/gatekeeper/dist/lib/authz.js +42 -0
  113. package/vendor/gatekeeper/dist/lib/authz.js.map +1 -0
  114. package/vendor/gatekeeper/dist/lib/cache-bust.d.ts +15 -0
  115. package/vendor/gatekeeper/dist/lib/cache-bust.d.ts.map +1 -0
  116. package/vendor/gatekeeper/dist/lib/cache-bust.js +19 -0
  117. package/vendor/gatekeeper/dist/lib/cache-bust.js.map +1 -0
  118. package/vendor/gatekeeper/dist/lib/constants.d.ts +82 -0
  119. package/vendor/gatekeeper/dist/lib/constants.d.ts.map +1 -0
  120. package/vendor/gatekeeper/dist/lib/constants.js +79 -0
  121. package/vendor/gatekeeper/dist/lib/constants.js.map +1 -0
  122. package/vendor/gatekeeper/dist/lib/grants.d.ts +32 -0
  123. package/vendor/gatekeeper/dist/lib/grants.d.ts.map +1 -0
  124. package/vendor/gatekeeper/dist/lib/grants.js +314 -0
  125. package/vendor/gatekeeper/dist/lib/grants.js.map +1 -0
  126. package/vendor/gatekeeper/dist/lib/groups.d.ts +10 -0
  127. package/vendor/gatekeeper/dist/lib/groups.d.ts.map +1 -0
  128. package/vendor/gatekeeper/dist/lib/groups.js +247 -0
  129. package/vendor/gatekeeper/dist/lib/groups.js.map +1 -0
  130. package/vendor/gatekeeper/dist/lib/migration.d.ts +17 -0
  131. package/vendor/gatekeeper/dist/lib/migration.d.ts.map +1 -0
  132. package/vendor/gatekeeper/dist/lib/migration.js +79 -0
  133. package/vendor/gatekeeper/dist/lib/migration.js.map +1 -0
  134. package/vendor/gatekeeper/dist/lib/migrations/gatekeeper/0001_groups.sql +7 -0
  135. package/vendor/gatekeeper/dist/lib/migrations/gatekeeper/0002_access_grants.sql +14 -0
  136. package/vendor/gatekeeper/dist/lib/migrations/gatekeeper/0003_access_request_log.sql +9 -0
  137. package/vendor/gatekeeper/dist/lib/migrations/migrations/gatekeeper/0001_groups.sql +7 -0
  138. package/vendor/gatekeeper/dist/lib/migrations/migrations/gatekeeper/0002_access_grants.sql +14 -0
  139. package/vendor/gatekeeper/dist/lib/migrations/migrations/gatekeeper/0003_access_request_log.sql +9 -0
  140. package/vendor/gatekeeper/dist/lib/state-db.d.ts +16 -0
  141. package/vendor/gatekeeper/dist/lib/state-db.d.ts.map +1 -0
  142. package/vendor/gatekeeper/dist/lib/state-db.js +154 -0
  143. package/vendor/gatekeeper/dist/lib/state-db.js.map +1 -0
  144. package/vendor/gatekeeper/dist/lib/templates.d.ts +21 -0
  145. package/vendor/gatekeeper/dist/lib/templates.d.ts.map +1 -0
  146. package/vendor/gatekeeper/dist/lib/templates.js +151 -0
  147. package/vendor/gatekeeper/dist/lib/templates.js.map +1 -0
  148. package/vendor/gatekeeper/dist/lib/types.d.ts +178 -0
  149. package/vendor/gatekeeper/dist/lib/types.d.ts.map +1 -0
  150. package/vendor/gatekeeper/dist/lib/types.js +2 -0
  151. package/vendor/gatekeeper/dist/lib/types.js.map +1 -0
  152. package/vendor/gatekeeper/dist/server/index.d.ts +15 -0
  153. package/vendor/gatekeeper/dist/server/index.d.ts.map +1 -0
  154. package/vendor/gatekeeper/dist/server/index.js +251 -0
  155. package/vendor/gatekeeper/dist/server/index.js.map +1 -0
  156. package/vendor/gatekeeper/dist/server/routes/access-request.d.ts +7 -0
  157. package/vendor/gatekeeper/dist/server/routes/access-request.d.ts.map +1 -0
  158. package/vendor/gatekeeper/dist/server/routes/access-request.js +17 -0
  159. package/vendor/gatekeeper/dist/server/routes/access-request.js.map +1 -0
  160. package/vendor/gatekeeper/dist/server/routes/authz.d.ts +3 -0
  161. package/vendor/gatekeeper/dist/server/routes/authz.d.ts.map +1 -0
  162. package/vendor/gatekeeper/dist/server/routes/authz.js +279 -0
  163. package/vendor/gatekeeper/dist/server/routes/authz.js.map +1 -0
  164. package/vendor/gatekeeper/dist/server/routes/diagnostic.d.ts +13 -0
  165. package/vendor/gatekeeper/dist/server/routes/diagnostic.d.ts.map +1 -0
  166. package/vendor/gatekeeper/dist/server/routes/diagnostic.js +249 -0
  167. package/vendor/gatekeeper/dist/server/routes/diagnostic.js.map +1 -0
  168. package/vendor/gatekeeper/dist/server/routes/grants.d.ts +3 -0
  169. package/vendor/gatekeeper/dist/server/routes/grants.d.ts.map +1 -0
  170. package/vendor/gatekeeper/dist/server/routes/grants.js +112 -0
  171. package/vendor/gatekeeper/dist/server/routes/grants.js.map +1 -0
  172. package/vendor/gatekeeper/dist/server/routes/groups.d.ts +3 -0
  173. package/vendor/gatekeeper/dist/server/routes/groups.d.ts.map +1 -0
  174. package/vendor/gatekeeper/dist/server/routes/groups.js +139 -0
  175. package/vendor/gatekeeper/dist/server/routes/groups.js.map +1 -0
  176. package/vendor/gatekeeper/package.json +49 -0
  177. package/vendor/server/bin/lamaste-server.js +204 -0
  178. package/vendor/server/package.json +31 -0
  179. package/vendor/server/src/commands/certs.js +224 -0
  180. package/vendor/server/src/commands/chisel.js +132 -0
  181. package/vendor/server/src/commands/logs.js +49 -0
  182. package/vendor/server/src/commands/plugins.js +321 -0
  183. package/vendor/server/src/commands/reset-admin.js +478 -0
  184. package/vendor/server/src/commands/restart.js +94 -0
  185. package/vendor/server/src/commands/sites.js +257 -0
  186. package/vendor/server/src/commands/status.js +158 -0
  187. package/vendor/server/src/commands/tunnels.js +365 -0
  188. package/vendor/server/src/commands/uninstall.js +326 -0
  189. package/vendor/server/src/config.js +38 -0
  190. package/vendor/server/src/exec.js +26 -0
  191. package/vendor/server/src/logger.js +39 -0
  192. package/vendor/server/src/ndjson.js +56 -0
  193. package/vendor/server/src/panel-api.js +85 -0
  194. package/vendor/server/src/state.js +45 -0
  195. package/vendor/server-ui/dist/assets/index-B0nWuUyL.js +257 -0
  196. package/vendor/server-ui/dist/assets/index-BqWWFlTU.css +1 -0
  197. package/vendor/server-ui/dist/index.html +13 -0
  198. package/vendor/serverd/package.json +44 -0
  199. package/vendor/serverd/src/boot.js +13 -0
  200. package/vendor/serverd/src/index.js +191 -0
  201. package/vendor/serverd/src/lib/app-error.js +14 -0
  202. package/vendor/serverd/src/lib/authelia.js +85 -0
  203. package/vendor/serverd/src/lib/certbot.js +74 -0
  204. package/vendor/serverd/src/lib/chisel-args.js +6 -0
  205. package/vendor/serverd/src/lib/chisel-reconcile.js +232 -0
  206. package/vendor/serverd/src/lib/chisel-runtime.js +46 -0
  207. package/vendor/serverd/src/lib/chisel-users.js +109 -0
  208. package/vendor/serverd/src/lib/chisel.js +81 -0
  209. package/vendor/serverd/src/lib/config.js +190 -0
  210. package/vendor/serverd/src/lib/constants.js +8 -0
  211. package/vendor/serverd/src/lib/csr-signing.js +331 -0
  212. package/vendor/serverd/src/lib/enrollment.js +302 -0
  213. package/vendor/serverd/src/lib/files.js +19 -0
  214. package/vendor/serverd/src/lib/gatekeeper-client.js +135 -0
  215. package/vendor/serverd/src/lib/invite-page.js +281 -0
  216. package/vendor/serverd/src/lib/ip-utils.js +79 -0
  217. package/vendor/serverd/src/lib/migrations/state/0001_enrollment_tokens.sql +17 -0
  218. package/vendor/serverd/src/lib/migrations/state/0002_tickets.sql +70 -0
  219. package/vendor/serverd/src/lib/migrations/state/0003_plugins.sql +15 -0
  220. package/vendor/serverd/src/lib/migrations/state/0004_agents.sql +19 -0
  221. package/vendor/serverd/src/lib/mtls.js +795 -0
  222. package/vendor/serverd/src/lib/nginx.js +1090 -0
  223. package/vendor/serverd/src/lib/pki-paths.js +41 -0
  224. package/vendor/serverd/src/lib/pki-sign.js +103 -0
  225. package/vendor/serverd/src/lib/plugins.js +538 -0
  226. package/vendor/serverd/src/lib/push-install.js +390 -0
  227. package/vendor/serverd/src/lib/revocation.js +112 -0
  228. package/vendor/serverd/src/lib/services.js +131 -0
  229. package/vendor/serverd/src/lib/session.js +151 -0
  230. package/vendor/serverd/src/lib/state-db.js +130 -0
  231. package/vendor/serverd/src/lib/state.js +140 -0
  232. package/vendor/serverd/src/lib/storage.js +63 -0
  233. package/vendor/serverd/src/lib/system-stats.js +58 -0
  234. package/vendor/serverd/src/lib/tickets.js +1556 -0
  235. package/vendor/serverd/src/lib/totp.js +76 -0
  236. package/vendor/serverd/src/lib/tunnel-deps.js +41 -0
  237. package/vendor/serverd/src/lib/user-access-session.js +142 -0
  238. package/vendor/serverd/src/lib/user-access.js +52 -0
  239. package/vendor/serverd/src/middleware/errors.js +63 -0
  240. package/vendor/serverd/src/middleware/mtls.js +120 -0
  241. package/vendor/serverd/src/middleware/onboarding-guard.js +30 -0
  242. package/vendor/serverd/src/middleware/role-guard.js +58 -0
  243. package/vendor/serverd/src/middleware/twofa-session.js +65 -0
  244. package/vendor/serverd/src/middleware/user-access-session.js +52 -0
  245. package/vendor/serverd/src/routes/enrollment.js +120 -0
  246. package/vendor/serverd/src/routes/health.js +22 -0
  247. package/vendor/serverd/src/routes/invite.js +163 -0
  248. package/vendor/serverd/src/routes/management/agents.js +182 -0
  249. package/vendor/serverd/src/routes/management/certs.js +911 -0
  250. package/vendor/serverd/src/routes/management/gatekeeper-proxy.js +160 -0
  251. package/vendor/serverd/src/routes/management/identity.js +107 -0
  252. package/vendor/serverd/src/routes/management/invitations.js +166 -0
  253. package/vendor/serverd/src/routes/management/logs.js +218 -0
  254. package/vendor/serverd/src/routes/management/plugins.js +560 -0
  255. package/vendor/serverd/src/routes/management/services.js +63 -0
  256. package/vendor/serverd/src/routes/management/settings.js +325 -0
  257. package/vendor/serverd/src/routes/management/sites.js +522 -0
  258. package/vendor/serverd/src/routes/management/storage.js +192 -0
  259. package/vendor/serverd/src/routes/management/system.js +201 -0
  260. package/vendor/serverd/src/routes/management/tickets.js +457 -0
  261. package/vendor/serverd/src/routes/management/tunnels.js +709 -0
  262. package/vendor/serverd/src/routes/management/users.js +377 -0
  263. package/vendor/serverd/src/routes/management.js +36 -0
  264. package/vendor/serverd/src/routes/onboarding/dns.js +101 -0
  265. package/vendor/serverd/src/routes/onboarding/domain.js +42 -0
  266. package/vendor/serverd/src/routes/onboarding/index.js +18 -0
  267. package/vendor/serverd/src/routes/onboarding/provision.js +327 -0
  268. package/vendor/serverd/src/routes/onboarding/status.js +12 -0
  269. package/vendor/serverd/src/routes/plugin-router.js +79 -0
  270. package/vendor/serverd/src/routes/user-access.js +389 -0
@@ -0,0 +1,560 @@
1
+ import { z } from 'zod';
2
+ import {
3
+ readPlugins,
4
+ installPlugin,
5
+ uninstallPlugin,
6
+ enablePlugin,
7
+ disablePlugin,
8
+ getPluginCapabilities,
9
+ } from '../../lib/plugins.js';
10
+ import { setPluginCapabilities, loadAgentRegistry } from '../../lib/mtls.js';
11
+ import {
12
+ readPushInstallConfig,
13
+ readPushInstallSessions,
14
+ updatePushInstallConfigFields,
15
+ createPushInstallPolicy,
16
+ updatePushInstallPolicy,
17
+ deletePushInstallPolicy,
18
+ enableAgentPushInstall,
19
+ disableAgentPushInstall,
20
+ logPushInstallSession,
21
+ validatePushInstallAccess,
22
+ } from '../../lib/push-install.js';
23
+
24
+ // --- Zod schemas ---
25
+
26
+ const PluginNameParamSchema = z.object({
27
+ name: z
28
+ .string()
29
+ .min(1)
30
+ .max(100)
31
+ .regex(/^[a-z0-9-]+$/, 'Plugin name must contain only lowercase letters, numbers, and hyphens'),
32
+ });
33
+
34
+ const InstallBodySchema = z.object({
35
+ packageName: z
36
+ .string()
37
+ .min(1)
38
+ .max(200)
39
+ .regex(/^@lamalibre\//, 'Package must be in the @lamalibre/ scope'),
40
+ });
41
+
42
+ // --- Push install schemas ---
43
+
44
+ const IpEntrySchema = z.string().refine(
45
+ (val) => {
46
+ // Accept bare IPv4 or IPv4/CIDR
47
+ const ipv4 = /^(\d{1,3}\.){3}\d{1,3}$/;
48
+ const cidr = /^(\d{1,3}\.){3}\d{1,3}\/(\d{1,2})$/;
49
+
50
+ if (ipv4.test(val)) return true;
51
+
52
+ if (cidr.test(val)) {
53
+ const prefix = parseInt(val.split('/')[1], 10);
54
+ return prefix >= 0 && prefix <= 32;
55
+ }
56
+
57
+ return false;
58
+ },
59
+ { message: 'Must be a valid IPv4 address or CIDR (e.g. 192.168.1.0/24)' },
60
+ );
61
+
62
+ const PolicyIdSchema = z
63
+ .string()
64
+ .min(1)
65
+ .max(100)
66
+ .regex(/^[a-z0-9-]+$/, 'Policy ID must contain only lowercase letters, numbers, and hyphens');
67
+
68
+ const ScopedPackageSchema = z
69
+ .string()
70
+ .min(1)
71
+ .max(200)
72
+ .regex(/^@lamalibre\//, 'Package must be in the @lamalibre/ scope');
73
+
74
+ const CreatePolicySchema = z.object({
75
+ name: z.string().min(1).max(100),
76
+ id: PolicyIdSchema.optional(),
77
+ description: z.string().max(500).optional().default(''),
78
+ allowedIps: z.array(IpEntrySchema).optional().default([]),
79
+ deniedIps: z.array(IpEntrySchema).optional().default([]),
80
+ allowedPlugins: z.array(ScopedPackageSchema).optional().default([]),
81
+ allowedActions: z
82
+ .array(z.enum(['install', 'update', 'uninstall', 'check-prerequisites']))
83
+ .optional()
84
+ .default(['install', 'update', 'check-prerequisites']),
85
+ });
86
+
87
+ const UpdatePolicySchema = z.object({
88
+ name: z.string().min(1).max(100).optional(),
89
+ description: z.string().max(500).optional(),
90
+ allowedIps: z.array(IpEntrySchema).optional(),
91
+ deniedIps: z.array(IpEntrySchema).optional(),
92
+ allowedPlugins: z.array(ScopedPackageSchema).optional(),
93
+ allowedActions: z
94
+ .array(z.enum(['install', 'update', 'uninstall', 'check-prerequisites']))
95
+ .optional(),
96
+ });
97
+
98
+ const UpdatePushInstallConfigSchema = z.object({
99
+ enabled: z.boolean().optional(),
100
+ defaultPolicy: PolicyIdSchema.optional(),
101
+ });
102
+
103
+ const EnablePushInstallSchema = z.object({
104
+ durationMinutes: z.number().int().min(5).max(480),
105
+ policyId: PolicyIdSchema.optional(),
106
+ });
107
+
108
+ const AgentLabelParamSchema = z.object({
109
+ label: z
110
+ .string()
111
+ .min(1)
112
+ .max(50)
113
+ .regex(/^[a-z0-9-]+$/, 'Invalid agent label'),
114
+ });
115
+
116
+ const PushInstallCommandSchema = z
117
+ .object({
118
+ action: z.enum(['install', 'update', 'uninstall', 'check-prerequisites']),
119
+ packageName: z
120
+ .string()
121
+ .min(1)
122
+ .max(200)
123
+ .regex(/^@lamalibre\//, 'Package must be in the @lamalibre/ scope')
124
+ .optional(),
125
+ })
126
+ .refine(
127
+ (data) => {
128
+ if (data.action === 'install' || data.action === 'update' || data.action === 'uninstall') {
129
+ return !!data.packageName;
130
+ }
131
+ return true;
132
+ },
133
+ {
134
+ message: 'packageName is required for install, update, and uninstall actions',
135
+ path: ['packageName'],
136
+ },
137
+ );
138
+
139
+ export default async function pluginRoutes(fastify, _opts) {
140
+ // ===========================================================================
141
+ // Plugin management routes
142
+ // ===========================================================================
143
+
144
+ // ------------------------------------------------------------------
145
+ // GET /plugins — list all plugins
146
+ // ------------------------------------------------------------------
147
+ fastify.get(
148
+ '/plugins',
149
+ {
150
+ preHandler: fastify.requireRole(['admin']),
151
+ },
152
+ async (_request, _reply) => {
153
+ const registry = await readPlugins();
154
+ return { plugins: registry.plugins };
155
+ },
156
+ );
157
+
158
+ // ------------------------------------------------------------------
159
+ // GET /plugins/:name — get plugin details
160
+ // ------------------------------------------------------------------
161
+ fastify.get(
162
+ '/plugins/:name',
163
+ {
164
+ preHandler: fastify.requireRole(['admin']),
165
+ },
166
+ async (request, reply) => {
167
+ const params = PluginNameParamSchema.parse(request.params);
168
+ const registry = await readPlugins();
169
+ const plugin = registry.plugins.find((p) => p.name === params.name);
170
+
171
+ if (!plugin) {
172
+ return reply.code(404).send({ error: `Plugin "${params.name}" not found` });
173
+ }
174
+
175
+ return { plugin };
176
+ },
177
+ );
178
+
179
+ // ------------------------------------------------------------------
180
+ // POST /plugins/install — install a plugin
181
+ // ------------------------------------------------------------------
182
+ fastify.post(
183
+ '/plugins/install',
184
+ {
185
+ preHandler: fastify.requireRole(['admin']),
186
+ },
187
+ async (request, reply) => {
188
+ const body = InstallBodySchema.parse(request.body);
189
+
190
+ try {
191
+ const plugin = await installPlugin(body.packageName, request.log);
192
+ // Refresh in-memory capability list so new plugin caps are valid immediately
193
+ setPluginCapabilities(await getPluginCapabilities());
194
+ return { ok: true, plugin };
195
+ } catch (err) {
196
+ const statusCode = err.statusCode || 500;
197
+ return reply.code(statusCode).send({
198
+ error: err.message || 'Plugin installation failed',
199
+ });
200
+ }
201
+ },
202
+ );
203
+
204
+ // ------------------------------------------------------------------
205
+ // POST /plugins/:name/enable — enable a plugin
206
+ // ------------------------------------------------------------------
207
+ fastify.post(
208
+ '/plugins/:name/enable',
209
+ {
210
+ preHandler: fastify.requireRole(['admin']),
211
+ },
212
+ async (request, reply) => {
213
+ const params = PluginNameParamSchema.parse(request.params);
214
+
215
+ try {
216
+ const result = await enablePlugin(params.name, request.log);
217
+ setPluginCapabilities(await getPluginCapabilities());
218
+ return result;
219
+ } catch (err) {
220
+ const statusCode = err.statusCode || 500;
221
+ return reply.code(statusCode).send({
222
+ error: err.message || 'Failed to enable plugin',
223
+ });
224
+ }
225
+ },
226
+ );
227
+
228
+ // ------------------------------------------------------------------
229
+ // POST /plugins/:name/disable — disable a plugin
230
+ // ------------------------------------------------------------------
231
+ fastify.post(
232
+ '/plugins/:name/disable',
233
+ {
234
+ preHandler: fastify.requireRole(['admin']),
235
+ },
236
+ async (request, reply) => {
237
+ const params = PluginNameParamSchema.parse(request.params);
238
+
239
+ try {
240
+ const result = await disablePlugin(params.name, request.log);
241
+ setPluginCapabilities(await getPluginCapabilities());
242
+ return result;
243
+ } catch (err) {
244
+ const statusCode = err.statusCode || 500;
245
+ return reply.code(statusCode).send({
246
+ error: err.message || 'Failed to disable plugin',
247
+ });
248
+ }
249
+ },
250
+ );
251
+
252
+ // ------------------------------------------------------------------
253
+ // DELETE /plugins/:name — uninstall a plugin (must be disabled)
254
+ // ------------------------------------------------------------------
255
+ fastify.delete(
256
+ '/plugins/:name',
257
+ {
258
+ preHandler: fastify.requireRole(['admin']),
259
+ },
260
+ async (request, reply) => {
261
+ const params = PluginNameParamSchema.parse(request.params);
262
+
263
+ try {
264
+ const result = await uninstallPlugin(params.name, request.log);
265
+ setPluginCapabilities(await getPluginCapabilities());
266
+ return result;
267
+ } catch (err) {
268
+ const statusCode = err.statusCode || 500;
269
+ return reply.code(statusCode).send({
270
+ error: err.message || 'Plugin uninstall failed',
271
+ });
272
+ }
273
+ },
274
+ );
275
+
276
+ // ===========================================================================
277
+ // Push install routes
278
+ // ===========================================================================
279
+
280
+ // ------------------------------------------------------------------
281
+ // GET /plugins/push-install/config — get push install config
282
+ // ------------------------------------------------------------------
283
+ fastify.get(
284
+ '/plugins/push-install/config',
285
+ {
286
+ preHandler: fastify.requireRole(['admin']),
287
+ },
288
+ async (_request, _reply) => {
289
+ const config = await readPushInstallConfig();
290
+ return config;
291
+ },
292
+ );
293
+
294
+ // ------------------------------------------------------------------
295
+ // PATCH /plugins/push-install/config — update push install config
296
+ // ------------------------------------------------------------------
297
+ fastify.patch(
298
+ '/plugins/push-install/config',
299
+ {
300
+ preHandler: fastify.requireRole(['admin']),
301
+ },
302
+ async (request, reply) => {
303
+ const body = UpdatePushInstallConfigSchema.parse(request.body);
304
+
305
+ try {
306
+ const config = await updatePushInstallConfigFields(body);
307
+ return { ok: true, ...config };
308
+ } catch (err) {
309
+ const statusCode = err.statusCode || 500;
310
+ return reply.code(statusCode).send({
311
+ error: err.message || 'Failed to update push install config',
312
+ });
313
+ }
314
+ },
315
+ );
316
+
317
+ // ------------------------------------------------------------------
318
+ // GET /plugins/push-install/policies — list push install policies
319
+ // ------------------------------------------------------------------
320
+ fastify.get(
321
+ '/plugins/push-install/policies',
322
+ {
323
+ preHandler: fastify.requireRole(['admin']),
324
+ },
325
+ async (_request, _reply) => {
326
+ const config = await readPushInstallConfig();
327
+ return { policies: config.policies };
328
+ },
329
+ );
330
+
331
+ // ------------------------------------------------------------------
332
+ // POST /plugins/push-install/policies — create push install policy
333
+ // ------------------------------------------------------------------
334
+ fastify.post(
335
+ '/plugins/push-install/policies',
336
+ {
337
+ preHandler: fastify.requireRole(['admin']),
338
+ },
339
+ async (request, reply) => {
340
+ const body = CreatePolicySchema.parse(request.body);
341
+
342
+ try {
343
+ const policy = await createPushInstallPolicy(body);
344
+ return { ok: true, policy };
345
+ } catch (err) {
346
+ const statusCode = err.statusCode || 500;
347
+ return reply.code(statusCode).send({
348
+ error: err.message || 'Failed to create policy',
349
+ });
350
+ }
351
+ },
352
+ );
353
+
354
+ // ------------------------------------------------------------------
355
+ // PATCH /plugins/push-install/policies/:id — update push install policy
356
+ // ------------------------------------------------------------------
357
+ fastify.patch(
358
+ '/plugins/push-install/policies/:id',
359
+ {
360
+ preHandler: fastify.requireRole(['admin']),
361
+ },
362
+ async (request, reply) => {
363
+ const { id } = z.object({ id: PolicyIdSchema }).parse(request.params);
364
+ const body = UpdatePolicySchema.parse(request.body);
365
+
366
+ try {
367
+ const policy = await updatePushInstallPolicy(id, body);
368
+ return { ok: true, policy };
369
+ } catch (err) {
370
+ const statusCode = err.statusCode || 500;
371
+ return reply.code(statusCode).send({
372
+ error: err.message || 'Failed to update policy',
373
+ });
374
+ }
375
+ },
376
+ );
377
+
378
+ // ------------------------------------------------------------------
379
+ // DELETE /plugins/push-install/policies/:id — delete push install policy
380
+ // ------------------------------------------------------------------
381
+ fastify.delete(
382
+ '/plugins/push-install/policies/:id',
383
+ {
384
+ preHandler: fastify.requireRole(['admin']),
385
+ },
386
+ async (request, reply) => {
387
+ const { id } = z.object({ id: PolicyIdSchema }).parse(request.params);
388
+
389
+ try {
390
+ const result = await deletePushInstallPolicy(id);
391
+ return result;
392
+ } catch (err) {
393
+ const statusCode = err.statusCode || 500;
394
+ return reply.code(statusCode).send({
395
+ error: err.message || 'Failed to delete policy',
396
+ });
397
+ }
398
+ },
399
+ );
400
+
401
+ // ------------------------------------------------------------------
402
+ // POST /plugins/push-install/enable/:label — enable push install for agent
403
+ // ------------------------------------------------------------------
404
+ fastify.post(
405
+ '/plugins/push-install/enable/:label',
406
+ {
407
+ preHandler: fastify.requireRole(['admin']),
408
+ },
409
+ async (request, reply) => {
410
+ const params = AgentLabelParamSchema.parse(request.params);
411
+ const body = EnablePushInstallSchema.parse(request.body);
412
+
413
+ try {
414
+ const result = await enableAgentPushInstall(
415
+ params.label,
416
+ body.durationMinutes,
417
+ body.policyId,
418
+ );
419
+ return result;
420
+ } catch (err) {
421
+ const statusCode = err.statusCode || 500;
422
+ return reply.code(statusCode).send({
423
+ error: err.message || 'Failed to enable push install',
424
+ });
425
+ }
426
+ },
427
+ );
428
+
429
+ // ------------------------------------------------------------------
430
+ // DELETE /plugins/push-install/enable/:label — disable push install for agent
431
+ // ------------------------------------------------------------------
432
+ fastify.delete(
433
+ '/plugins/push-install/enable/:label',
434
+ {
435
+ preHandler: fastify.requireRole(['admin']),
436
+ },
437
+ async (request, reply) => {
438
+ const params = AgentLabelParamSchema.parse(request.params);
439
+
440
+ try {
441
+ const result = await disableAgentPushInstall(params.label);
442
+ return result;
443
+ } catch (err) {
444
+ const statusCode = err.statusCode || 500;
445
+ return reply.code(statusCode).send({
446
+ error: err.message || 'Failed to disable push install',
447
+ });
448
+ }
449
+ },
450
+ );
451
+
452
+ // ------------------------------------------------------------------
453
+ // POST /plugins/push-install/:label — send push install command
454
+ // ------------------------------------------------------------------
455
+ fastify.post(
456
+ '/plugins/push-install/:label',
457
+ {
458
+ preHandler: fastify.requireRole(['admin']),
459
+ },
460
+ async (request, reply) => {
461
+ const params = AgentLabelParamSchema.parse(request.params);
462
+ const body = PushInstallCommandSchema.parse(request.body);
463
+
464
+ // Validate push install access (5-gate auth)
465
+ const access = await validatePushInstallAccess(params.label, request.ip);
466
+ if (!access.ok) {
467
+ return reply.code(access.statusCode).send({ error: access.error });
468
+ }
469
+
470
+ // Check if the action is allowed by the policy
471
+ if (
472
+ access.policy.allowedActions &&
473
+ access.policy.allowedActions.length > 0 &&
474
+ !access.policy.allowedActions.includes(body.action)
475
+ ) {
476
+ return reply.code(403).send({
477
+ error: `Action "${body.action}" is not allowed by the assigned policy`,
478
+ });
479
+ }
480
+
481
+ // Check if the package is allowed by the policy
482
+ if (
483
+ body.packageName &&
484
+ access.policy.allowedPlugins &&
485
+ access.policy.allowedPlugins.length > 0 &&
486
+ !access.policy.allowedPlugins.includes(body.packageName)
487
+ ) {
488
+ return reply.code(403).send({
489
+ error: `Package "${body.packageName}" is not allowed by the assigned policy`,
490
+ });
491
+ }
492
+
493
+ // Log the session
494
+ await logPushInstallSession({
495
+ agentLabel: params.label,
496
+ action: body.action,
497
+ packageName: body.packageName || null,
498
+ sourceIp: request.ip,
499
+ policyId: access.policy.id,
500
+ status: 'sent',
501
+ });
502
+
503
+ // The actual command execution happens on the agent side.
504
+ // This endpoint signals intent; the agent polls or receives via WebSocket.
505
+ return {
506
+ ok: true,
507
+ label: params.label,
508
+ action: body.action,
509
+ packageName: body.packageName || null,
510
+ message: `Push install command "${body.action}" sent to agent "${params.label}"`,
511
+ };
512
+ },
513
+ );
514
+
515
+ // ------------------------------------------------------------------
516
+ // GET /plugins/push-install/sessions — audit log
517
+ // ------------------------------------------------------------------
518
+ fastify.get(
519
+ '/plugins/push-install/sessions',
520
+ {
521
+ preHandler: fastify.requireRole(['admin']),
522
+ },
523
+ async (_request, _reply) => {
524
+ const sessions = await readPushInstallSessions();
525
+ return { sessions };
526
+ },
527
+ );
528
+
529
+ // ------------------------------------------------------------------
530
+ // GET /plugins/push-install/agent-status — agent self-check
531
+ // ------------------------------------------------------------------
532
+ fastify.get(
533
+ '/plugins/push-install/agent-status',
534
+ {
535
+ preHandler: fastify.requireRole(['admin', 'agent']),
536
+ },
537
+ async (request, _reply) => {
538
+ const label = request.certLabel;
539
+ if (!label) {
540
+ return { pushInstallEnabled: false };
541
+ }
542
+
543
+ const registry = await loadAgentRegistry();
544
+ const agent = registry.agents.find((a) => a.label === label && !a.revoked);
545
+
546
+ if (!agent) {
547
+ return { pushInstallEnabled: false };
548
+ }
549
+
550
+ const enabled =
551
+ agent.pushInstallEnabledUntil && new Date(agent.pushInstallEnabledUntil) > new Date();
552
+
553
+ return {
554
+ pushInstallEnabled: !!enabled,
555
+ pushInstallEnabledUntil: agent.pushInstallEnabledUntil || null,
556
+ pushInstallPolicy: agent.pushInstallPolicy || null,
557
+ };
558
+ },
559
+ );
560
+ }
@@ -0,0 +1,63 @@
1
+ import { z } from 'zod';
2
+ import {
3
+ ALLOWED_SERVICES,
4
+ getAllServiceStatuses,
5
+ isAllowedService,
6
+ isAllowedAction,
7
+ executeServiceAction,
8
+ } from '../../lib/services.js';
9
+
10
+ const ServiceActionParamsSchema = z.object({
11
+ name: z.string().refine((v) => ALLOWED_SERVICES.includes(v), {
12
+ message: 'Unknown service',
13
+ }),
14
+ action: z.enum(['start', 'stop', 'restart', 'reload']),
15
+ });
16
+
17
+ export default async function servicesRoutes(fastify, _opts) {
18
+ fastify.get(
19
+ '/services',
20
+ {
21
+ preHandler: fastify.requireRole(['admin', 'agent'], { capability: 'services:read' }),
22
+ },
23
+ async (_request, _reply) => {
24
+ const services = await getAllServiceStatuses();
25
+ return { services };
26
+ },
27
+ );
28
+
29
+ fastify.post(
30
+ '/services/:name/:action',
31
+ {
32
+ preHandler: fastify.requireRole(['admin', 'agent'], { capability: 'services:write' }),
33
+ },
34
+ async (request, reply) => {
35
+ const { name, action } = request.params;
36
+
37
+ // Validate service name
38
+ if (!isAllowedService(name)) {
39
+ return reply.code(400).send({ error: 'Unknown service' });
40
+ }
41
+
42
+ // Validate action
43
+ if (!isAllowedAction(action)) {
44
+ return reply.code(400).send({ error: 'Invalid action' });
45
+ }
46
+
47
+ // Parse for Zod validation (will throw ZodError caught by error handler)
48
+ ServiceActionParamsSchema.parse({ name, action });
49
+
50
+ try {
51
+ const result = await executeServiceAction(name, action);
52
+ return result;
53
+ } catch (err) {
54
+ const statusCode = err.statusCode || 500;
55
+ const body = { error: err.message };
56
+ if (err.details) {
57
+ body.details = err.details;
58
+ }
59
+ return reply.code(statusCode).send(body);
60
+ }
61
+ },
62
+ );
63
+ }