@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,131 @@
1
+ import chalk from 'chalk';
2
+
3
+ // eslint-disable-next-line no-control-regex
4
+ const ansiPattern = new RegExp('\x1b\\[[0-9;]*m', 'g');
5
+ const stripAnsi = (str) => str.replace(ansiPattern, '');
6
+
7
+ /**
8
+ * Print an array of lines inside a bordered box with a title separator
9
+ * after the first 3 lines.
10
+ * @param {string[]} lines
11
+ */
12
+ function printBox(lines) {
13
+ const maxLineWidth = Math.max(...lines.map((l) => stripAnsi(l).length));
14
+ const boxInnerWidth = Math.max(maxLineWidth + 2, 62);
15
+
16
+ const border = chalk.cyan;
17
+ const topBorder = border(`╔${'═'.repeat(boxInnerWidth)}╗`);
18
+ const bottomBorder = border(`╚${'═'.repeat(boxInnerWidth)}╝`);
19
+ const midBorder = border(`╠${'═'.repeat(boxInnerWidth)}╣`);
20
+
21
+ const padLine = (line) => {
22
+ const visibleLength = stripAnsi(line).length;
23
+ const padding = boxInnerWidth - visibleLength;
24
+ return `${border('║')}${line}${' '.repeat(Math.max(0, padding))}${border('║')}`;
25
+ };
26
+
27
+ console.log('');
28
+ console.log(topBorder);
29
+
30
+ // Title section (first 3 lines: empty, title, empty)
31
+ for (let i = 0; i < 3; i++) {
32
+ console.log(padLine(lines[i]));
33
+ }
34
+
35
+ // Separator
36
+ console.log(midBorder);
37
+
38
+ // Remaining content lines
39
+ for (let i = 3; i < lines.length; i++) {
40
+ console.log(padLine(lines[i]));
41
+ }
42
+
43
+ console.log(bottomBorder);
44
+ console.log('');
45
+ }
46
+
47
+ /**
48
+ * Print the post-install summary box with all the information the user needs
49
+ * to download their client certificate, import it, and access the panel.
50
+ *
51
+ * In redeploy mode, prints a shorter summary since the user already has
52
+ * their certificate and knows how to access the panel.
53
+ *
54
+ * @param {{ ip: string, pkiDir: string, installedVersion?: string, vendorVersion?: string }} ctx - Shared installer context.
55
+ */
56
+ export async function printSummary(ctx) {
57
+ const ip = ctx.ip;
58
+ const panelUrl = `https://${ip}:9292`;
59
+
60
+ // Redeploy mode: short summary
61
+ if (ctx.installedVersion !== undefined) {
62
+ const lines = [
63
+ '',
64
+ ` ${chalk.green.bold('Panel updated successfully!')}`,
65
+ '',
66
+ ` ${chalk.white.bold('Version:')} ${chalk.dim(ctx.installedVersion || 'unknown')} ${chalk.white('→')} ${chalk.cyan(ctx.vendorVersion || 'unknown')}`,
67
+ '',
68
+ ` ${chalk.white.bold('Panel:')} ${chalk.cyan.underline(panelUrl)}`,
69
+ '',
70
+ ` ${chalk.dim('Your certificates, nginx config, and OS settings are unchanged.')}`,
71
+ ` ${chalk.dim('Refresh your browser to see the updated panel.')}`,
72
+ '',
73
+ ];
74
+
75
+ printBox(lines);
76
+ return;
77
+ }
78
+
79
+ // Full install mode: complete summary with certificate instructions
80
+ const scpCmd = `scp root@${ip}:${ctx.pkiDir}/client.p12 .`;
81
+
82
+ const lines = [
83
+ '',
84
+ ` ${chalk.green.bold('Lamaste installed successfully!')}`,
85
+ '',
86
+ ` ${chalk.white.bold('1.')} ${chalk.white('Download your client certificate:')}`,
87
+ '',
88
+ ` ${chalk.cyan(scpCmd)}`,
89
+ '',
90
+ ` ${chalk.white.bold('2.')} ${chalk.white('Import client.p12 into your browser:')}`,
91
+ '',
92
+ ` ${chalk.white.bold('macOS:')} ${chalk.white('Double-click the file → Keychain Access')}`,
93
+ ` ${chalk.white('→ select "System" keychain (not "login")')}`,
94
+ ` ${chalk.white('→ enter the password below when prompted')}`,
95
+ ` ${chalk.white('→ find cert in System keychain → double-click')}`,
96
+ ` ${chalk.white('→ Trust → Always Trust')}`,
97
+ ` ${chalk.dim('(ignore any "Local Items" import error — System is what matters)')}`,
98
+ '',
99
+ ` ${chalk.white.bold('Linux:')} ${chalk.white('Chrome → Settings → Privacy & Security')}`,
100
+ ` ${chalk.white('→ Security → Manage certificates → Import')}`,
101
+ '',
102
+ ` ${chalk.white.bold('Windows:')} ${chalk.white('Double-click the file → Certificate Import')}`,
103
+ ` ${chalk.white('Wizard → enter the password below')}`,
104
+ '',
105
+ ` ${chalk.white.bold('3.')} ${chalk.white('Certificate password:')}`,
106
+ '',
107
+ ` ${chalk.yellow.bold(`cat ${ctx.pkiDir}/.p12-password`)}`,
108
+ '',
109
+ ` ${chalk.white.bold('4.')} ${chalk.white('Open the Lamaste panel:')}`,
110
+ '',
111
+ ` ${chalk.cyan.underline(panelUrl)}`,
112
+ '',
113
+ ` ${chalk.white('(Your browser will warn about the self-signed cert.')}`,
114
+ ` ${chalk.white('This is expected — click "Advanced" → "Proceed")')}`,
115
+ '',
116
+ ` ${chalk.white.bold('5.')} ${chalk.white('Bind admin cert to macOS Keychain')} ${chalk.yellow('(recommended)')}`,
117
+ ` ${chalk.dim('Makes the private key non-extractable — the P12 file')}`,
118
+ ` ${chalk.dim('and password can no longer grant panel access.')}`,
119
+ '',
120
+ ` ${chalk.cyan('npx @lamalibre/create-lamaste-admin')}`,
121
+ '',
122
+ ` ${chalk.dim('Run on your Mac after importing the P12 above.')}`,
123
+ ` ${chalk.dim('One-way operation — recovery requires server console.')}`,
124
+ '',
125
+ ` ${chalk.green('You can now disconnect from SSH.')}`,
126
+ ` ${chalk.green('The panel is running and accessible from your browser.')}`,
127
+ '',
128
+ ];
129
+
130
+ printBox(lines);
131
+ }
@@ -0,0 +1,63 @@
1
+ import { execa } from 'execa';
2
+ import { existsSync } from 'node:fs';
3
+ import { chmod, rename, rm, writeFile } from 'node:fs/promises';
4
+ import { join } from 'node:path';
5
+ import { RETIRED_SUDOERS_WRAPPERS, SUDOERS_WRAPPERS } from './service-config.js';
6
+
7
+ /**
8
+ * Install the root-owned programs the sudoers rules name, from this
9
+ * package's scripts/ directory, and remove the ones earlier versions
10
+ * installed that no rule references any more. Each script is syntax-checked
11
+ * first: an error would only surface when the panel needs it.
12
+ *
13
+ * Run before the sudoers file is written.
14
+ *
15
+ * @param {string} scriptsDir
16
+ * @param {(line: string) => void} report
17
+ */
18
+ export async function installSudoersWrappers(scriptsDir, report) {
19
+ for (const w of SUDOERS_WRAPPERS) {
20
+ const src = join(scriptsDir, w.name);
21
+ if (!existsSync(src)) {
22
+ throw new Error(
23
+ `Sudoers wrapper script not found in package: ${src}. The provisioner package is incomplete.`,
24
+ );
25
+ }
26
+ report(`Validating ${w.name}...`);
27
+ if (w.interpreter === 'node') {
28
+ await execa('node', ['--check', src]);
29
+ } else {
30
+ await execa('bash', ['-n', src]);
31
+ }
32
+ report(`Installing ${w.name} to ${w.dest}...`);
33
+ await execa('install', ['-o', 'root', '-g', 'root', '-m', '0755', src, w.dest]);
34
+ }
35
+ for (const path of RETIRED_SUDOERS_WRAPPERS) {
36
+ await rm(path, { force: true });
37
+ }
38
+ }
39
+
40
+ const SUDOERS_PATH = '/etc/sudoers.d/lamaste';
41
+
42
+ /**
43
+ * Install the sudoers file atomically: written under a name sudo ignores
44
+ * (it skips files in sudoers.d whose name contains a dot), checked with
45
+ * `visudo -c`, then renamed into place. An invalid file is never live, so a
46
+ * failure cannot lock sudo out.
47
+ *
48
+ * @param {string} content
49
+ */
50
+ export async function installSudoersFile(content) {
51
+ const staged = `${SUDOERS_PATH}.lamaste-new`;
52
+ await writeFile(staged, content, { mode: 0o440 });
53
+ try {
54
+ await chmod(staged, 0o440);
55
+ await execa('visudo', ['-c', '-q', '-f', staged]);
56
+ } catch (error) {
57
+ await rm(staged, { force: true });
58
+ throw new Error(
59
+ `Sudoers validation failed — the installed rules are unchanged.\n${error.stderr || error.message}`,
60
+ );
61
+ }
62
+ await rename(staged, SUDOERS_PATH);
63
+ }
@@ -0,0 +1,233 @@
1
+ import crypto from 'node:crypto';
2
+ import { execa } from 'execa';
3
+ import { writeFile, mkdir, cp, rm, lstat } from 'node:fs/promises';
4
+ import { existsSync } from 'node:fs';
5
+ import { setTimeout as sleep } from 'node:timers/promises';
6
+ import { fileURLToPath } from 'node:url';
7
+ import { dirname, join } from 'node:path';
8
+ import { generateGatekeeperServiceUnit } from '../lib/service-config.js';
9
+ import { lookupId } from '../lib/accounts.js';
10
+ import { writeFileNoFollow } from '../lib/ownership.js';
11
+
12
+ /**
13
+ * Deploy the gatekeeper package from vendor/ into <installDir>/gatekeeper,
14
+ * root-owned (the service runs it as lamaste, which may read but not change
15
+ * it). Shared by the full install and the redeploy.
16
+ *
17
+ * @param {string} vendorDir
18
+ * @param {string} installDir
19
+ * @returns {Promise<boolean>} false when the package is not bundled
20
+ */
21
+ export async function deployGatekeeperPackage(vendorDir, installDir) {
22
+ const gatekeeperSrc = join(vendorDir, 'gatekeeper');
23
+ const gatekeeperDest = `${installDir}/gatekeeper`;
24
+ if (!existsSync(gatekeeperSrc)) return false;
25
+
26
+ await mkdir(gatekeeperDest, { recursive: true });
27
+ await cp(join(gatekeeperSrc, 'package.json'), join(gatekeeperDest, 'package.json'));
28
+ await rm(join(gatekeeperDest, 'dist'), { recursive: true, force: true });
29
+ await cp(join(gatekeeperSrc, 'dist'), join(gatekeeperDest, 'dist'), { recursive: true });
30
+ await execa('npm', ['install', '--production', '--ignore-scripts'], { cwd: gatekeeperDest });
31
+ return true;
32
+ }
33
+
34
+ async function exists(path) {
35
+ try {
36
+ await lstat(path);
37
+ return true;
38
+ } catch (err) {
39
+ if (err.code === 'ENOENT') return false;
40
+ throw err;
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Gatekeeper deployment subtasks: deploy package, create state files,
46
+ * write systemd service, enable and start.
47
+ *
48
+ * @param {object} ctx Shared installer context.
49
+ * @param {object} task Parent Listr2 task reference.
50
+ * @returns {import('listr2').ListrTask[]}
51
+ */
52
+ export function gatekeeperTasks(ctx, task) {
53
+ const installDir = ctx.installDir;
54
+ const configDir = ctx.configDir;
55
+
56
+ const thisFile = fileURLToPath(import.meta.url);
57
+ const thisDir = dirname(thisFile);
58
+ const packageRoot = join(thisDir, '..', '..');
59
+ const vendorDir = join(packageRoot, 'vendor');
60
+
61
+ return task.newListr([
62
+ {
63
+ title: 'Deploying gatekeeper package',
64
+ task: async (_ctx, subtask) => {
65
+ subtask.output = 'Copying gatekeeper files and installing dependencies...';
66
+ if (!(await deployGatekeeperPackage(vendorDir, installDir))) {
67
+ subtask.output = 'Gatekeeper package not found in vendor, skipping';
68
+ return;
69
+ }
70
+ subtask.output = 'Gatekeeper package deployed';
71
+ },
72
+ rendererOptions: { persistentOutput: true },
73
+ },
74
+ {
75
+ title: 'Creating gatekeeper state files',
76
+ task: async (_ctx, subtask) => {
77
+ const groupsPath = join(configDir, 'groups.json');
78
+ const grantsPath = join(configDir, 'access-grants.json');
79
+ const settingsPath = join(configDir, 'gatekeeper.json');
80
+
81
+ // Create empty state files if they don't exist. The config
82
+ // directory belongs to lamaste: never write through what is there.
83
+ const owner = {
84
+ uid: await lookupId('user', 'lamaste'),
85
+ gid: await lookupId('group', 'lamaste'),
86
+ mode: 0o600,
87
+ };
88
+ if (!(await exists(groupsPath))) {
89
+ await writeFileNoFollow(groupsPath, '{"groups":[]}\n', owner);
90
+ subtask.output = 'Created groups.json';
91
+ }
92
+ if (!(await exists(grantsPath))) {
93
+ await writeFileNoFollow(grantsPath, '{"grants":[]}\n', owner);
94
+ subtask.output = 'Created access-grants.json';
95
+ }
96
+ if (!(await exists(settingsPath))) {
97
+ await writeFileNoFollow(settingsPath, '{}\n', owner);
98
+ subtask.output = 'Created gatekeeper.json';
99
+ }
100
+ subtask.output = 'State files ready';
101
+ },
102
+ rendererOptions: { persistentOutput: true },
103
+ },
104
+ {
105
+ title: 'Migrating legacy grants',
106
+ skip: () => {
107
+ const legacyPath = join(configDir, 'user-plugin-access.json');
108
+ return !existsSync(legacyPath) ? 'No legacy grants file found' : false;
109
+ },
110
+ task: async (_ctx, subtask) => {
111
+ // Migration is handled by the gatekeeper library on first startup.
112
+ // We just ensure the gatekeeper service will pick it up.
113
+ subtask.output = 'Legacy grants will be migrated on first gatekeeper startup';
114
+ },
115
+ rendererOptions: { persistentOutput: true },
116
+ },
117
+ {
118
+ title: 'Writing nginx proxy_cache config',
119
+ skip: () => {
120
+ const gatekeeperDest = `${installDir}/gatekeeper`;
121
+ return !existsSync(join(gatekeeperDest, 'dist'))
122
+ ? 'Gatekeeper package not deployed'
123
+ : false;
124
+ },
125
+ task: async (_ctx, subtask) => {
126
+ // Two-snippet design:
127
+ // lamalibre-lamaste-authz-cache.conf — http{} scope, defines the zone.
128
+ // lamalibre-lamaste-authz-cache-loc.conf — location{} scope, applies TTLs.
129
+ // Denials (403) are NOT cached so grant additions take effect
130
+ // immediately. Successful auth (200) is cached for 10s — matching
131
+ // the Cache-Control max-age Gatekeeper sets on the response. Omitting
132
+ // `proxy_ignore_headers Cache-Control` means nginx will honor the
133
+ // upstream max-age as a further shortening signal.
134
+ const cacheConfig = `# Lamaste Gatekeeper auth response cache zone
135
+ # Scope: http{}. Included from nginx.conf.
136
+ proxy_cache_path /var/cache/nginx/authz levels=1:2
137
+ keys_zone=lamaste_authz:1m
138
+ max_size=10m
139
+ inactive=5m;
140
+ `;
141
+ const cacheLocConfig = `# Lamaste Gatekeeper auth_request cache policy
142
+ # Scope: location{} using auth_request /authz/check.
143
+ proxy_cache lamaste_authz;
144
+ proxy_cache_key "$cookie_authelia_session$http_host";
145
+ proxy_cache_valid 200 10s;
146
+ proxy_cache_valid 403 0;
147
+ proxy_cache_use_stale off;
148
+ `;
149
+ const snippetPath = '/etc/nginx/snippets/lamalibre-lamaste-authz-cache.conf';
150
+ const locSnippetPath = '/etc/nginx/snippets/lamalibre-lamaste-authz-cache-loc.conf';
151
+
152
+ // Create snippets directory if needed
153
+ await execa('sudo', ['mkdir', '-p', '/etc/nginx/snippets']);
154
+
155
+ const tmpFile = `/tmp/nginx-authz-cache-${crypto.randomBytes(4).toString('hex')}`;
156
+ await writeFile(tmpFile, cacheConfig, 'utf-8');
157
+ await execa('sudo', ['mv', tmpFile, snippetPath]);
158
+ await execa('sudo', ['chmod', '644', snippetPath]);
159
+
160
+ const tmpLoc = `/tmp/nginx-authz-cache-loc-${crypto.randomBytes(4).toString('hex')}`;
161
+ await writeFile(tmpLoc, cacheLocConfig, 'utf-8');
162
+ await execa('sudo', ['mv', tmpLoc, locSnippetPath]);
163
+ await execa('sudo', ['chmod', '644', locSnippetPath]);
164
+
165
+ subtask.output = 'nginx proxy_cache config written';
166
+ },
167
+ rendererOptions: { persistentOutput: true },
168
+ },
169
+ {
170
+ title: 'Writing gatekeeper systemd service',
171
+ skip: () => {
172
+ const gatekeeperDest = `${installDir}/gatekeeper`;
173
+ return !existsSync(join(gatekeeperDest, 'dist'))
174
+ ? 'Gatekeeper package not deployed'
175
+ : false;
176
+ },
177
+ task: async (_ctx, subtask) => {
178
+ const serviceUnit = generateGatekeeperServiceUnit({ installDir, configDir });
179
+ const servicePath = '/etc/systemd/system/lamalibre-lamaste-gatekeeper.service';
180
+ await writeFile(servicePath, serviceUnit);
181
+ await execa('chmod', ['644', servicePath]);
182
+ subtask.output = 'Systemd service unit written';
183
+ },
184
+ rendererOptions: { persistentOutput: true },
185
+ },
186
+ {
187
+ title: 'Starting gatekeeper service',
188
+ skip: () => {
189
+ const gatekeeperDest = `${installDir}/gatekeeper`;
190
+ return !existsSync(join(gatekeeperDest, 'dist'))
191
+ ? 'Gatekeeper package not deployed'
192
+ : false;
193
+ },
194
+ task: async (_ctx, subtask) => {
195
+ await execa('systemctl', ['daemon-reload']);
196
+ await execa('systemctl', ['enable', 'lamalibre-lamaste-gatekeeper']);
197
+ await execa('systemctl', ['start', 'lamalibre-lamaste-gatekeeper']);
198
+
199
+ // Wait for startup
200
+ await sleep(2000);
201
+
202
+ const { stdout: status } = await execa('systemctl', [
203
+ 'is-active',
204
+ 'lamalibre-lamaste-gatekeeper',
205
+ ]);
206
+ if (status.trim() !== 'active') {
207
+ const { stdout: logs } = await execa('journalctl', [
208
+ '-u',
209
+ 'lamalibre-lamaste-gatekeeper',
210
+ '-n',
211
+ '20',
212
+ '--no-pager',
213
+ ]);
214
+ throw new Error(`Gatekeeper service failed to start: ${status}\n${logs}`);
215
+ }
216
+
217
+ // Health check
218
+ try {
219
+ const { stdout } = await execa('curl', [
220
+ '-s',
221
+ '--max-time',
222
+ '5',
223
+ 'http://127.0.0.1:9294/health',
224
+ ]);
225
+ subtask.output = `Gatekeeper service running. Health: ${stdout}`;
226
+ } catch (err) {
227
+ subtask.output = `Gatekeeper service active but health check pending (${err.message})`;
228
+ }
229
+ },
230
+ rendererOptions: { persistentOutput: true },
231
+ },
232
+ ]);
233
+ }