@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.
- package/LICENSE.md +118 -0
- package/README.md +114 -0
- package/bin/create-lamaste.js +15 -0
- package/package.json +54 -0
- package/scripts/bundle-vendor.js +110 -0
- package/scripts/lamaste-cert-info +62 -0
- package/scripts/lamaste-certbot +96 -0
- package/scripts/lamaste-priv +693 -0
- package/src/index.js +646 -0
- package/src/lib/accounts.js +86 -0
- package/src/lib/binaries.js +137 -0
- package/src/lib/cert-help-page.js +160 -0
- package/src/lib/env.js +132 -0
- package/src/lib/http-redirect.js +38 -0
- package/src/lib/ownership.js +231 -0
- package/src/lib/relay-services.js +103 -0
- package/src/lib/secrets.js +19 -0
- package/src/lib/service-config.js +311 -0
- package/src/lib/summary.js +131 -0
- package/src/lib/wrappers.js +63 -0
- package/src/tasks/gatekeeper.js +233 -0
- package/src/tasks/harden.js +293 -0
- package/src/tasks/mtls.js +193 -0
- package/src/tasks/nginx.js +265 -0
- package/src/tasks/node.js +110 -0
- package/src/tasks/panel.js +408 -0
- package/src/tasks/redeploy.js +438 -0
- package/vendor/docs/00-introduction/how-it-works.md +447 -0
- package/vendor/docs/00-introduction/quickstart.md +472 -0
- package/vendor/docs/00-introduction/what-is-lamaste.md +112 -0
- package/vendor/docs/01-concepts/authentication.md +464 -0
- package/vendor/docs/01-concepts/authorization.md +242 -0
- package/vendor/docs/01-concepts/certificates.md +452 -0
- package/vendor/docs/01-concepts/dns-and-domains.md +343 -0
- package/vendor/docs/01-concepts/mtls.md +490 -0
- package/vendor/docs/01-concepts/nginx-reverse-proxy.md +540 -0
- package/vendor/docs/01-concepts/security-model.md +600 -0
- package/vendor/docs/01-concepts/tickets.md +414 -0
- package/vendor/docs/01-concepts/tunneling.md +367 -0
- package/vendor/docs/02-guides/agent-setup.md +194 -0
- package/vendor/docs/02-guides/certificate-management.md +341 -0
- package/vendor/docs/02-guides/cloud-provisioning.md +406 -0
- package/vendor/docs/02-guides/desktop-app-setup.md +226 -0
- package/vendor/docs/02-guides/disaster-recovery.md +464 -0
- package/vendor/docs/02-guides/first-tunnel.md +230 -0
- package/vendor/docs/02-guides/installation.md +393 -0
- package/vendor/docs/02-guides/managing-users.md +255 -0
- package/vendor/docs/02-guides/onboarding.md +244 -0
- package/vendor/docs/02-guides/standalone-plugin-with-tickets.md +375 -0
- package/vendor/docs/02-guides/static-sites.md +364 -0
- package/vendor/docs/03-architecture/e2e-three-vm-sequences.md +457 -0
- package/vendor/docs/03-architecture/installer-flow.md +151 -0
- package/vendor/docs/03-architecture/installer.md +445 -0
- package/vendor/docs/03-architecture/management-flow.md +288 -0
- package/vendor/docs/03-architecture/nginx-configuration.md +830 -0
- package/vendor/docs/03-architecture/onboarding-flow.md +164 -0
- package/vendor/docs/03-architecture/overview.md +541 -0
- package/vendor/docs/03-architecture/panel-client.md +416 -0
- package/vendor/docs/03-architecture/panel-server.md +695 -0
- package/vendor/docs/03-architecture/state-management.md +529 -0
- package/vendor/docs/03-architecture/system-overview.md +167 -0
- package/vendor/docs/04-api-reference/certificates.md +1141 -0
- package/vendor/docs/04-api-reference/gatekeeper.md +592 -0
- package/vendor/docs/04-api-reference/onboarding.md +473 -0
- package/vendor/docs/04-api-reference/overview.md +404 -0
- package/vendor/docs/04-api-reference/services.md +267 -0
- package/vendor/docs/04-api-reference/settings.md +343 -0
- package/vendor/docs/04-api-reference/sites.md +716 -0
- package/vendor/docs/04-api-reference/system.md +268 -0
- package/vendor/docs/04-api-reference/tickets.md +618 -0
- package/vendor/docs/04-api-reference/tunnels.md +417 -0
- package/vendor/docs/04-api-reference/users.md +333 -0
- package/vendor/docs/05-operations/backup-and-restore.md +342 -0
- package/vendor/docs/05-operations/monitoring.md +285 -0
- package/vendor/docs/05-operations/uninstalling.md +344 -0
- package/vendor/docs/05-operations/upgrades.md +270 -0
- package/vendor/docs/06-reference/config-files.md +1080 -0
- package/vendor/docs/06-reference/glossary.md +528 -0
- package/vendor/docs/06-reference/installer-flags.md +260 -0
- package/vendor/docs/06-reference/ports-and-services.md +244 -0
- package/vendor/docs/06-reference/troubleshooting.md +665 -0
- package/vendor/docs/_index.json +233 -0
- package/vendor/docs/gatekeeper-design.md +953 -0
- package/vendor/docs/package.json +15 -0
- package/vendor/docs/todo/lib-cli-desktop-refactor.md +641 -0
- package/vendor/gatekeeper/dist/boot.d.ts +1 -0
- package/vendor/gatekeeper/dist/boot.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/boot.js +17 -0
- package/vendor/gatekeeper/dist/boot.js.map +1 -0
- package/vendor/gatekeeper/dist/cli/commands/access.d.ts +2 -0
- package/vendor/gatekeeper/dist/cli/commands/access.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/cli/commands/access.js +33 -0
- package/vendor/gatekeeper/dist/cli/commands/access.js.map +1 -0
- package/vendor/gatekeeper/dist/cli/commands/grants.d.ts +2 -0
- package/vendor/gatekeeper/dist/cli/commands/grants.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/cli/commands/grants.js +110 -0
- package/vendor/gatekeeper/dist/cli/commands/grants.js.map +1 -0
- package/vendor/gatekeeper/dist/cli/commands/groups.d.ts +2 -0
- package/vendor/gatekeeper/dist/cli/commands/groups.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/cli/commands/groups.js +98 -0
- package/vendor/gatekeeper/dist/cli/commands/groups.js.map +1 -0
- package/vendor/gatekeeper/dist/cli/index.d.ts +3 -0
- package/vendor/gatekeeper/dist/cli/index.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/cli/index.js +45 -0
- package/vendor/gatekeeper/dist/cli/index.js.map +1 -0
- package/vendor/gatekeeper/dist/index.d.ts +9 -0
- package/vendor/gatekeeper/dist/index.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/index.js +13 -0
- package/vendor/gatekeeper/dist/index.js.map +1 -0
- package/vendor/gatekeeper/dist/lib/authz.d.ts +16 -0
- package/vendor/gatekeeper/dist/lib/authz.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/lib/authz.js +42 -0
- package/vendor/gatekeeper/dist/lib/authz.js.map +1 -0
- package/vendor/gatekeeper/dist/lib/cache-bust.d.ts +15 -0
- package/vendor/gatekeeper/dist/lib/cache-bust.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/lib/cache-bust.js +19 -0
- package/vendor/gatekeeper/dist/lib/cache-bust.js.map +1 -0
- package/vendor/gatekeeper/dist/lib/constants.d.ts +82 -0
- package/vendor/gatekeeper/dist/lib/constants.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/lib/constants.js +79 -0
- package/vendor/gatekeeper/dist/lib/constants.js.map +1 -0
- package/vendor/gatekeeper/dist/lib/grants.d.ts +32 -0
- package/vendor/gatekeeper/dist/lib/grants.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/lib/grants.js +314 -0
- package/vendor/gatekeeper/dist/lib/grants.js.map +1 -0
- package/vendor/gatekeeper/dist/lib/groups.d.ts +10 -0
- package/vendor/gatekeeper/dist/lib/groups.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/lib/groups.js +247 -0
- package/vendor/gatekeeper/dist/lib/groups.js.map +1 -0
- package/vendor/gatekeeper/dist/lib/migration.d.ts +17 -0
- package/vendor/gatekeeper/dist/lib/migration.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/lib/migration.js +79 -0
- package/vendor/gatekeeper/dist/lib/migration.js.map +1 -0
- package/vendor/gatekeeper/dist/lib/migrations/gatekeeper/0001_groups.sql +7 -0
- package/vendor/gatekeeper/dist/lib/migrations/gatekeeper/0002_access_grants.sql +14 -0
- package/vendor/gatekeeper/dist/lib/migrations/gatekeeper/0003_access_request_log.sql +9 -0
- package/vendor/gatekeeper/dist/lib/migrations/migrations/gatekeeper/0001_groups.sql +7 -0
- package/vendor/gatekeeper/dist/lib/migrations/migrations/gatekeeper/0002_access_grants.sql +14 -0
- package/vendor/gatekeeper/dist/lib/migrations/migrations/gatekeeper/0003_access_request_log.sql +9 -0
- package/vendor/gatekeeper/dist/lib/state-db.d.ts +16 -0
- package/vendor/gatekeeper/dist/lib/state-db.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/lib/state-db.js +154 -0
- package/vendor/gatekeeper/dist/lib/state-db.js.map +1 -0
- package/vendor/gatekeeper/dist/lib/templates.d.ts +21 -0
- package/vendor/gatekeeper/dist/lib/templates.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/lib/templates.js +151 -0
- package/vendor/gatekeeper/dist/lib/templates.js.map +1 -0
- package/vendor/gatekeeper/dist/lib/types.d.ts +178 -0
- package/vendor/gatekeeper/dist/lib/types.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/lib/types.js +2 -0
- package/vendor/gatekeeper/dist/lib/types.js.map +1 -0
- package/vendor/gatekeeper/dist/server/index.d.ts +15 -0
- package/vendor/gatekeeper/dist/server/index.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/server/index.js +251 -0
- package/vendor/gatekeeper/dist/server/index.js.map +1 -0
- package/vendor/gatekeeper/dist/server/routes/access-request.d.ts +7 -0
- package/vendor/gatekeeper/dist/server/routes/access-request.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/server/routes/access-request.js +17 -0
- package/vendor/gatekeeper/dist/server/routes/access-request.js.map +1 -0
- package/vendor/gatekeeper/dist/server/routes/authz.d.ts +3 -0
- package/vendor/gatekeeper/dist/server/routes/authz.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/server/routes/authz.js +279 -0
- package/vendor/gatekeeper/dist/server/routes/authz.js.map +1 -0
- package/vendor/gatekeeper/dist/server/routes/diagnostic.d.ts +13 -0
- package/vendor/gatekeeper/dist/server/routes/diagnostic.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/server/routes/diagnostic.js +249 -0
- package/vendor/gatekeeper/dist/server/routes/diagnostic.js.map +1 -0
- package/vendor/gatekeeper/dist/server/routes/grants.d.ts +3 -0
- package/vendor/gatekeeper/dist/server/routes/grants.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/server/routes/grants.js +112 -0
- package/vendor/gatekeeper/dist/server/routes/grants.js.map +1 -0
- package/vendor/gatekeeper/dist/server/routes/groups.d.ts +3 -0
- package/vendor/gatekeeper/dist/server/routes/groups.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/server/routes/groups.js +139 -0
- package/vendor/gatekeeper/dist/server/routes/groups.js.map +1 -0
- package/vendor/gatekeeper/package.json +49 -0
- package/vendor/server/bin/lamaste-server.js +204 -0
- package/vendor/server/package.json +31 -0
- package/vendor/server/src/commands/certs.js +224 -0
- package/vendor/server/src/commands/chisel.js +132 -0
- package/vendor/server/src/commands/logs.js +49 -0
- package/vendor/server/src/commands/plugins.js +321 -0
- package/vendor/server/src/commands/reset-admin.js +478 -0
- package/vendor/server/src/commands/restart.js +94 -0
- package/vendor/server/src/commands/sites.js +257 -0
- package/vendor/server/src/commands/status.js +158 -0
- package/vendor/server/src/commands/tunnels.js +365 -0
- package/vendor/server/src/commands/uninstall.js +326 -0
- package/vendor/server/src/config.js +38 -0
- package/vendor/server/src/exec.js +26 -0
- package/vendor/server/src/logger.js +39 -0
- package/vendor/server/src/ndjson.js +56 -0
- package/vendor/server/src/panel-api.js +85 -0
- package/vendor/server/src/state.js +45 -0
- package/vendor/server-ui/dist/assets/index-B0nWuUyL.js +257 -0
- package/vendor/server-ui/dist/assets/index-BqWWFlTU.css +1 -0
- package/vendor/server-ui/dist/index.html +13 -0
- package/vendor/serverd/package.json +44 -0
- package/vendor/serverd/src/boot.js +13 -0
- package/vendor/serverd/src/index.js +191 -0
- package/vendor/serverd/src/lib/app-error.js +14 -0
- package/vendor/serverd/src/lib/authelia.js +85 -0
- package/vendor/serverd/src/lib/certbot.js +74 -0
- package/vendor/serverd/src/lib/chisel-args.js +6 -0
- package/vendor/serverd/src/lib/chisel-reconcile.js +232 -0
- package/vendor/serverd/src/lib/chisel-runtime.js +46 -0
- package/vendor/serverd/src/lib/chisel-users.js +109 -0
- package/vendor/serverd/src/lib/chisel.js +81 -0
- package/vendor/serverd/src/lib/config.js +190 -0
- package/vendor/serverd/src/lib/constants.js +8 -0
- package/vendor/serverd/src/lib/csr-signing.js +331 -0
- package/vendor/serverd/src/lib/enrollment.js +302 -0
- package/vendor/serverd/src/lib/files.js +19 -0
- package/vendor/serverd/src/lib/gatekeeper-client.js +135 -0
- package/vendor/serverd/src/lib/invite-page.js +281 -0
- package/vendor/serverd/src/lib/ip-utils.js +79 -0
- package/vendor/serverd/src/lib/migrations/state/0001_enrollment_tokens.sql +17 -0
- package/vendor/serverd/src/lib/migrations/state/0002_tickets.sql +70 -0
- package/vendor/serverd/src/lib/migrations/state/0003_plugins.sql +15 -0
- package/vendor/serverd/src/lib/migrations/state/0004_agents.sql +19 -0
- package/vendor/serverd/src/lib/mtls.js +795 -0
- package/vendor/serverd/src/lib/nginx.js +1090 -0
- package/vendor/serverd/src/lib/pki-paths.js +41 -0
- package/vendor/serverd/src/lib/pki-sign.js +103 -0
- package/vendor/serverd/src/lib/plugins.js +538 -0
- package/vendor/serverd/src/lib/push-install.js +390 -0
- package/vendor/serverd/src/lib/revocation.js +112 -0
- package/vendor/serverd/src/lib/services.js +131 -0
- package/vendor/serverd/src/lib/session.js +151 -0
- package/vendor/serverd/src/lib/state-db.js +130 -0
- package/vendor/serverd/src/lib/state.js +140 -0
- package/vendor/serverd/src/lib/storage.js +63 -0
- package/vendor/serverd/src/lib/system-stats.js +58 -0
- package/vendor/serverd/src/lib/tickets.js +1556 -0
- package/vendor/serverd/src/lib/totp.js +76 -0
- package/vendor/serverd/src/lib/tunnel-deps.js +41 -0
- package/vendor/serverd/src/lib/user-access-session.js +142 -0
- package/vendor/serverd/src/lib/user-access.js +52 -0
- package/vendor/serverd/src/middleware/errors.js +63 -0
- package/vendor/serverd/src/middleware/mtls.js +120 -0
- package/vendor/serverd/src/middleware/onboarding-guard.js +30 -0
- package/vendor/serverd/src/middleware/role-guard.js +58 -0
- package/vendor/serverd/src/middleware/twofa-session.js +65 -0
- package/vendor/serverd/src/middleware/user-access-session.js +52 -0
- package/vendor/serverd/src/routes/enrollment.js +120 -0
- package/vendor/serverd/src/routes/health.js +22 -0
- package/vendor/serverd/src/routes/invite.js +163 -0
- package/vendor/serverd/src/routes/management/agents.js +182 -0
- package/vendor/serverd/src/routes/management/certs.js +911 -0
- package/vendor/serverd/src/routes/management/gatekeeper-proxy.js +160 -0
- package/vendor/serverd/src/routes/management/identity.js +107 -0
- package/vendor/serverd/src/routes/management/invitations.js +166 -0
- package/vendor/serverd/src/routes/management/logs.js +218 -0
- package/vendor/serverd/src/routes/management/plugins.js +560 -0
- package/vendor/serverd/src/routes/management/services.js +63 -0
- package/vendor/serverd/src/routes/management/settings.js +325 -0
- package/vendor/serverd/src/routes/management/sites.js +522 -0
- package/vendor/serverd/src/routes/management/storage.js +192 -0
- package/vendor/serverd/src/routes/management/system.js +201 -0
- package/vendor/serverd/src/routes/management/tickets.js +457 -0
- package/vendor/serverd/src/routes/management/tunnels.js +709 -0
- package/vendor/serverd/src/routes/management/users.js +377 -0
- package/vendor/serverd/src/routes/management.js +36 -0
- package/vendor/serverd/src/routes/onboarding/dns.js +101 -0
- package/vendor/serverd/src/routes/onboarding/domain.js +42 -0
- package/vendor/serverd/src/routes/onboarding/index.js +18 -0
- package/vendor/serverd/src/routes/onboarding/provision.js +327 -0
- package/vendor/serverd/src/routes/onboarding/status.js +12 -0
- package/vendor/serverd/src/routes/plugin-router.js +79 -0
- package/vendor/serverd/src/routes/user-access.js +389 -0
package/src/index.js
ADDED
|
@@ -0,0 +1,646 @@
|
|
|
1
|
+
// TODO(v3): A handful of server-side identifiers (sudoers user, the `lamaste`
|
|
2
|
+
// system user) remain under the "lamaste" name. The VPS-side rebrand from
|
|
3
|
+
// "lamaste" → "lamaste" is its own migration. As of the 2.0 brand-split, FS
|
|
4
|
+
// paths live under `/etc/lamalibre/lamaste/` and `/opt/lamalibre/lamaste/`,
|
|
5
|
+
// nginx vhosts are named `lamalibre-lamaste-panel-*`, and the systemd unit
|
|
6
|
+
// is `lamalibre-lamaste-serverd`. The remaining "lamaste-*" surfaces (user
|
|
7
|
+
// account, sudoers prefix) are touched by the v3 migration on each server.
|
|
8
|
+
|
|
9
|
+
import { createInterface } from 'node:readline';
|
|
10
|
+
import { readFile, readdir } from 'node:fs/promises';
|
|
11
|
+
import { existsSync } from 'node:fs';
|
|
12
|
+
import { Listr } from 'listr2';
|
|
13
|
+
import chalk from 'chalk';
|
|
14
|
+
import { execa } from 'execa';
|
|
15
|
+
import { detectOS, detectIP, checkRoot } from './lib/env.js';
|
|
16
|
+
import { printSummary } from './lib/summary.js';
|
|
17
|
+
import { hardenTasks } from './tasks/harden.js';
|
|
18
|
+
import { nodeTasks } from './tasks/node.js';
|
|
19
|
+
import { mtlsTasks } from './tasks/mtls.js';
|
|
20
|
+
import { nginxTasks } from './tasks/nginx.js';
|
|
21
|
+
import { panelTasks } from './tasks/panel.js';
|
|
22
|
+
import { gatekeeperTasks } from './tasks/gatekeeper.js';
|
|
23
|
+
import { redeployTasks } from './tasks/redeploy.js';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Parse minimal CLI flags from process.argv.
|
|
27
|
+
* @returns {{ skipHarden: boolean, uninstall: boolean, dev: boolean, help: boolean, yes: boolean, json: boolean, forceFull: boolean }}
|
|
28
|
+
*/
|
|
29
|
+
function parseFlags() {
|
|
30
|
+
const args = process.argv.slice(2);
|
|
31
|
+
return {
|
|
32
|
+
skipHarden: args.includes('--skip-harden'),
|
|
33
|
+
uninstall: args.includes('--uninstall'),
|
|
34
|
+
dev: args.includes('--dev'),
|
|
35
|
+
help: args.includes('--help') || args.includes('-h'),
|
|
36
|
+
yes: args.includes('--yes') || args.includes('-y'),
|
|
37
|
+
forceFull: args.includes('--force-full'),
|
|
38
|
+
json: args.includes('--json'),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Emit a single NDJSON line to stdout. Used when --json mode is active
|
|
44
|
+
* so that callers (e.g. Tauri desktop app) can parse structured progress.
|
|
45
|
+
* @param {Record<string, unknown>} obj
|
|
46
|
+
*/
|
|
47
|
+
function emitJson(obj) {
|
|
48
|
+
process.stdout.write(JSON.stringify(obj) + '\n');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Print help message describing Lamaste and what the installer does,
|
|
53
|
+
* then exit with code 0.
|
|
54
|
+
*/
|
|
55
|
+
function printHelp() {
|
|
56
|
+
const b = chalk.bold;
|
|
57
|
+
const c = chalk.cyan;
|
|
58
|
+
const y = chalk.yellow;
|
|
59
|
+
const d = chalk.dim;
|
|
60
|
+
|
|
61
|
+
console.log(`
|
|
62
|
+
${b('Lamaste')} — A self-hosted secure tunneling platform
|
|
63
|
+
|
|
64
|
+
${b('DESCRIPTION')}
|
|
65
|
+
|
|
66
|
+
Lamaste exposes web apps running behind a firewall through a VPS
|
|
67
|
+
via WebSocket-over-HTTPS tunnels. This installer provisions a fresh
|
|
68
|
+
Ubuntu 24.04 droplet with nginx, mTLS client certificates, and a
|
|
69
|
+
browser-based management panel — all in a single command.
|
|
70
|
+
|
|
71
|
+
${b('USAGE')}
|
|
72
|
+
|
|
73
|
+
${c('npx @lamalibre/create-lamaste')} ${d('[flags]')}
|
|
74
|
+
|
|
75
|
+
${b('SYSTEM MODIFICATIONS')}
|
|
76
|
+
|
|
77
|
+
This installer makes the following changes to the machine:
|
|
78
|
+
|
|
79
|
+
${y('Swap & Memory')}
|
|
80
|
+
• Creates a 1GB swap file
|
|
81
|
+
|
|
82
|
+
${y('Firewall & Security')}
|
|
83
|
+
• Resets UFW firewall (allows only ports 22, 80, 443, 9292)
|
|
84
|
+
• Installs fail2ban with SSH and nginx jails
|
|
85
|
+
• Hardens SSH (disables password authentication)
|
|
86
|
+
|
|
87
|
+
${y('Packages')}
|
|
88
|
+
• Installs Node.js 20, nginx, certbot
|
|
89
|
+
|
|
90
|
+
${y('Certificates')}
|
|
91
|
+
• Generates mTLS CA, server, and client certificates
|
|
92
|
+
• Creates a PKCS12 (.p12) bundle for browser-based access
|
|
93
|
+
|
|
94
|
+
${y('Users & Services')}
|
|
95
|
+
• Creates ${c('lamaste')} system user
|
|
96
|
+
• Creates systemd service ${c('lamalibre-lamaste-serverd')}
|
|
97
|
+
• Deploys panel server + client to ${c('/opt/lamalibre/lamaste/')}
|
|
98
|
+
|
|
99
|
+
${y('Directories')}
|
|
100
|
+
• ${c('/etc/lamalibre/lamaste/')} — configuration and PKI certificates
|
|
101
|
+
• ${c('/opt/lamalibre/lamaste/')} — panel server and client files
|
|
102
|
+
• ${c('/var/www/lamaste/')} — static web assets
|
|
103
|
+
|
|
104
|
+
${b('REQUIREMENTS')}
|
|
105
|
+
|
|
106
|
+
• Ubuntu 24.04
|
|
107
|
+
• Root access
|
|
108
|
+
• Public IP address (unless --dev is used)
|
|
109
|
+
|
|
110
|
+
${b('FLAGS')}
|
|
111
|
+
|
|
112
|
+
${c('--help')}, ${c('-h')} Show this help message and exit
|
|
113
|
+
${c('--yes')}, ${c('-y')} Skip the confirmation prompt
|
|
114
|
+
${c('--skip-harden')} Skip OS hardening (swap, UFW, fail2ban, SSH)
|
|
115
|
+
${c('--dev')} Allow private/non-routable IP addresses
|
|
116
|
+
${c('--json')} Output NDJSON progress lines instead of terminal UI
|
|
117
|
+
${c('--force-full')} Run full installation even on existing installs
|
|
118
|
+
${c('--uninstall')} Show manual removal guide for Lamaste
|
|
119
|
+
`);
|
|
120
|
+
process.exit(0);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Print detailed uninstall guide listing all components installed by Lamaste,
|
|
125
|
+
* then exit with code 0.
|
|
126
|
+
*/
|
|
127
|
+
function printUninstallGuide() {
|
|
128
|
+
const b = chalk.bold;
|
|
129
|
+
const c = chalk.cyan;
|
|
130
|
+
const y = chalk.yellow;
|
|
131
|
+
const d = chalk.dim;
|
|
132
|
+
|
|
133
|
+
console.log(`
|
|
134
|
+
${b('Lamaste — Manual Removal Guide')}
|
|
135
|
+
|
|
136
|
+
${y('Automated removal: sudo lamaste-server uninstall')}
|
|
137
|
+
${y('Or follow the steps below to remove Lamaste by hand.')}
|
|
138
|
+
|
|
139
|
+
${b('1. Stop and disable services')}
|
|
140
|
+
|
|
141
|
+
${c('sudo systemctl disable --now lamalibre-lamaste-serverd lamalibre-lamaste-gatekeeper chisel authelia')}
|
|
142
|
+
${c('sudo rm /etc/systemd/system/{lamalibre-lamaste-serverd,lamalibre-lamaste-gatekeeper,chisel,authelia}.service')}
|
|
143
|
+
${c('sudo systemctl daemon-reload')}
|
|
144
|
+
|
|
145
|
+
${b('2. Remove nginx configuration')}
|
|
146
|
+
|
|
147
|
+
${c('sudo rm /etc/nginx/sites-enabled/lamalibre-lamaste-*')}
|
|
148
|
+
${c('sudo rm /etc/nginx/sites-available/lamalibre-lamaste-*')}
|
|
149
|
+
${c('sudo rm /etc/nginx/snippets/lamalibre-lamaste-*')}
|
|
150
|
+
${c('sudo nginx -t && sudo systemctl reload nginx')}
|
|
151
|
+
|
|
152
|
+
${b('3. Remove Lamaste directories and binaries')}
|
|
153
|
+
|
|
154
|
+
${c('sudo rm -rf /etc/lamalibre/lamaste/')} ${d('# Configuration, PKI certificates, state')}
|
|
155
|
+
${c('sudo rm -rf /opt/lamalibre/lamaste/')} ${d('# Panel server and client files')}
|
|
156
|
+
${c('sudo rm -rf /var/www/lamaste/')} ${d('# Static site files')}
|
|
157
|
+
${c('sudo rm -rf /etc/authelia/ /var/log/authelia/')} ${d('# Authelia configuration, database, log')}
|
|
158
|
+
${c('sudo rm /usr/local/bin/chisel /usr/local/bin/authelia')}
|
|
159
|
+
${c('sudo rm /usr/local/sbin/lamaste-priv /usr/local/sbin/lamaste-certbot /usr/local/sbin/lamaste-cert-info')}
|
|
160
|
+
|
|
161
|
+
${b('4. Remove service accounts')}
|
|
162
|
+
|
|
163
|
+
${c('sudo userdel -r lamaste')}
|
|
164
|
+
${c('sudo userdel lamaste-authelia')}
|
|
165
|
+
${c('sudo groupdel lamaste-chisel')}
|
|
166
|
+
|
|
167
|
+
${b('5. Remove sudoers rules')}
|
|
168
|
+
|
|
169
|
+
${c('sudo rm /etc/sudoers.d/lamaste')}
|
|
170
|
+
|
|
171
|
+
${b('6. Remove fail2ban config (optional)')}
|
|
172
|
+
|
|
173
|
+
${c('sudo rm /etc/fail2ban/jail.d/lamaste.conf')}
|
|
174
|
+
${c('sudo systemctl restart fail2ban')}
|
|
175
|
+
|
|
176
|
+
${b('7. Revert SSH hardening (optional)')}
|
|
177
|
+
|
|
178
|
+
${d('If a backup was created during install:')}
|
|
179
|
+
${c('sudo cp /etc/ssh/sshd_config.pre-lamaste /etc/ssh/sshd_config')}
|
|
180
|
+
${c('sudo sshd -t && sudo systemctl restart ssh')}
|
|
181
|
+
|
|
182
|
+
${b('8. Revert firewall changes (optional)')}
|
|
183
|
+
|
|
184
|
+
${d('Remove Lamaste-specific UFW rules:')}
|
|
185
|
+
${c('sudo ufw delete allow 9292/tcp')}
|
|
186
|
+
|
|
187
|
+
${b('9. Remove swap file (optional)')}
|
|
188
|
+
|
|
189
|
+
${d('Only if Lamaste created it:')}
|
|
190
|
+
${c('sudo swapoff /swapfile')}
|
|
191
|
+
${c('sudo rm /swapfile')}
|
|
192
|
+
${d('Remove the /swapfile line from /etc/fstab')}
|
|
193
|
+
|
|
194
|
+
${b("10. Remove Let's Encrypt certificates (optional)")}
|
|
195
|
+
|
|
196
|
+
${d('List Lamaste-issued certs:')}
|
|
197
|
+
${c('sudo certbot certificates')}
|
|
198
|
+
${d('Delete specific ones:')}
|
|
199
|
+
${c('sudo certbot delete --cert-name <domain>')}
|
|
200
|
+
|
|
201
|
+
${d('Note: Steps 6-10 are optional. They revert OS hardening changes that')}
|
|
202
|
+
${d('may be useful to keep even after removing Lamaste.')}
|
|
203
|
+
`);
|
|
204
|
+
process.exit(0);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Detect existing system state to surface warnings before installation.
|
|
209
|
+
* All checks are wrapped in try/catch — detection failures never block the installer.
|
|
210
|
+
* @returns {Promise<{
|
|
211
|
+
* lamasteExists: boolean,
|
|
212
|
+
* onboardingStatus: string | null,
|
|
213
|
+
* existingNginxSites: string[],
|
|
214
|
+
* port3100InUse: boolean,
|
|
215
|
+
* ufwActive: boolean,
|
|
216
|
+
* ufwRuleCount: number,
|
|
217
|
+
* }>}
|
|
218
|
+
*/
|
|
219
|
+
async function detectExistingState() {
|
|
220
|
+
const state = {
|
|
221
|
+
lamasteExists: false,
|
|
222
|
+
onboardingStatus: null,
|
|
223
|
+
existingNginxSites: [],
|
|
224
|
+
port3100InUse: false,
|
|
225
|
+
ufwActive: false,
|
|
226
|
+
ufwRuleCount: 0,
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
// 1. Check for existing Lamaste installation
|
|
230
|
+
try {
|
|
231
|
+
if (existsSync('/etc/lamalibre/lamaste/panel.json')) {
|
|
232
|
+
state.lamasteExists = true;
|
|
233
|
+
const raw = await readFile('/etc/lamalibre/lamaste/panel.json', 'utf8');
|
|
234
|
+
const config = JSON.parse(raw);
|
|
235
|
+
state.onboardingStatus = config.onboardingStatus || 'FRESH';
|
|
236
|
+
}
|
|
237
|
+
} catch {
|
|
238
|
+
// If panel.json exists but is unreadable/invalid, still flag it
|
|
239
|
+
if (existsSync('/etc/lamalibre/lamaste/panel.json')) {
|
|
240
|
+
state.lamasteExists = true;
|
|
241
|
+
state.onboardingStatus = 'UNKNOWN';
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// 2. Check for existing nginx sites (non-lamaste, non-default)
|
|
246
|
+
try {
|
|
247
|
+
const entries = await readdir('/etc/nginx/sites-enabled');
|
|
248
|
+
state.existingNginxSites = entries.filter(
|
|
249
|
+
(name) => !name.startsWith('lamalibre-lamaste-') && name !== 'default',
|
|
250
|
+
);
|
|
251
|
+
} catch {
|
|
252
|
+
// nginx not installed or sites-enabled doesn't exist — nothing to report
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// 3. Check if port 3100 is in use
|
|
256
|
+
try {
|
|
257
|
+
const { stdout } = await execa('ss', ['-tlnp', 'sport', '=', ':3100']);
|
|
258
|
+
// ss always prints a header line; if there are more lines, the port is in use
|
|
259
|
+
const lines = stdout.trim().split('\n');
|
|
260
|
+
state.port3100InUse = lines.length > 1;
|
|
261
|
+
} catch {
|
|
262
|
+
// ss not available or command failed — assume port is free
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// 4. Check UFW status
|
|
266
|
+
try {
|
|
267
|
+
const { stdout } = await execa('ufw', ['status']);
|
|
268
|
+
state.ufwActive = stdout.includes('Status: active');
|
|
269
|
+
if (state.ufwActive) {
|
|
270
|
+
// Count rule lines: each rule line starts with a port number or an action keyword
|
|
271
|
+
// Skip the header lines (Status, blank lines, header dividers)
|
|
272
|
+
const lines = stdout.split('\n');
|
|
273
|
+
let ruleCount = 0;
|
|
274
|
+
let pastHeader = false;
|
|
275
|
+
for (const line of lines) {
|
|
276
|
+
if (line.startsWith('--')) {
|
|
277
|
+
pastHeader = true;
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
if (pastHeader && line.trim().length > 0) {
|
|
281
|
+
ruleCount++;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
state.ufwRuleCount = ruleCount;
|
|
285
|
+
}
|
|
286
|
+
} catch {
|
|
287
|
+
// ufw not installed — nothing to report
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return state;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Print a confirmation banner and optionally wait for user input.
|
|
295
|
+
* @param {{ yes: boolean }} flags - Parsed CLI flags.
|
|
296
|
+
* @param {boolean} isRedeploy - Whether we are in redeploy mode.
|
|
297
|
+
* @param {{ lamasteExists: boolean, onboardingStatus: string | null, existingNginxSites: string[], port3100InUse: boolean, ufwActive: boolean, ufwRuleCount: number }} existingState - Detection results.
|
|
298
|
+
*/
|
|
299
|
+
async function confirmInstallation(flags, isRedeploy, existingState) {
|
|
300
|
+
let banner;
|
|
301
|
+
|
|
302
|
+
if (isRedeploy) {
|
|
303
|
+
banner = `
|
|
304
|
+
${chalk.cyan.bold('┌─────────────────────────────────────────────────────────────┐')}
|
|
305
|
+
${chalk.cyan.bold('│')} ${chalk.white.bold('Lamaste Panel Update')} ${chalk.cyan.bold('│')}
|
|
306
|
+
${chalk.cyan.bold('├─────────────────────────────────────────────────────────────┤')}
|
|
307
|
+
${chalk.cyan.bold('│')} ${chalk.cyan.bold('│')}
|
|
308
|
+
${chalk.cyan.bold('│')} Existing installation detected. Updating panel only. ${chalk.cyan.bold('│')}
|
|
309
|
+
${chalk.cyan.bold('│')} ${chalk.cyan.bold('│')}
|
|
310
|
+
${chalk.cyan.bold('│')} The following changes will be made: ${chalk.cyan.bold('│')}
|
|
311
|
+
${chalk.cyan.bold('│')} ${chalk.cyan.bold('│')}
|
|
312
|
+
${chalk.cyan.bold('│')} ${chalk.yellow('•')} Stop panel service ${chalk.cyan.bold('│')}
|
|
313
|
+
${chalk.cyan.bold('│')} ${chalk.yellow('•')} Update serverd and server-ui files ${chalk.cyan.bold('│')}
|
|
314
|
+
${chalk.cyan.bold('│')} ${chalk.yellow('•')} Install updated dependencies ${chalk.cyan.bold('│')}
|
|
315
|
+
${chalk.cyan.bold('│')} ${chalk.yellow('•')} Restart panel service ${chalk.cyan.bold('│')}
|
|
316
|
+
${chalk.cyan.bold('│')} ${chalk.cyan.bold('│')}
|
|
317
|
+
${chalk.cyan.bold('│')} ${chalk.dim('OS, nginx, mTLS certs, and firewall are untouched.')} ${chalk.cyan.bold('│')}
|
|
318
|
+
${chalk.cyan.bold('│')} ${chalk.dim('Use --force-full to run the complete installer.')} ${chalk.cyan.bold('│')}
|
|
319
|
+
${chalk.cyan.bold('│')} ${chalk.cyan.bold('│')}
|
|
320
|
+
${chalk.cyan.bold('└─────────────────────────────────────────────────────────────┘')}`;
|
|
321
|
+
} else {
|
|
322
|
+
banner = `
|
|
323
|
+
${chalk.cyan.bold('┌─────────────────────────────────────────────────────────────┐')}
|
|
324
|
+
${chalk.cyan.bold('│')} ${chalk.white.bold('Lamaste Installer')} ${chalk.cyan.bold('│')}
|
|
325
|
+
${chalk.cyan.bold('├─────────────────────────────────────────────────────────────┤')}
|
|
326
|
+
${chalk.cyan.bold('│')} ${chalk.cyan.bold('│')}
|
|
327
|
+
${chalk.cyan.bold('│')} This will install Lamaste on this machine. ${chalk.cyan.bold('│')}
|
|
328
|
+
${chalk.cyan.bold('│')} ${chalk.cyan.bold('│')}
|
|
329
|
+
${chalk.cyan.bold('│')} The following changes will be made: ${chalk.cyan.bold('│')}
|
|
330
|
+
${chalk.cyan.bold('│')} ${chalk.cyan.bold('│')}
|
|
331
|
+
${chalk.cyan.bold('│')} ${chalk.yellow('•')} Reset UFW firewall (allow ports 22, 80, 443, 9292) ${chalk.cyan.bold('│')}
|
|
332
|
+
${chalk.cyan.bold('│')} ${chalk.yellow('•')} Harden SSH (disable password authentication) ${chalk.cyan.bold('│')}
|
|
333
|
+
${chalk.cyan.bold('│')} ${chalk.yellow('•')} Install fail2ban, Node.js 20, nginx, certbot ${chalk.cyan.bold('│')}
|
|
334
|
+
${chalk.cyan.bold('│')} ${chalk.yellow('•')} Generate mTLS certificates for browser access ${chalk.cyan.bold('│')}
|
|
335
|
+
${chalk.cyan.bold('│')} ${chalk.yellow('•')} Create lamaste user and systemd service ${chalk.cyan.bold('│')}
|
|
336
|
+
${chalk.cyan.bold('│')} ${chalk.yellow('•')} Deploy panel to /opt/lamalibre/lamaste/ ${chalk.cyan.bold('│')}
|
|
337
|
+
${chalk.cyan.bold('│')} ${chalk.cyan.bold('│')}
|
|
338
|
+
${chalk.cyan.bold('│')} ${chalk.dim('Designed for a fresh Ubuntu 24.04 droplet.')} ${chalk.cyan.bold('│')}
|
|
339
|
+
${chalk.cyan.bold('│')} ${chalk.cyan.bold('│')}
|
|
340
|
+
${chalk.cyan.bold('└─────────────────────────────────────────────────────────────┘')}`;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
console.log(banner);
|
|
344
|
+
|
|
345
|
+
// Display detection warnings below the banner (only for full install)
|
|
346
|
+
if (!isRedeploy) {
|
|
347
|
+
const warnings = [];
|
|
348
|
+
|
|
349
|
+
if (existingState.lamasteExists) {
|
|
350
|
+
const status = existingState.onboardingStatus || 'UNKNOWN';
|
|
351
|
+
warnings.push(
|
|
352
|
+
`An existing Lamaste installation was detected (onboarding: ${status}). Re-running will update the installation but preserve your configuration.`,
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if (existingState.existingNginxSites.length > 0) {
|
|
357
|
+
warnings.push(
|
|
358
|
+
`Existing nginx sites will be affected: ${existingState.existingNginxSites.join(', ')}`,
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
if (existingState.port3100InUse) {
|
|
363
|
+
warnings.push('Port 3100 is currently in use. The panel may fail to start.');
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
if (existingState.ufwActive && existingState.ufwRuleCount > 0) {
|
|
367
|
+
warnings.push(
|
|
368
|
+
`Existing UFW firewall rules (${existingState.ufwRuleCount} rules) will be reset.`,
|
|
369
|
+
);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
if (warnings.length > 0) {
|
|
373
|
+
console.log('');
|
|
374
|
+
for (const warning of warnings) {
|
|
375
|
+
console.log(` ${chalk.yellow('!')} ${chalk.yellow(warning)}`);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
if (flags.yes) {
|
|
381
|
+
console.log(`\n ${chalk.dim('Skipping confirmation (--yes)')}\n`);
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const rl = createInterface({
|
|
386
|
+
input: process.stdin,
|
|
387
|
+
output: process.stdout,
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
await new Promise((resolve) => {
|
|
391
|
+
rl.question(`\n ${chalk.white.bold('Press Enter to continue or Ctrl+C to abort...')} `, () => {
|
|
392
|
+
rl.close();
|
|
393
|
+
resolve();
|
|
394
|
+
});
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Run a single Listr task step in JSON mode: emit running/complete/skipped
|
|
400
|
+
* NDJSON events around each step so the caller gets per-step progress.
|
|
401
|
+
* @param {Record<string, unknown>} ctx - Shared installer context.
|
|
402
|
+
* @param {{ key: string, title: string, fn: Function, skip?: () => boolean }} step
|
|
403
|
+
*/
|
|
404
|
+
async function runJsonStep(ctx, step) {
|
|
405
|
+
if (step.skip && step.skip()) {
|
|
406
|
+
emitJson({ event: 'step', step: step.key, status: 'skipped' });
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
emitJson({ event: 'step', step: step.key, status: 'running' });
|
|
410
|
+
const taskList = new Listr([{ title: step.title, task: (_c, t) => step.fn(ctx, t) }], {
|
|
411
|
+
renderer: 'silent',
|
|
412
|
+
exitOnError: true,
|
|
413
|
+
});
|
|
414
|
+
try {
|
|
415
|
+
await taskList.run();
|
|
416
|
+
} catch (error) {
|
|
417
|
+
emitJson({ event: 'step', step: step.key, status: 'failed' });
|
|
418
|
+
throw error;
|
|
419
|
+
}
|
|
420
|
+
emitJson({ event: 'step', step: step.key, status: 'complete' });
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Main installer orchestrator. Creates a shared context, runs all installation
|
|
425
|
+
* tasks through Listr2, and prints a summary on completion.
|
|
426
|
+
*
|
|
427
|
+
* When --json is active, outputs NDJSON progress lines instead of the
|
|
428
|
+
* interactive Listr2 terminal UI. The --json flag implies --yes and --dev.
|
|
429
|
+
*/
|
|
430
|
+
export async function main() {
|
|
431
|
+
const flags = parseFlags();
|
|
432
|
+
|
|
433
|
+
if (flags.help) {
|
|
434
|
+
printHelp();
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
if (flags.uninstall) {
|
|
438
|
+
printUninstallGuide();
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// --json implies --yes (no interactive prompts) and --dev (accept private IPs)
|
|
442
|
+
if (flags.json) {
|
|
443
|
+
flags.yes = true;
|
|
444
|
+
flags.dev = true;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
const renderer = flags.json ? 'silent' : 'default';
|
|
448
|
+
|
|
449
|
+
const ctx = {
|
|
450
|
+
ip: null,
|
|
451
|
+
osRelease: null,
|
|
452
|
+
skipHarden: flags.skipHarden,
|
|
453
|
+
nodeAlreadyInstalled: false,
|
|
454
|
+
nodeVersion: null,
|
|
455
|
+
npmVersion: null,
|
|
456
|
+
p12Password: null,
|
|
457
|
+
pkiDir: '/etc/lamalibre/lamaste/pki',
|
|
458
|
+
configDir: '/etc/lamalibre/lamaste',
|
|
459
|
+
installDir: '/opt/lamalibre/lamaste',
|
|
460
|
+
};
|
|
461
|
+
|
|
462
|
+
// Phase 1: Environment checks
|
|
463
|
+
const envTasks = new Listr(
|
|
464
|
+
[
|
|
465
|
+
{
|
|
466
|
+
title: 'Checking environment',
|
|
467
|
+
task: async (_ctx, task) => {
|
|
468
|
+
return task.newListr([
|
|
469
|
+
{
|
|
470
|
+
title: 'Verifying root access',
|
|
471
|
+
task: async () => {
|
|
472
|
+
checkRoot();
|
|
473
|
+
},
|
|
474
|
+
},
|
|
475
|
+
{
|
|
476
|
+
title: 'Detecting operating system',
|
|
477
|
+
task: async (_ctx, subtask) => {
|
|
478
|
+
ctx.osRelease = await detectOS();
|
|
479
|
+
subtask.output = ctx.osRelease.prettyName;
|
|
480
|
+
},
|
|
481
|
+
rendererOptions: { persistentOutput: true },
|
|
482
|
+
},
|
|
483
|
+
{
|
|
484
|
+
title: 'Detecting IP address',
|
|
485
|
+
task: async (_ctx, subtask) => {
|
|
486
|
+
ctx.ip = await detectIP({ allowPrivate: flags.dev });
|
|
487
|
+
if (flags.dev) {
|
|
488
|
+
subtask.output = `${ctx.ip} (dev mode — private IP accepted)`;
|
|
489
|
+
} else {
|
|
490
|
+
subtask.output = ctx.ip;
|
|
491
|
+
}
|
|
492
|
+
},
|
|
493
|
+
rendererOptions: { persistentOutput: true },
|
|
494
|
+
},
|
|
495
|
+
]);
|
|
496
|
+
},
|
|
497
|
+
},
|
|
498
|
+
],
|
|
499
|
+
{
|
|
500
|
+
renderer,
|
|
501
|
+
rendererOptions: { collapseSubtasks: false },
|
|
502
|
+
exitOnError: true,
|
|
503
|
+
},
|
|
504
|
+
);
|
|
505
|
+
|
|
506
|
+
try {
|
|
507
|
+
// Run environment checks first
|
|
508
|
+
if (flags.json) {
|
|
509
|
+
emitJson({ event: 'step', step: 'check_environment', status: 'running' });
|
|
510
|
+
}
|
|
511
|
+
await envTasks.run();
|
|
512
|
+
if (flags.json) {
|
|
513
|
+
emitJson({ event: 'step', step: 'check_environment', status: 'complete' });
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// Detect existing system state for the confirmation banner
|
|
517
|
+
const existingState = await detectExistingState();
|
|
518
|
+
|
|
519
|
+
// Determine mode: redeploy (fast update) or full install
|
|
520
|
+
const isRedeploy = existingState.lamasteExists && !flags.forceFull;
|
|
521
|
+
|
|
522
|
+
// Show confirmation banner and wait for user input (skipped in JSON mode)
|
|
523
|
+
if (!flags.json) {
|
|
524
|
+
await confirmInstallation(flags, isRedeploy, existingState);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
if (isRedeploy) {
|
|
528
|
+
if (flags.json) {
|
|
529
|
+
// JSON mode: emit step-level progress for redeploy
|
|
530
|
+
await runJsonStep(ctx, {
|
|
531
|
+
key: 'redeploy_panel',
|
|
532
|
+
title: 'Redeploying Lamaste panel',
|
|
533
|
+
fn: redeployTasks,
|
|
534
|
+
});
|
|
535
|
+
} else {
|
|
536
|
+
// Fast path: only update panel files and restart
|
|
537
|
+
const redeployTaskList = new Listr(
|
|
538
|
+
[
|
|
539
|
+
{
|
|
540
|
+
title: 'Redeploying Lamaste panel',
|
|
541
|
+
task: (_ctx, task) => redeployTasks(ctx, task),
|
|
542
|
+
},
|
|
543
|
+
],
|
|
544
|
+
{
|
|
545
|
+
renderer,
|
|
546
|
+
rendererOptions: { collapseSubtasks: false },
|
|
547
|
+
exitOnError: true,
|
|
548
|
+
},
|
|
549
|
+
);
|
|
550
|
+
await redeployTaskList.run();
|
|
551
|
+
}
|
|
552
|
+
} else if (flags.json) {
|
|
553
|
+
// JSON mode: run each install step individually with NDJSON progress
|
|
554
|
+
const installSteps = [
|
|
555
|
+
// Not skipped as a whole with --skip-harden: the group also installs
|
|
556
|
+
// nginx, certbot and openssl. Its hardening subtasks (UFW, fail2ban,
|
|
557
|
+
// SSH) skip themselves — the same as the interactive install.
|
|
558
|
+
{ key: 'harden_system', title: 'Hardening operating system', fn: hardenTasks },
|
|
559
|
+
{ key: 'install_node', title: 'Installing Node.js 20', fn: nodeTasks },
|
|
560
|
+
{ key: 'generate_certs', title: 'Generating mTLS certificates', fn: mtlsTasks },
|
|
561
|
+
{ key: 'configure_nginx', title: 'Configuring nginx', fn: nginxTasks },
|
|
562
|
+
{ key: 'deploy_panel', title: 'Deploying Lamaste panel', fn: panelTasks },
|
|
563
|
+
{ key: 'deploy_gatekeeper', title: 'Deploying Gatekeeper', fn: gatekeeperTasks },
|
|
564
|
+
];
|
|
565
|
+
for (const step of installSteps) {
|
|
566
|
+
await runJsonStep(ctx, step);
|
|
567
|
+
}
|
|
568
|
+
} else {
|
|
569
|
+
// Full install path with interactive Listr2 rendering
|
|
570
|
+
const installTasks = new Listr(
|
|
571
|
+
[
|
|
572
|
+
{
|
|
573
|
+
title: 'Hardening operating system',
|
|
574
|
+
task: (_ctx, task) => hardenTasks(ctx, task),
|
|
575
|
+
},
|
|
576
|
+
{
|
|
577
|
+
title: 'Installing Node.js 20',
|
|
578
|
+
task: (_ctx, task) => nodeTasks(ctx, task),
|
|
579
|
+
},
|
|
580
|
+
{
|
|
581
|
+
title: 'Generating mTLS certificates',
|
|
582
|
+
task: (_ctx, task) => mtlsTasks(ctx, task),
|
|
583
|
+
},
|
|
584
|
+
{
|
|
585
|
+
title: 'Configuring nginx',
|
|
586
|
+
task: (_ctx, task) => nginxTasks(ctx, task),
|
|
587
|
+
},
|
|
588
|
+
{
|
|
589
|
+
title: 'Deploying Lamaste panel',
|
|
590
|
+
task: (_ctx, task) => panelTasks(ctx, task),
|
|
591
|
+
},
|
|
592
|
+
{
|
|
593
|
+
title: 'Deploying Gatekeeper',
|
|
594
|
+
task: (_ctx, task) => gatekeeperTasks(ctx, task),
|
|
595
|
+
},
|
|
596
|
+
{
|
|
597
|
+
title: 'Installation complete',
|
|
598
|
+
task: async () => {
|
|
599
|
+
// Summary will be printed after Listr finishes
|
|
600
|
+
},
|
|
601
|
+
},
|
|
602
|
+
],
|
|
603
|
+
{
|
|
604
|
+
renderer,
|
|
605
|
+
rendererOptions: { collapseSubtasks: false },
|
|
606
|
+
exitOnError: true,
|
|
607
|
+
},
|
|
608
|
+
);
|
|
609
|
+
await installTasks.run();
|
|
610
|
+
}
|
|
611
|
+
} catch (error) {
|
|
612
|
+
if (flags.json) {
|
|
613
|
+
emitJson({
|
|
614
|
+
event: 'error',
|
|
615
|
+
message: error.message || 'Unknown error',
|
|
616
|
+
recoverable: false,
|
|
617
|
+
});
|
|
618
|
+
process.exit(1);
|
|
619
|
+
}
|
|
620
|
+
console.error('\n');
|
|
621
|
+
console.error(' ┌─────────────────────────────────────────────┐');
|
|
622
|
+
console.error(' │ Lamaste installation failed. │');
|
|
623
|
+
console.error(` │ ${(error.message || 'Unknown error').slice(0, 43).padEnd(43)} │`);
|
|
624
|
+
console.error(' │ │');
|
|
625
|
+
console.error(' │ You can safely re-run this installer │');
|
|
626
|
+
console.error(' │ to retry. │');
|
|
627
|
+
console.error(' └─────────────────────────────────────────────┘');
|
|
628
|
+
console.error('\n');
|
|
629
|
+
process.exit(1);
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
if (flags.json) {
|
|
633
|
+
emitJson({
|
|
634
|
+
event: 'complete',
|
|
635
|
+
server: {
|
|
636
|
+
ip: ctx.ip,
|
|
637
|
+
panelUrl: `https://${ctx.ip}:9292`,
|
|
638
|
+
p12Path: `${ctx.pkiDir}/client.p12`,
|
|
639
|
+
p12PasswordPath: `${ctx.pkiDir}/.p12-password`,
|
|
640
|
+
},
|
|
641
|
+
});
|
|
642
|
+
} else {
|
|
643
|
+
// Print summary after Listr2 finishes rendering
|
|
644
|
+
await printSummary(ctx);
|
|
645
|
+
}
|
|
646
|
+
}
|