@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,408 @@
1
+ import { execa } from 'execa';
2
+ import { writeFile, readFile, mkdir, mkdtemp, cp, rm } from 'node:fs/promises';
3
+ import { existsSync } from 'node:fs';
4
+ import { setTimeout as sleep } from 'node:timers/promises';
5
+ import { tmpdir } from 'node:os';
6
+ import { fileURLToPath } from 'node:url';
7
+ import { dirname, join } from 'node:path';
8
+ import { generateServiceUnit, generateSudoersContent } from '../lib/service-config.js';
9
+ import { lookupId } from '../lib/accounts.js';
10
+ import { ensureRootOwnedInstallDir, removeTree, writeFileNoFollow } from '../lib/ownership.js';
11
+ import { generateCertHelpPage } from '../lib/cert-help-page.js';
12
+ import { provisionRelayServices } from '../lib/relay-services.js';
13
+ import { installSudoersFile, installSudoersWrappers } from '../lib/wrappers.js';
14
+
15
+ /**
16
+ * Panel deployment subtasks: system user, directories, server + client deploy,
17
+ * config, systemd service, sudoers, and service start.
18
+ *
19
+ * @param {object} ctx Shared installer context.
20
+ * @param {object} task Parent Listr2 task reference.
21
+ * @returns {import('listr2').ListrTask[]}
22
+ */
23
+ export function panelTasks(ctx, task) {
24
+ const installDir = ctx.installDir;
25
+ const configDir = ctx.configDir;
26
+
27
+ // Resolve vendor directory relative to this package root.
28
+ // This file is at packages/create-lamaste/src/tasks/panel.js
29
+ // The package root is 2 levels up; vendor/ is bundled at publish time.
30
+ const thisFile = fileURLToPath(import.meta.url);
31
+ const thisDir = dirname(thisFile);
32
+ const packageRoot = join(thisDir, '..', '..');
33
+ const vendorDir = join(packageRoot, 'vendor');
34
+
35
+ return task.newListr([
36
+ {
37
+ title: 'Creating system user',
38
+ task: async (_ctx, subtask) => {
39
+ try {
40
+ await execa('id', ['lamaste']);
41
+ subtask.output = 'User lamaste already exists';
42
+ } catch {
43
+ await execa('useradd', [
44
+ '--system',
45
+ '--no-create-home',
46
+ '--shell',
47
+ '/usr/sbin/nologin',
48
+ 'lamaste',
49
+ ]);
50
+ subtask.output = 'Created system user: lamaste';
51
+ }
52
+ },
53
+ rendererOptions: { persistentOutput: true },
54
+ },
55
+ {
56
+ title: 'Creating directory structure',
57
+ task: async (_ctx, subtask) => {
58
+ // Code (the panel, the gatekeeper, and the lamaste-server CLI that
59
+ // administrators run as root) is root-owned: the lamaste user must
60
+ // not be able to change what root later executes. State is lamaste's.
61
+ const { previous } = await ensureRootOwnedInstallDir(installDir);
62
+ // A lamaste-owned tree from an earlier version was moved aside;
63
+ // every component is deployed fresh below, so it can go now.
64
+ if (previous) await removeTree(previous);
65
+ await mkdir(`${installDir}/serverd`, { recursive: true });
66
+ await mkdir(`${installDir}/server-ui`, { recursive: true });
67
+ await mkdir(configDir, { recursive: true });
68
+
69
+ // Served by the IP vhost to browsers without a client certificate.
70
+ const helpDir = `${installDir}/lamaste-server-ui`;
71
+ await mkdir(helpDir, { recursive: true });
72
+ await writeFile(`${helpDir}/cert-help.html`, generateCertHelpPage(ctx));
73
+
74
+ await execa('chown', ['-R', 'lamaste:lamaste', configDir]);
75
+
76
+ subtask.output = `Directories created: ${installDir}, ${configDir}`;
77
+ },
78
+ rendererOptions: { persistentOutput: true },
79
+ },
80
+ {
81
+ title: 'Installing chisel and Authelia',
82
+ task: async (_ctx, subtask) => {
83
+ // Pinned, digest-verified binaries, their units, service accounts,
84
+ // and the web root / Authelia directories the panel writes without
85
+ // privileges. Onboarding enables and starts the services.
86
+ await provisionRelayServices(ctx, (line) => {
87
+ subtask.output = line;
88
+ });
89
+ subtask.output = 'chisel and Authelia installed (started during onboarding)';
90
+ },
91
+ rendererOptions: { persistentOutput: true },
92
+ },
93
+ {
94
+ title: 'Deploying serverd',
95
+ task: async (_ctx, subtask) => {
96
+ const serverSrc = join(vendorDir, 'serverd');
97
+ const serverDest = `${installDir}/serverd`;
98
+
99
+ if (!existsSync(serverSrc)) {
100
+ throw new Error(
101
+ `Panel server source not found at ${serverSrc}. Ensure the monorepo is intact.`,
102
+ );
103
+ }
104
+
105
+ subtask.output = 'Copying serverd files...';
106
+ // Copy package.json first. For src/ we nuke the existing directory
107
+ // before copying — redeploys over an existing install need to drop
108
+ // stale files that are no longer in the source tree. `fs.cp` with
109
+ // recursive merges but does not delete orphans, and its overwrite
110
+ // behaviour is not reliable for every file shape.
111
+ await cp(join(serverSrc, 'package.json'), join(serverDest, 'package.json'), {
112
+ force: true,
113
+ });
114
+ await rm(join(serverDest, 'src'), { recursive: true, force: true });
115
+ await cp(join(serverSrc, 'src'), join(serverDest, 'src'), {
116
+ recursive: true,
117
+ force: true,
118
+ });
119
+
120
+ subtask.output = 'Installing production dependencies...';
121
+ try {
122
+ await execa('npm', ['install', '--production', '--ignore-scripts'], {
123
+ cwd: serverDest,
124
+ });
125
+ } catch (err) {
126
+ throw new Error(
127
+ `Failed to install serverd dependencies. Check your network connection and try again.\n${err.stderr || err.message}`,
128
+ );
129
+ }
130
+
131
+ subtask.output = 'Panel server deployed';
132
+ },
133
+ rendererOptions: { persistentOutput: true },
134
+ },
135
+ {
136
+ title: 'Deploying lamaste-server CLI',
137
+ task: async (_ctx, subtask) => {
138
+ // The 2.0 refactor split operational commands out of the daemon
139
+ // into a separate CLI package. This task deploys the CLI alongside
140
+ // the daemon and publishes two binaries:
141
+ // • /usr/local/bin/lamaste-server (full subcommand set)
142
+ // • /usr/local/bin/lamaste-reset-admin (shim for reset-admin
143
+ // — the emergency recovery path tests and operators depend on)
144
+ const cliSrc = join(vendorDir, 'server');
145
+ const cliDest = `${installDir}/server`;
146
+
147
+ if (!existsSync(cliSrc)) {
148
+ throw new Error(
149
+ `lamaste-server CLI source not found at ${cliSrc}. The installer is missing bundled vendor files — rebuild create-lamaste.`,
150
+ );
151
+ }
152
+
153
+ subtask.output = 'Copying lamaste-server CLI files...';
154
+ await mkdir(cliDest, { recursive: true });
155
+ await cp(join(cliSrc, 'package.json'), join(cliDest, 'package.json'), { force: true });
156
+ await rm(join(cliDest, 'bin'), { recursive: true, force: true });
157
+ await rm(join(cliDest, 'src'), { recursive: true, force: true });
158
+ await cp(join(cliSrc, 'bin'), join(cliDest, 'bin'), { recursive: true, force: true });
159
+ await cp(join(cliSrc, 'src'), join(cliDest, 'src'), { recursive: true, force: true });
160
+
161
+ subtask.output = 'Installing lamaste-server CLI dependencies...';
162
+ try {
163
+ await execa('npm', ['install', '--production', '--ignore-scripts'], {
164
+ cwd: cliDest,
165
+ });
166
+ } catch (err) {
167
+ throw new Error(
168
+ `Failed to install lamaste-server CLI dependencies.\n${err.stderr || err.message}`,
169
+ );
170
+ }
171
+
172
+ const serverBin = join(cliDest, 'bin', 'lamaste-server.js');
173
+ await execa('chmod', ['+x', serverBin]);
174
+ await execa('ln', ['-sf', serverBin, '/usr/local/bin/lamaste-server']);
175
+
176
+ // lamaste-reset-admin shim — a one-line wrapper that dispatches
177
+ // to `lamaste-server reset-admin`. Kept as its own binary because
178
+ // the recovery docs and existing tests refer to it by name; the
179
+ // shim means the name can never drift from the CLI implementation.
180
+ const shimPath = '/usr/local/bin/lamaste-reset-admin';
181
+ const shim = [
182
+ '#!/usr/bin/env bash',
183
+ '# Shim installed by create-lamaste: delegates to lamaste-server CLI.',
184
+ '# The authoritative reset-admin implementation lives in',
185
+ '# @lamalibre/lamaste-server; this wrapper preserves the legacy',
186
+ '# binary name used by operator docs and e2e tests.',
187
+ 'exec /usr/local/bin/lamaste-server reset-admin "$@"',
188
+ '',
189
+ ].join('\n');
190
+ await writeFile(shimPath, shim, { mode: 0o755 });
191
+
192
+ subtask.output = 'lamaste-server CLI deployed; lamaste-reset-admin shim installed';
193
+ },
194
+ rendererOptions: { persistentOutput: true },
195
+ },
196
+ {
197
+ title: 'Deploying server-ui',
198
+ task: async (_ctx, subtask) => {
199
+ const clientSrc = join(vendorDir, 'server-ui');
200
+ const clientDest = `${installDir}/server-ui`;
201
+
202
+ if (!existsSync(clientSrc)) {
203
+ throw new Error(
204
+ `Panel client source not found at ${clientSrc}. Ensure the monorepo is intact.`,
205
+ );
206
+ }
207
+
208
+ // If a pre-built dist/ exists, use it directly (faster, avoids OOM on low-RAM VMs)
209
+ const prebuiltDist = join(clientSrc, 'dist');
210
+ if (existsSync(join(prebuiltDist, 'index.html'))) {
211
+ subtask.output = 'Using pre-built lamaste-server-ui dist...';
212
+ const distDest = join(clientDest, 'dist');
213
+ await rm(distDest, { recursive: true, force: true });
214
+ await cp(prebuiltDist, distDest, { recursive: true });
215
+
216
+ subtask.output = 'Panel client deployed from pre-built dist';
217
+ return;
218
+ }
219
+
220
+ // No pre-built dist — build from source in a temp directory
221
+ const buildDir = await mkdtemp(join(tmpdir(), 'lamalibre-lamaste-server-ui-build-'));
222
+
223
+ subtask.output = 'Copying lamaste-server-ui source for build...';
224
+ for (const entry of [
225
+ 'package.json',
226
+ 'src',
227
+ 'index.html',
228
+ 'vite.config.js',
229
+ 'tailwind.config.js',
230
+ 'postcss.config.js',
231
+ ]) {
232
+ const srcPath = join(clientSrc, entry);
233
+ if (existsSync(srcPath)) {
234
+ await cp(srcPath, join(buildDir, entry), { recursive: true });
235
+ }
236
+ }
237
+
238
+ subtask.output = 'Installing dependencies for build...';
239
+ try {
240
+ await execa('npm', ['install', '--ignore-scripts'], { cwd: buildDir });
241
+ } catch (err) {
242
+ throw new Error(
243
+ `Failed to install lamaste-server-ui build dependencies. Check your network connection and try again.\n${err.stderr || err.message}`,
244
+ );
245
+ }
246
+
247
+ subtask.output = 'Building lamaste-server-ui (vite build)...';
248
+ await execa('npx', ['vite', 'build'], { cwd: buildDir });
249
+
250
+ subtask.output = 'Copying built assets...';
251
+ const distSrc = join(buildDir, 'dist');
252
+ const distDest = join(clientDest, 'dist');
253
+ await rm(distDest, { recursive: true, force: true });
254
+ await cp(distSrc, distDest, { recursive: true });
255
+
256
+ await rm(buildDir, { recursive: true, force: true });
257
+
258
+ subtask.output = 'Panel client built and deployed';
259
+ },
260
+ rendererOptions: { persistentOutput: true },
261
+ },
262
+ {
263
+ title: 'Deploying documentation',
264
+ task: async (_ctx, subtask) => {
265
+ const docsSrc = join(vendorDir, 'docs');
266
+ const docsDest = `${installDir}/docs`;
267
+
268
+ if (existsSync(docsSrc)) {
269
+ subtask.output = 'Copying version-stamped docs...';
270
+ await rm(docsDest, { recursive: true, force: true });
271
+ await cp(docsSrc, docsDest, { recursive: true });
272
+ subtask.output = 'Documentation deployed';
273
+ } else {
274
+ subtask.output = 'No bundled docs found — skipping';
275
+ }
276
+ },
277
+ rendererOptions: { persistentOutput: true },
278
+ },
279
+ {
280
+ title: 'Writing panel configuration',
281
+ task: async (_ctx, subtask) => {
282
+ const configPath = `${configDir}/panel.json`;
283
+ let config;
284
+
285
+ if (existsSync(configPath)) {
286
+ // Preserve user/onboarding state from an existing configuration.
287
+ // Only update installer-owned fields (ip, dataDir, staticDir).
288
+ subtask.output = 'Existing panel.json found — merging...';
289
+ const existing = JSON.parse(await readFile(configPath, 'utf8'));
290
+
291
+ config = {
292
+ ...existing,
293
+ ip: ctx.ip,
294
+ dataDir: configDir,
295
+ staticDir: `${installDir}/server-ui/dist`,
296
+ };
297
+ } else {
298
+ config = {
299
+ ip: ctx.ip,
300
+ domain: null,
301
+ email: null,
302
+ dataDir: configDir,
303
+ staticDir: `${installDir}/server-ui/dist`,
304
+ onboarding: {
305
+ status: 'FRESH',
306
+ },
307
+ };
308
+ }
309
+
310
+ await writeFileNoFollow(configPath, JSON.stringify(config, null, 2) + '\n', {
311
+ mode: 0o640,
312
+ uid: await lookupId('user', 'lamaste'),
313
+ gid: await lookupId('group', 'lamaste'),
314
+ });
315
+
316
+ subtask.output = `Configuration written to ${configPath}`;
317
+ },
318
+ rendererOptions: { persistentOutput: true },
319
+ },
320
+ {
321
+ title: 'Writing systemd service unit',
322
+ task: async (_ctx, subtask) => {
323
+ const serviceUnit = generateServiceUnit({ installDir, configDir });
324
+ await writeFile('/etc/systemd/system/lamalibre-lamaste-serverd.service', serviceUnit);
325
+
326
+ subtask.output = 'Systemd service unit written';
327
+ },
328
+ rendererOptions: { persistentOutput: true },
329
+ },
330
+ {
331
+ title: 'Installing sudoers wrapper scripts',
332
+ task: async (_ctx, subtask) => {
333
+ // The root-owned programs the sudoers rules name. They must be in
334
+ // place before the panel service starts and calls them.
335
+ await installSudoersWrappers(join(packageRoot, 'scripts'), (line) => {
336
+ subtask.output = line;
337
+ });
338
+ subtask.output = 'Sudoers wrappers installed';
339
+ },
340
+ rendererOptions: { persistentOutput: true },
341
+ },
342
+ {
343
+ title: 'Writing sudoers rules',
344
+ task: async (_ctx, subtask) => {
345
+ await installSudoersFile(generateSudoersContent());
346
+
347
+ subtask.output = 'Sudoers rules written and validated';
348
+ },
349
+ rendererOptions: { persistentOutput: true },
350
+ },
351
+ {
352
+ title: 'Starting panel service',
353
+ task: async (_ctx, subtask) => {
354
+ subtask.output = 'Reloading systemd daemon...';
355
+ await execa('systemctl', ['daemon-reload']);
356
+
357
+ subtask.output = 'Enabling and starting lamalibre-lamaste-serverd...';
358
+ await execa('systemctl', ['enable', 'lamalibre-lamaste-serverd']);
359
+ await execa('systemctl', ['start', 'lamalibre-lamaste-serverd']);
360
+
361
+ // Wait for the service to start
362
+ subtask.output = 'Waiting for service to start...';
363
+ await sleep(3000);
364
+
365
+ const { stdout: status } = await execa('systemctl', [
366
+ 'is-active',
367
+ 'lamalibre-lamaste-serverd',
368
+ ]);
369
+ if (status.trim() !== 'active') {
370
+ const { stdout: logs } = await execa('journalctl', [
371
+ '-u',
372
+ 'lamalibre-lamaste-serverd',
373
+ '--no-pager',
374
+ '-n',
375
+ '20',
376
+ ]);
377
+ throw new Error(
378
+ `Panel service failed to start. Status: ${status.trim()}\nRecent logs:\n${logs}`,
379
+ );
380
+ }
381
+
382
+ // Health check
383
+ subtask.output = 'Running health check...';
384
+ try {
385
+ const { stdout: healthResponse } = await execa('curl', [
386
+ '-s',
387
+ '--max-time',
388
+ '5',
389
+ 'http://127.0.0.1:3100/api/health',
390
+ ]);
391
+ subtask.output = `Panel service running. Health: ${healthResponse}`;
392
+ } catch (error) {
393
+ const { stdout: logs } = await execa('journalctl', [
394
+ '-u',
395
+ 'lamalibre-lamaste-serverd',
396
+ '--no-pager',
397
+ '-n',
398
+ '20',
399
+ ]);
400
+ throw new Error(
401
+ `Panel health check failed. The service is running but not responding.\nRecent logs:\n${logs}\n${error.message}`,
402
+ );
403
+ }
404
+ },
405
+ rendererOptions: { persistentOutput: true },
406
+ },
407
+ ]);
408
+ }