@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,438 @@
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 { ensureHttpRedirect } from '../lib/http-redirect.js';
11
+ import { generateCertHelpPage } from '../lib/cert-help-page.js';
12
+ import { ensureRootOwnedInstallDir, removeTree, writeFileNoFollow } from '../lib/ownership.js';
13
+ import { provisionRelayServices } from '../lib/relay-services.js';
14
+ import { installSudoersFile, installSudoersWrappers } from '../lib/wrappers.js';
15
+ import { deployGatekeeperPackage } from './gatekeeper.js';
16
+
17
+ const GATEKEEPER_SERVICE = 'lamalibre-lamaste-gatekeeper';
18
+
19
+ async function isActive(service) {
20
+ try {
21
+ const { stdout } = await execa('systemctl', ['is-active', service]);
22
+ return stdout.trim() === 'active';
23
+ } catch {
24
+ return false;
25
+ }
26
+ }
27
+
28
+ /**
29
+ * Read the installed serverd's package.json version, or null if not found.
30
+ * @param {string} installDir
31
+ * @returns {Promise<string | null>}
32
+ */
33
+ async function getInstalledVersion(installDir) {
34
+ try {
35
+ const pkgPath = join(installDir, 'serverd', 'package.json');
36
+ const raw = await readFile(pkgPath, 'utf8');
37
+ return JSON.parse(raw).version || null;
38
+ } catch {
39
+ return null;
40
+ }
41
+ }
42
+
43
+ /**
44
+ * Fetch the latest published version of @lamalibre/lamaste-serverd from npm.
45
+ * @returns {Promise<string | null>}
46
+ */
47
+ async function getLatestNpmVersion() {
48
+ try {
49
+ const { stdout } = await execa('npm', ['view', '@lamalibre/lamaste-serverd', 'version']);
50
+ return stdout.trim() || null;
51
+ } catch {
52
+ return null;
53
+ }
54
+ }
55
+
56
+ /**
57
+ * Panel redeployment subtasks — the upgrade path, also run by the panel's
58
+ * self-update (`lamaste-priv self-update`). Updates the panel, CLI, UI, docs
59
+ * and gatekeeper; installs the pinned chisel and Authelia releases, their
60
+ * units, the sudoers rules and the programs they name; restarts what
61
+ * changed. Does not touch OS hardening, mTLS certs or the panel's vhosts.
62
+ *
63
+ * Runs as root and may be started by a compromised panel, so it never
64
+ * writes through a path the lamaste user controls (see lib/ownership.js).
65
+ *
66
+ * @param {object} ctx Shared installer context.
67
+ * @param {object} task Parent Listr2 task reference.
68
+ * @returns {import('listr2').ListrTask[]}
69
+ */
70
+ export function redeployTasks(ctx, task) {
71
+ const installDir = ctx.installDir;
72
+ const configDir = ctx.configDir;
73
+
74
+ // vendorDir still needed for lamaste-server-ui (pre-built static dist)
75
+ const thisFile = fileURLToPath(import.meta.url);
76
+ const vendorDir = join(dirname(thisFile), '..', '..', 'vendor');
77
+
78
+ return task.newListr([
79
+ {
80
+ title: 'Checking versions',
81
+ task: async (_ctx, subtask) => {
82
+ const installed = await getInstalledVersion(installDir);
83
+ const latest = await getLatestNpmVersion();
84
+ ctx.installedVersion = installed;
85
+ ctx.latestVersion = latest;
86
+ subtask.output = `Installed: ${installed || 'unknown'} → Latest: ${latest || 'unknown'}`;
87
+ },
88
+ rendererOptions: { persistentOutput: true },
89
+ },
90
+ {
91
+ title: 'Stopping panel services',
92
+ task: async (_ctx, subtask) => {
93
+ const panelActive = await isActive('lamalibre-lamaste-serverd');
94
+ if (panelActive) await execa('systemctl', ['stop', 'lamalibre-lamaste-serverd']);
95
+ // Gatekeeper runs from the install directory, which may be replaced.
96
+ ctx.gatekeeperWasActive = await isActive(GATEKEEPER_SERVICE);
97
+ if (ctx.gatekeeperWasActive) await execa('systemctl', ['stop', GATEKEEPER_SERVICE]);
98
+ subtask.output = panelActive ? 'Services stopped' : 'Panel was not running';
99
+ },
100
+ rendererOptions: { persistentOutput: true },
101
+ },
102
+ {
103
+ title: 'Securing the install directory',
104
+ task: async (_ctx, subtask) => {
105
+ // Earlier versions gave the install directory to the lamaste user.
106
+ // Code root runs (the lamaste-server CLI) and code the panel runs
107
+ // must not be writable by the panel: the old tree is moved aside,
108
+ // every component is redeployed into a fresh root-owned directory,
109
+ // and the old tree is removed at the end.
110
+ const { previous } = await ensureRootOwnedInstallDir(installDir);
111
+ ctx.previousInstallDir = previous;
112
+ subtask.output = previous
113
+ ? `Moved the lamaste-owned tree aside (${previous}); redeploying into a root-owned directory`
114
+ : 'Install directory is root-owned';
115
+ },
116
+ rendererOptions: { persistentOutput: true },
117
+ },
118
+ {
119
+ title: 'Updating serverd',
120
+ task: async (_ctx, subtask) => {
121
+ const serverDest = join(installDir, 'serverd');
122
+ // A fresh root-private directory: a predictable /tmp path could be
123
+ // prepared in advance by another local user.
124
+ const tmpDir = await mkdtemp(join(tmpdir(), 'lamaste-panel-update-'));
125
+
126
+ try {
127
+ // Download the package tarball to a temp directory via npm pack,
128
+ // then extract. We cannot `npm install` inside serverDest because
129
+ // its package.json has the same name — npm refuses to install a
130
+ // package as a dependency of itself.
131
+ await mkdir(serverDest, { recursive: true });
132
+
133
+ subtask.output = 'Downloading @lamalibre/lamaste-serverd from npm...';
134
+ const { stdout: tarball } = await execa('npm', [
135
+ 'pack',
136
+ '@lamalibre/lamaste-serverd@latest',
137
+ '--prefer-online',
138
+ '--pack-destination',
139
+ tmpDir,
140
+ ]);
141
+ const tarballPath = join(tmpDir, tarball.trim());
142
+
143
+ subtask.output = 'Extracting package...';
144
+ await execa('tar', ['xzf', tarballPath, '-C', tmpDir]);
145
+
146
+ // npm pack extracts to a `package/` directory
147
+ const extracted = join(tmpDir, 'package');
148
+
149
+ subtask.output = 'Copying serverd files...';
150
+ await rm(join(serverDest, 'src'), { recursive: true, force: true });
151
+ await cp(join(extracted, 'src'), join(serverDest, 'src'), { recursive: true });
152
+ await cp(join(extracted, 'package.json'), join(serverDest, 'package.json'));
153
+
154
+ subtask.output = 'Installing production dependencies...';
155
+ await execa('npm', ['install', '--production', '--ignore-scripts'], {
156
+ cwd: serverDest,
157
+ });
158
+
159
+ subtask.output = `Panel server updated to ${ctx.latestVersion || 'latest'}`;
160
+ } finally {
161
+ await rm(tmpDir, { recursive: true, force: true }).catch(() => {});
162
+ }
163
+ },
164
+ rendererOptions: { persistentOutput: true },
165
+ },
166
+ {
167
+ title: 'Updating lamaste-server CLI',
168
+ task: async (_ctx, subtask) => {
169
+ // Deploy the operational CLI from the bundled vendor directory —
170
+ // the redeploy path installs the CLI version that shipped with
171
+ // this create-lamaste release rather than querying npm again.
172
+ // Matches the full-install deploy in tasks/panel.js; also
173
+ // (re)installs the lamaste-reset-admin shim so recovery is
174
+ // available even if the shim drifted.
175
+ const cliSrc = join(vendorDir, 'server');
176
+ const cliDest = join(installDir, 'server');
177
+
178
+ if (!existsSync(cliSrc)) {
179
+ subtask.output =
180
+ 'lamaste-server CLI not bundled — skipping (upgrade create-lamaste to fix)';
181
+ return;
182
+ }
183
+
184
+ await mkdir(cliDest, { recursive: true });
185
+ await cp(join(cliSrc, 'package.json'), join(cliDest, 'package.json'), { force: true });
186
+ await rm(join(cliDest, 'bin'), { recursive: true, force: true });
187
+ await rm(join(cliDest, 'src'), { recursive: true, force: true });
188
+ await cp(join(cliSrc, 'bin'), join(cliDest, 'bin'), { recursive: true, force: true });
189
+ await cp(join(cliSrc, 'src'), join(cliDest, 'src'), { recursive: true, force: true });
190
+
191
+ subtask.output = 'Installing lamaste-server CLI dependencies...';
192
+ await execa('npm', ['install', '--production', '--ignore-scripts'], { cwd: cliDest });
193
+
194
+ const serverBin = join(cliDest, 'bin', 'lamaste-server.js');
195
+ await execa('chmod', ['+x', serverBin]);
196
+ await execa('ln', ['-sf', serverBin, '/usr/local/bin/lamaste-server']);
197
+
198
+ const shimPath = '/usr/local/bin/lamaste-reset-admin';
199
+ const shim = [
200
+ '#!/usr/bin/env bash',
201
+ '# Shim installed by create-lamaste: delegates to lamaste-server CLI.',
202
+ 'exec /usr/local/bin/lamaste-server reset-admin "$@"',
203
+ '',
204
+ ].join('\n');
205
+ await writeFile(shimPath, shim, { mode: 0o755 });
206
+
207
+ subtask.output = 'lamaste-server CLI updated; lamaste-reset-admin shim refreshed';
208
+ },
209
+ rendererOptions: { persistentOutput: true },
210
+ },
211
+ {
212
+ title: 'Updating server-ui',
213
+ task: async (_ctx, subtask) => {
214
+ const clientSrc = join(vendorDir, 'server-ui');
215
+ const clientDest = join(installDir, 'server-ui');
216
+
217
+ if (!existsSync(clientSrc)) {
218
+ throw new Error(
219
+ `Panel client source not found at ${clientSrc}. Ensure the package is intact.`,
220
+ );
221
+ }
222
+
223
+ const prebuiltDist = join(clientSrc, 'dist');
224
+ if (!existsSync(join(prebuiltDist, 'index.html'))) {
225
+ throw new Error(
226
+ 'Pre-built lamaste-server-ui dist not found. The package may be corrupted.',
227
+ );
228
+ }
229
+
230
+ subtask.output = 'Copying lamaste-server-ui dist...';
231
+ await mkdir(clientDest, { recursive: true });
232
+ const distDest = join(clientDest, 'dist');
233
+ await rm(distDest, { recursive: true, force: true });
234
+ await cp(prebuiltDist, distDest, { recursive: true });
235
+
236
+ subtask.output = 'Panel client updated';
237
+ },
238
+ rendererOptions: { persistentOutput: true },
239
+ },
240
+ {
241
+ title: 'Updating documentation',
242
+ task: async (_ctx, subtask) => {
243
+ const docsSrc = join(vendorDir, 'docs');
244
+ const docsDest = join(installDir, 'docs');
245
+
246
+ if (existsSync(docsSrc)) {
247
+ subtask.output = 'Copying version-stamped docs...';
248
+ await rm(docsDest, { recursive: true, force: true });
249
+ await cp(docsSrc, docsDest, { recursive: true });
250
+ subtask.output = 'Documentation updated';
251
+ } else {
252
+ subtask.output = 'No bundled docs found — skipping';
253
+ }
254
+ },
255
+ rendererOptions: { persistentOutput: true },
256
+ },
257
+ {
258
+ title: 'Updating gatekeeper',
259
+ task: async (_ctx, subtask) => {
260
+ subtask.output = (await deployGatekeeperPackage(vendorDir, installDir))
261
+ ? 'Gatekeeper updated'
262
+ : 'Gatekeeper not bundled — skipping';
263
+ },
264
+ rendererOptions: { persistentOutput: true },
265
+ },
266
+ {
267
+ title: 'Updating certificate help page',
268
+ task: async (_ctx, subtask) => {
269
+ const helpDir = join(installDir, 'lamaste-server-ui');
270
+ await mkdir(helpDir, { recursive: true });
271
+ await writeFile(join(helpDir, 'cert-help.html'), generateCertHelpPage(ctx));
272
+ subtask.output = 'Certificate help page written';
273
+ },
274
+ rendererOptions: { persistentOutput: true },
275
+ },
276
+ {
277
+ title: 'Updating panel configuration',
278
+ task: async (_ctx, subtask) => {
279
+ const configPath = join(configDir, 'panel.json');
280
+
281
+ if (!existsSync(configPath)) {
282
+ subtask.output = 'No existing config — skipping (full install needed)';
283
+ return;
284
+ }
285
+
286
+ subtask.output = 'Merging configuration...';
287
+ const existing = JSON.parse(await readFile(configPath, 'utf8'));
288
+
289
+ const config = {
290
+ ...existing,
291
+ ip: ctx.ip,
292
+ dataDir: configDir,
293
+ staticDir: join(installDir, 'server-ui', 'dist'),
294
+ };
295
+
296
+ // The config directory belongs to lamaste: write a fresh file and
297
+ // rename it into place, never through what is already there.
298
+ await writeFileNoFollow(configPath, JSON.stringify(config, null, 2) + '\n', {
299
+ mode: 0o600,
300
+ uid: await lookupId('user', 'lamaste'),
301
+ gid: await lookupId('group', 'lamaste'),
302
+ });
303
+
304
+ subtask.output = 'Configuration updated';
305
+ },
306
+ rendererOptions: { persistentOutput: true },
307
+ },
308
+ {
309
+ title: 'Updating chisel and Authelia',
310
+ task: async (_ctx, subtask) => {
311
+ // Authelia is stopped while its files change hands (earlier versions
312
+ // ran it as root: a root process writing its database during the
313
+ // migration would leave root-owned files the new account cannot
314
+ // open) and while its binary is replaced; it is started again below.
315
+ ctx.autheliaWasActive = await isActive('authelia');
316
+ if (ctx.autheliaWasActive) await execa('systemctl', ['stop', 'authelia']);
317
+ const result = await provisionRelayServices(ctx, (line) => {
318
+ subtask.output = line;
319
+ });
320
+ ctx.chiselChanged = result.chiselChanged;
321
+ subtask.output = 'chisel and Authelia up to date';
322
+ },
323
+ rendererOptions: { persistentOutput: true },
324
+ },
325
+ {
326
+ title: 'Updating systemd unit and sudoers',
327
+ task: async (_ctx, subtask) => {
328
+ subtask.output = 'Writing systemd service unit...';
329
+ const serviceUnit = generateServiceUnit({ installDir, configDir });
330
+ await writeFile('/etc/systemd/system/lamalibre-lamaste-serverd.service', serviceUnit);
331
+
332
+ // The programs the sudoers rules name go in first; retired ones go.
333
+ await installSudoersWrappers(join(dirname(thisFile), '..', '..', 'scripts'), (line) => {
334
+ subtask.output = line;
335
+ });
336
+
337
+ subtask.output = 'Writing sudoers rules...';
338
+ await installSudoersFile(generateSudoersContent());
339
+
340
+ subtask.output = 'Systemd unit and sudoers updated';
341
+ },
342
+ rendererOptions: { persistentOutput: true },
343
+ },
344
+ {
345
+ title: 'Redirecting plain HTTP to HTTPS',
346
+ task: async (_ctx, subtask) => {
347
+ // Not fatal: another site on this machine may already own port 80's
348
+ // default server. The relay works without the redirect.
349
+ try {
350
+ const { changed } = await ensureHttpRedirect();
351
+ subtask.output = changed
352
+ ? 'Port 80 now redirects every host to https://'
353
+ : 'Port 80 redirect already in place';
354
+ } catch (err) {
355
+ subtask.skip(`Skipped — ${err.message}`);
356
+ }
357
+ },
358
+ rendererOptions: { persistentOutput: true },
359
+ },
360
+ {
361
+ title: 'Reloading systemd and restarting panel',
362
+ task: async (_ctx, subtask) => {
363
+ subtask.output = 'Reloading systemd daemon...';
364
+ await execa('systemctl', ['daemon-reload']);
365
+
366
+ // A new binary or unit takes effect on restart. try-restart leaves a
367
+ // service that is not running (not yet onboarded, or held down by the
368
+ // panel's fail-closed reconciliation) alone.
369
+ if (ctx.chiselChanged) {
370
+ subtask.output = 'Restarting chisel...';
371
+ await execa('systemctl', ['try-restart', 'chisel']);
372
+ }
373
+ if (ctx.autheliaWasActive) {
374
+ subtask.output = 'Starting Authelia...';
375
+ await execa('systemctl', ['start', 'authelia']);
376
+ }
377
+ if (ctx.gatekeeperWasActive) {
378
+ subtask.output = 'Starting gatekeeper...';
379
+ await execa('systemctl', ['start', GATEKEEPER_SERVICE]);
380
+ }
381
+
382
+ subtask.output = 'Starting lamalibre-lamaste-serverd...';
383
+ await execa('systemctl', ['start', 'lamalibre-lamaste-serverd']);
384
+
385
+ subtask.output = 'Waiting for service to start...';
386
+ await sleep(3000);
387
+
388
+ const { stdout: status } = await execa('systemctl', [
389
+ 'is-active',
390
+ 'lamalibre-lamaste-serverd',
391
+ ]);
392
+ if (status.trim() !== 'active') {
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 service failed to start. Status: ${status.trim()}\nRecent logs:\n${logs}`,
402
+ );
403
+ }
404
+
405
+ subtask.output = 'Running health check...';
406
+ try {
407
+ const { stdout: healthResponse } = await execa('curl', [
408
+ '-s',
409
+ '--max-time',
410
+ '5',
411
+ 'http://127.0.0.1:3100/api/health',
412
+ ]);
413
+ subtask.output = `Panel running. Health: ${healthResponse}`;
414
+ } catch (error) {
415
+ const { stdout: logs } = await execa('journalctl', [
416
+ '-u',
417
+ 'lamalibre-lamaste-serverd',
418
+ '--no-pager',
419
+ '-n',
420
+ '20',
421
+ ]);
422
+ throw new Error(`Panel health check failed.\nRecent logs:\n${logs}\n${error.message}`);
423
+ }
424
+ },
425
+ rendererOptions: { persistentOutput: true },
426
+ },
427
+ {
428
+ title: 'Removing the previous install directory',
429
+ skip: () => (ctx.previousInstallDir ? false : 'Nothing to remove'),
430
+ task: async (_ctx, subtask) => {
431
+ // coreutils rm: does not follow symlinks the old (lamaste-owned) tree holds.
432
+ await removeTree(ctx.previousInstallDir);
433
+ subtask.output = `Removed ${ctx.previousInstallDir}`;
434
+ },
435
+ rendererOptions: { persistentOutput: true },
436
+ },
437
+ ]);
438
+ }