@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
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lamaste-server reset-admin — Emergency recovery tool for admin authentication.
|
|
3
|
+
*
|
|
4
|
+
* Requires root access on the server (run via DigitalOcean console).
|
|
5
|
+
* Reverts admin auth from hardware-bound back to P12:
|
|
6
|
+
*
|
|
7
|
+
* 1. Generate new admin keypair + CSR + sign + P12
|
|
8
|
+
* 2. Revoke old hardware-bound admin cert
|
|
9
|
+
* 3. Set adminAuthMode: 'p12' in panel.json
|
|
10
|
+
* 4. Clear 2FA if enabled
|
|
11
|
+
* 5. Re-enable IP:9292 vhost
|
|
12
|
+
* 6. Restart serverd + reload nginx
|
|
13
|
+
* 7. Print new P12 password
|
|
14
|
+
*
|
|
15
|
+
* The PKI directory and panel.json belong to the lamaste user — and the
|
|
16
|
+
* panel, if compromised, could have left symlinks there for root to write
|
|
17
|
+
* through. So every file operation inside them runs as lamaste (openssl, cp,
|
|
18
|
+
* chmod) or creates a fresh file (O_EXCL | O_NOFOLLOW) that is renamed into
|
|
19
|
+
* place; root never writes through an existing path there.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import crypto from 'node:crypto';
|
|
23
|
+
import { readFile, rename, access, constants, unlink, open, rm } from 'node:fs/promises';
|
|
24
|
+
import { basename, dirname, join } from 'node:path';
|
|
25
|
+
import chalk from 'chalk';
|
|
26
|
+
import { execa } from 'execa';
|
|
27
|
+
import { CONFIG_PATH, PKI_DIR, PANEL_SERVICE, STATE_DIR } from '../config.js';
|
|
28
|
+
import { emitStep, emitError, emitComplete } from '../ndjson.js';
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Atomically write the P12 password to a file (temp -> fsync -> chmod -> rename).
|
|
32
|
+
* @param {string} destPath
|
|
33
|
+
* @param {string} password
|
|
34
|
+
*/
|
|
35
|
+
async function atomicWritePassword(destPath, password) {
|
|
36
|
+
const tmpPath = `${destPath}.tmp`;
|
|
37
|
+
// Best-effort cleanup of a stale temp from a prior crashed run so 'wx' below
|
|
38
|
+
// does not spuriously fail.
|
|
39
|
+
await unlink(tmpPath).catch(() => {});
|
|
40
|
+
// Use 'wx' to fail if a stale temp file exists (avoids races).
|
|
41
|
+
const fh = await open(tmpPath, 'wx', 0o600);
|
|
42
|
+
try {
|
|
43
|
+
await fh.writeFile(password, 'utf-8');
|
|
44
|
+
await fh.sync();
|
|
45
|
+
} finally {
|
|
46
|
+
await fh.close();
|
|
47
|
+
}
|
|
48
|
+
await rename(tmpPath, destPath);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* uid/gid of the lamaste user, as execa options: commands run with them act
|
|
53
|
+
* inside the lamaste-owned PKI directory with lamaste's rights only.
|
|
54
|
+
*
|
|
55
|
+
* @returns {Promise<{ uid: number, gid: number }>}
|
|
56
|
+
*/
|
|
57
|
+
async function lamasteIdentity() {
|
|
58
|
+
const uid = Number.parseInt((await execa('id', ['-u', 'lamaste'])).stdout.trim(), 10);
|
|
59
|
+
const gid = Number.parseInt((await execa('id', ['-g', 'lamaste'])).stdout.trim(), 10);
|
|
60
|
+
if (!Number.isInteger(uid) || !Number.isInteger(gid)) {
|
|
61
|
+
throw new Error('Cannot resolve the lamaste user');
|
|
62
|
+
}
|
|
63
|
+
return { uid, gid };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Write `content` to `destPath` (in a lamaste-owned directory) as a fresh
|
|
68
|
+
* file owned by `owner`, renamed into place.
|
|
69
|
+
*
|
|
70
|
+
* @param {string} destPath
|
|
71
|
+
* @param {string} content
|
|
72
|
+
* @param {{ uid: number, gid: number }} owner
|
|
73
|
+
* @param {number} mode
|
|
74
|
+
*/
|
|
75
|
+
async function writeOwnedFile(destPath, content, owner, mode) {
|
|
76
|
+
const tmp = join(
|
|
77
|
+
dirname(destPath),
|
|
78
|
+
`.${basename(destPath)}.${crypto.randomBytes(8).toString('hex')}`,
|
|
79
|
+
);
|
|
80
|
+
const fh = await open(
|
|
81
|
+
tmp,
|
|
82
|
+
constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW,
|
|
83
|
+
0o600,
|
|
84
|
+
);
|
|
85
|
+
try {
|
|
86
|
+
await fh.writeFile(content, 'utf-8');
|
|
87
|
+
await fh.chown(owner.uid, owner.gid);
|
|
88
|
+
await fh.chmod(mode);
|
|
89
|
+
await fh.sync();
|
|
90
|
+
} catch (err) {
|
|
91
|
+
await fh.close();
|
|
92
|
+
await rm(tmp, { force: true });
|
|
93
|
+
throw err;
|
|
94
|
+
}
|
|
95
|
+
await fh.close();
|
|
96
|
+
await rename(tmp, destPath);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Add a certificate serial to the panel's revocation list (`revoked_certs`
|
|
101
|
+
* in `state.db`). Runs as lamaste: the database is in WAL mode, and a write
|
|
102
|
+
* as root could leave root-owned `-wal`/`-shm` files the panel cannot open.
|
|
103
|
+
* The serial and label reach the child through its environment, never
|
|
104
|
+
* through the code it runs.
|
|
105
|
+
*
|
|
106
|
+
* @param {string} serial
|
|
107
|
+
* @param {string} label
|
|
108
|
+
* @param {{ uid: number, gid: number }} lamaste
|
|
109
|
+
*/
|
|
110
|
+
async function revokeInStateDb(serial, label, lamaste) {
|
|
111
|
+
const script = `
|
|
112
|
+
import { DatabaseSync } from 'node:sqlite';
|
|
113
|
+
const db = new DatabaseSync(process.env.STATE_DB);
|
|
114
|
+
db.exec('PRAGMA busy_timeout = 5000');
|
|
115
|
+
db.exec('BEGIN IMMEDIATE');
|
|
116
|
+
try {
|
|
117
|
+
const seen = db.prepare('SELECT serial FROM revoked_certs WHERE serial = ?').get(process.env.SERIAL);
|
|
118
|
+
if (!seen) {
|
|
119
|
+
db.prepare('INSERT INTO revoked_certs (serial, label, revoked_at) VALUES (?, ?, ?)')
|
|
120
|
+
.run(process.env.SERIAL, process.env.LABEL, new Date().toISOString());
|
|
121
|
+
}
|
|
122
|
+
db.exec('COMMIT');
|
|
123
|
+
} catch (err) {
|
|
124
|
+
db.exec('ROLLBACK');
|
|
125
|
+
throw err;
|
|
126
|
+
}
|
|
127
|
+
db.close();
|
|
128
|
+
`;
|
|
129
|
+
await execa(process.execPath, ['--no-warnings', '--input-type=module', '-e', script], {
|
|
130
|
+
...lamaste,
|
|
131
|
+
cwd: STATE_DIR,
|
|
132
|
+
env: { STATE_DB: `${STATE_DIR}/state.db`, SERIAL: serial, LABEL: label },
|
|
133
|
+
extendEnv: false,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* @param {{ json: boolean, passwordFile?: string }} options
|
|
139
|
+
*/
|
|
140
|
+
export async function runResetAdmin({ json, passwordFile }) {
|
|
141
|
+
// Verify running as root
|
|
142
|
+
if (process.getuid && process.getuid() !== 0) {
|
|
143
|
+
const msg = 'lamaste-server reset-admin must be run as root.';
|
|
144
|
+
if (json) {
|
|
145
|
+
emitError(msg);
|
|
146
|
+
} else {
|
|
147
|
+
console.error(`\n Error: ${msg}`);
|
|
148
|
+
console.error(` Usage: sudo lamaste-server reset-admin\n`);
|
|
149
|
+
}
|
|
150
|
+
process.exit(1);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (!json) {
|
|
154
|
+
console.log('');
|
|
155
|
+
console.log(' Lamaste Admin Reset');
|
|
156
|
+
console.log(' Reverting admin authentication to P12...');
|
|
157
|
+
console.log('');
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// 1. Read current config
|
|
161
|
+
if (json) emitStep('read-config', 'running');
|
|
162
|
+
let config;
|
|
163
|
+
try {
|
|
164
|
+
const raw = await readFile(CONFIG_PATH, 'utf-8');
|
|
165
|
+
config = JSON.parse(raw);
|
|
166
|
+
if (json) emitStep('read-config', 'complete');
|
|
167
|
+
} catch (err) {
|
|
168
|
+
const msg = `Cannot read config at ${CONFIG_PATH}: ${err.message}`;
|
|
169
|
+
if (json) {
|
|
170
|
+
emitStep('read-config', 'failed', msg);
|
|
171
|
+
emitError(msg);
|
|
172
|
+
} else {
|
|
173
|
+
console.error(` Error: ${msg}`);
|
|
174
|
+
}
|
|
175
|
+
process.exit(1);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const lamaste = await lamasteIdentity();
|
|
179
|
+
|
|
180
|
+
// 2. Verify CA exists
|
|
181
|
+
try {
|
|
182
|
+
await access(`${PKI_DIR}/ca.key`, constants.R_OK);
|
|
183
|
+
await access(`${PKI_DIR}/ca.crt`, constants.R_OK);
|
|
184
|
+
} catch {
|
|
185
|
+
const msg = `CA key/cert not found in ${PKI_DIR}`;
|
|
186
|
+
if (json) emitError(msg);
|
|
187
|
+
else console.error(` Error: ${msg}`);
|
|
188
|
+
process.exit(1);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// 3. Read old admin cert serial for revocation
|
|
192
|
+
if (json) emitStep('generate-cert', 'running');
|
|
193
|
+
let oldSerial = '';
|
|
194
|
+
try {
|
|
195
|
+
const { stdout } = await execa(
|
|
196
|
+
'openssl',
|
|
197
|
+
['x509', '-in', `${PKI_DIR}/client.crt`, '-serial', '-noout'],
|
|
198
|
+
lamaste,
|
|
199
|
+
);
|
|
200
|
+
const match = stdout.match(/serial=([A-Fa-f0-9]+)/);
|
|
201
|
+
oldSerial = match ? match[1] : '';
|
|
202
|
+
} catch {
|
|
203
|
+
// Old cert may not exist
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// 4. Generate new admin key
|
|
207
|
+
if (!json) console.log(' Generating new admin private key...');
|
|
208
|
+
await execa('openssl', ['genrsa', '-out', `${PKI_DIR}/client.key.new`, '4096'], lamaste);
|
|
209
|
+
|
|
210
|
+
// 5. Create CSR
|
|
211
|
+
if (!json) console.log(' Creating certificate signing request...');
|
|
212
|
+
await execa(
|
|
213
|
+
'openssl',
|
|
214
|
+
[
|
|
215
|
+
'req',
|
|
216
|
+
'-new',
|
|
217
|
+
'-key',
|
|
218
|
+
`${PKI_DIR}/client.key.new`,
|
|
219
|
+
'-out',
|
|
220
|
+
`${PKI_DIR}/client.csr`,
|
|
221
|
+
'-subj',
|
|
222
|
+
'/CN=admin/O=Lamaste',
|
|
223
|
+
],
|
|
224
|
+
lamaste,
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
// 6. Sign with CA
|
|
228
|
+
if (!json) console.log(' Signing certificate with CA...');
|
|
229
|
+
await execa(
|
|
230
|
+
'openssl',
|
|
231
|
+
[
|
|
232
|
+
'x509',
|
|
233
|
+
'-req',
|
|
234
|
+
'-in',
|
|
235
|
+
`${PKI_DIR}/client.csr`,
|
|
236
|
+
'-CA',
|
|
237
|
+
`${PKI_DIR}/ca.crt`,
|
|
238
|
+
'-CAkey',
|
|
239
|
+
`${PKI_DIR}/ca.key`,
|
|
240
|
+
'-CAcreateserial',
|
|
241
|
+
'-out',
|
|
242
|
+
`${PKI_DIR}/client.crt.new`,
|
|
243
|
+
'-days',
|
|
244
|
+
'730',
|
|
245
|
+
'-sha256',
|
|
246
|
+
],
|
|
247
|
+
lamaste,
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
// 7. Create P12 bundle
|
|
251
|
+
const p12Password = crypto.randomBytes(16).toString('hex');
|
|
252
|
+
if (!json) console.log(' Creating PKCS12 bundle...');
|
|
253
|
+
await execa(
|
|
254
|
+
'openssl',
|
|
255
|
+
[
|
|
256
|
+
'pkcs12',
|
|
257
|
+
'-export',
|
|
258
|
+
'-keypbe',
|
|
259
|
+
'PBE-SHA1-3DES',
|
|
260
|
+
'-certpbe',
|
|
261
|
+
'PBE-SHA1-3DES',
|
|
262
|
+
'-macalg',
|
|
263
|
+
'sha1',
|
|
264
|
+
'-out',
|
|
265
|
+
`${PKI_DIR}/client.p12.new`,
|
|
266
|
+
'-inkey',
|
|
267
|
+
`${PKI_DIR}/client.key.new`,
|
|
268
|
+
'-in',
|
|
269
|
+
`${PKI_DIR}/client.crt.new`,
|
|
270
|
+
'-certfile',
|
|
271
|
+
`${PKI_DIR}/ca.crt`,
|
|
272
|
+
'-passout',
|
|
273
|
+
'stdin',
|
|
274
|
+
],
|
|
275
|
+
{ ...lamaste, input: p12Password },
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
if (json) emitStep('generate-cert', 'complete');
|
|
279
|
+
|
|
280
|
+
// 8. Back up old files
|
|
281
|
+
if (json) emitStep('backup', 'running');
|
|
282
|
+
else console.log(' Backing up old certificates...');
|
|
283
|
+
for (const ext of ['key', 'crt', 'p12']) {
|
|
284
|
+
try {
|
|
285
|
+
await access(`${PKI_DIR}/client.${ext}`, constants.F_OK);
|
|
286
|
+
await execa(
|
|
287
|
+
'cp',
|
|
288
|
+
['--', `${PKI_DIR}/client.${ext}`, `${PKI_DIR}/client.${ext}.bak`],
|
|
289
|
+
lamaste,
|
|
290
|
+
);
|
|
291
|
+
} catch {
|
|
292
|
+
// Old file may not exist
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
if (json) emitStep('backup', 'complete');
|
|
296
|
+
|
|
297
|
+
// 9. Move new files into place (atomic rename)
|
|
298
|
+
if (json) emitStep('install-cert', 'running');
|
|
299
|
+
else console.log(' Installing new certificates...');
|
|
300
|
+
await rename(`${PKI_DIR}/client.key.new`, `${PKI_DIR}/client.key`);
|
|
301
|
+
await rename(`${PKI_DIR}/client.crt.new`, `${PKI_DIR}/client.crt`);
|
|
302
|
+
await rename(`${PKI_DIR}/client.p12.new`, `${PKI_DIR}/client.p12`);
|
|
303
|
+
|
|
304
|
+
// 10. Persist P12 password for future redeployments and for NDJSON consumers.
|
|
305
|
+
// Atomic write (temp -> fsync -> chmod 0600 -> rename) so partial writes never
|
|
306
|
+
// expose the secret. Hard-fail if the write fails — never fall back to emitting
|
|
307
|
+
// the password value over stdout.
|
|
308
|
+
const defaultPasswordPath = `${PKI_DIR}/.p12-password`;
|
|
309
|
+
const targetPasswordPath = passwordFile || defaultPasswordPath;
|
|
310
|
+
try {
|
|
311
|
+
await atomicWritePassword(defaultPasswordPath, p12Password);
|
|
312
|
+
if (targetPasswordPath !== defaultPasswordPath) {
|
|
313
|
+
await atomicWritePassword(targetPasswordPath, p12Password);
|
|
314
|
+
}
|
|
315
|
+
} catch (err) {
|
|
316
|
+
const msg = `Failed to write P12 password file: ${err.message}`;
|
|
317
|
+
if (json) {
|
|
318
|
+
emitError(msg);
|
|
319
|
+
} else {
|
|
320
|
+
console.error(` Error: ${msg}`);
|
|
321
|
+
}
|
|
322
|
+
process.exit(1);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// 11. Clean up
|
|
326
|
+
await unlink(`${PKI_DIR}/client.csr`).catch(() => {});
|
|
327
|
+
await unlink(`${PKI_DIR}/ca.srl`).catch(() => {});
|
|
328
|
+
|
|
329
|
+
// 12. Set file permissions (the files are lamaste's: openssl ran as lamaste)
|
|
330
|
+
await execa('chmod', ['600', `${PKI_DIR}/client.key`, `${PKI_DIR}/client.p12`], lamaste);
|
|
331
|
+
await execa('chmod', ['644', `${PKI_DIR}/client.crt`], lamaste);
|
|
332
|
+
|
|
333
|
+
if (json) emitStep('install-cert', 'complete');
|
|
334
|
+
|
|
335
|
+
// 13. Revoke old cert — in the panel's revocation list (state.db,
|
|
336
|
+
// revoked_certs), which the mTLS middleware checks on every request.
|
|
337
|
+
if (oldSerial) {
|
|
338
|
+
if (json) emitStep('revoke-old', 'running');
|
|
339
|
+
else console.log(' Revoking old admin certificate...');
|
|
340
|
+
try {
|
|
341
|
+
await revokeInStateDb(oldSerial, 'admin (reset-admin)', lamaste);
|
|
342
|
+
} catch (err) {
|
|
343
|
+
// The new certificate is already installed; a revocation that did not
|
|
344
|
+
// happen must not pass silently — the old certificate would still work.
|
|
345
|
+
const msg = `Could not revoke the old admin certificate (serial ${oldSerial}): ${err.stderr || err.message}`;
|
|
346
|
+
if (json) {
|
|
347
|
+
emitStep('revoke-old', 'failed', msg);
|
|
348
|
+
emitError(msg);
|
|
349
|
+
} else {
|
|
350
|
+
console.error(` Error: ${msg}`);
|
|
351
|
+
}
|
|
352
|
+
process.exit(1);
|
|
353
|
+
}
|
|
354
|
+
if (json) emitStep('revoke-old', 'complete');
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// 14. Clear 2FA if enabled
|
|
358
|
+
if (config.panel2fa && config.panel2fa.enabled) {
|
|
359
|
+
config.panel2fa = { enabled: false, secret: null, setupComplete: false };
|
|
360
|
+
config.sessionSecret = null;
|
|
361
|
+
if (!json) console.log(' Two-factor authentication has been disabled.');
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// 15. Re-enable IP vhost if it was disabled by 2FA
|
|
365
|
+
if (json) emitStep('config', 'running');
|
|
366
|
+
try {
|
|
367
|
+
const ipAvailable = '/etc/nginx/sites-available/lamalibre-lamaste-panel-ip';
|
|
368
|
+
const ipEnabled = '/etc/nginx/sites-enabled/lamalibre-lamaste-panel-ip';
|
|
369
|
+
await access(ipAvailable, constants.F_OK);
|
|
370
|
+
await execa('ln', ['-sf', ipAvailable, ipEnabled]);
|
|
371
|
+
if (!json) console.log(' IP vhost re-enabled.');
|
|
372
|
+
} catch {
|
|
373
|
+
// IP vhost may not exist
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// 16. Update config
|
|
377
|
+
if (!json) console.log(' Updating panel configuration...');
|
|
378
|
+
config.adminAuthMode = 'p12';
|
|
379
|
+
await writeOwnedFile(CONFIG_PATH, JSON.stringify(config, null, 2) + '\n', lamaste, 0o640);
|
|
380
|
+
if (json) emitStep('config', 'complete');
|
|
381
|
+
|
|
382
|
+
// 17. Restart serverd
|
|
383
|
+
if (json) emitStep('restart', 'running');
|
|
384
|
+
else console.log(' Restarting panel server...');
|
|
385
|
+
try {
|
|
386
|
+
await execa('systemctl', ['restart', PANEL_SERVICE]);
|
|
387
|
+
if (!json) console.log(' Panel server restarted successfully.');
|
|
388
|
+
} catch (err) {
|
|
389
|
+
const msg = `panel server restart failed: ${err.stderr || err.message}`;
|
|
390
|
+
if (!json) {
|
|
391
|
+
console.error(` Warning: ${msg}`);
|
|
392
|
+
console.error(` You may need to restart it manually: systemctl restart ${PANEL_SERVICE}`);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// 18. Reload nginx
|
|
397
|
+
if (!json) console.log(' Reloading nginx...');
|
|
398
|
+
try {
|
|
399
|
+
await execa('nginx', ['-t']);
|
|
400
|
+
await execa('systemctl', ['reload', 'nginx']);
|
|
401
|
+
if (!json) console.log(' nginx reloaded successfully.');
|
|
402
|
+
} catch (err) {
|
|
403
|
+
const msg = `nginx reload failed: ${err.stderr || err.message}`;
|
|
404
|
+
if (!json) {
|
|
405
|
+
console.error(` Warning: ${msg}`);
|
|
406
|
+
console.error(' You may need to restart nginx manually: systemctl restart nginx');
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
if (json) {
|
|
411
|
+
emitStep('restart', 'complete');
|
|
412
|
+
// SECURITY: never emit the password value in NDJSON. The password lives at
|
|
413
|
+
// p12PasswordPath (mode 0600). Consumers (the desktop app) read the file
|
|
414
|
+
// directly via privileged escalation (e.g. `pkexec cat`) and store it in
|
|
415
|
+
// the OS keychain. The path-only contract avoids leaks into Tauri event
|
|
416
|
+
// logs, journald, or anything else that captures NDJSON.
|
|
417
|
+
emitComplete({ p12PasswordPath: targetPasswordPath });
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// 19. Determine IP for scp command
|
|
422
|
+
let serverIp = '';
|
|
423
|
+
try {
|
|
424
|
+
const { stdout } = await execa('hostname', ['-I']);
|
|
425
|
+
const first = stdout.trim().split(/\s+/)[0];
|
|
426
|
+
if (first) serverIp = first;
|
|
427
|
+
} catch {
|
|
428
|
+
serverIp = '<server-ip>';
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// 20. Print result
|
|
432
|
+
const b = chalk.bold;
|
|
433
|
+
const c = chalk.cyan;
|
|
434
|
+
const d = chalk.dim;
|
|
435
|
+
|
|
436
|
+
console.log('');
|
|
437
|
+
console.log(' ' + '='.repeat(44));
|
|
438
|
+
console.log(b(' Admin certificate has been reset to P12.'));
|
|
439
|
+
console.log(b(' Panel 2FA has been disabled (if it was on).'));
|
|
440
|
+
console.log(b(' IP:9292 access has been restored.'));
|
|
441
|
+
console.log('');
|
|
442
|
+
console.log(' 1. Download your client certificate:');
|
|
443
|
+
console.log('');
|
|
444
|
+
console.log(` ${c(`scp root@${serverIp}:${PKI_DIR}/client.p12 .`)}`);
|
|
445
|
+
console.log('');
|
|
446
|
+
console.log(' 2. Import client.p12 into your browser:');
|
|
447
|
+
console.log('');
|
|
448
|
+
console.log(` ${d('macOS: Double-click the file \u2192 Keychain Access')}`);
|
|
449
|
+
console.log(
|
|
450
|
+
` ${d(' \u2192 select "System" keychain \u2192 enter the password below')}`,
|
|
451
|
+
);
|
|
452
|
+
console.log(
|
|
453
|
+
` ${d(' \u2192 find cert \u2192 double-click \u2192 Trust \u2192 Always Trust')}`,
|
|
454
|
+
);
|
|
455
|
+
console.log(` ${d('Linux: Chrome \u2192 Settings \u2192 Privacy & Security')}`);
|
|
456
|
+
console.log(` ${d(' \u2192 Security \u2192 Manage certificates \u2192 Import')}`);
|
|
457
|
+
console.log(` ${d('Windows: Double-click the file \u2192 Certificate Import Wizard')}`);
|
|
458
|
+
console.log('');
|
|
459
|
+
// SECURITY: the new certificate password is NEVER written to a log sink
|
|
460
|
+
// (stdout/stderr/console). It is persisted at ${defaultPasswordPath}
|
|
461
|
+
// (mode 0600, root-readable only) — the single source of truth for
|
|
462
|
+
// redeployment scripts and for the desktop app, which reads it via
|
|
463
|
+
// privileged escalation. Operators retrieve the password with:
|
|
464
|
+
// sudo cat ${defaultPasswordPath}
|
|
465
|
+
console.log(' 3. Certificate password:');
|
|
466
|
+
console.log('');
|
|
467
|
+
console.log(` ${d(`stored at ${defaultPasswordPath} (mode 0600)`)}`);
|
|
468
|
+
console.log(` ${d(`retrieve with: sudo cat ${defaultPasswordPath}`)}`);
|
|
469
|
+
console.log('');
|
|
470
|
+
console.error(` ${b('*** (redacted — read the password file above)')}`);
|
|
471
|
+
console.log('');
|
|
472
|
+
console.log(' 4. Open the Lamaste panel:');
|
|
473
|
+
console.log('');
|
|
474
|
+
console.log(` ${c(`https://${serverIp}:9292`)}`);
|
|
475
|
+
console.log('');
|
|
476
|
+
console.log(' ' + '='.repeat(44));
|
|
477
|
+
console.log('');
|
|
478
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lamaste-server restart — Restart the panel server and optionally the gatekeeper.
|
|
3
|
+
*
|
|
4
|
+
* Waits for the service to reach active state after restart.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import chalk from 'chalk';
|
|
8
|
+
import { execa } from 'execa';
|
|
9
|
+
import { PANEL_SERVICE, GATEKEEPER_SERVICE } from '../config.js';
|
|
10
|
+
import { emitStep, emitError, emitComplete } from '../ndjson.js';
|
|
11
|
+
|
|
12
|
+
const HEALTH_CHECK_ATTEMPTS = 10;
|
|
13
|
+
const HEALTH_CHECK_INTERVAL_MS = 1000;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @param {string[]} args Remaining CLI arguments after 'restart'
|
|
17
|
+
* @param {{ json: boolean }} options
|
|
18
|
+
*/
|
|
19
|
+
export async function runRestart(args, { json }) {
|
|
20
|
+
const all = args.includes('--all');
|
|
21
|
+
const gatekeeperOnly = args.includes('--gatekeeper');
|
|
22
|
+
const services = gatekeeperOnly
|
|
23
|
+
? [GATEKEEPER_SERVICE]
|
|
24
|
+
: all
|
|
25
|
+
? [PANEL_SERVICE, GATEKEEPER_SERVICE]
|
|
26
|
+
: [PANEL_SERVICE];
|
|
27
|
+
|
|
28
|
+
for (const service of services) {
|
|
29
|
+
if (json) {
|
|
30
|
+
emitStep(service, 'running', `Restarting ${service}`);
|
|
31
|
+
} else {
|
|
32
|
+
process.stderr.write(` Restarting ${chalk.cyan(service)}...`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
await execa('systemctl', ['restart', service]);
|
|
37
|
+
} catch (err) {
|
|
38
|
+
const msg = `Failed to restart ${service}: ${err.stderr || err.message}`;
|
|
39
|
+
if (json) {
|
|
40
|
+
emitStep(service, 'failed', msg);
|
|
41
|
+
emitError(msg);
|
|
42
|
+
} else {
|
|
43
|
+
console.error(` ${chalk.red('failed')}`);
|
|
44
|
+
console.error(` ${chalk.red(msg)}`);
|
|
45
|
+
}
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Wait for service to become active
|
|
50
|
+
let healthy = false;
|
|
51
|
+
for (let i = 0; i < HEALTH_CHECK_ATTEMPTS; i++) {
|
|
52
|
+
await sleep(HEALTH_CHECK_INTERVAL_MS);
|
|
53
|
+
try {
|
|
54
|
+
const { stdout } = await execa('systemctl', ['is-active', service]);
|
|
55
|
+
if (stdout.trim() === 'active') {
|
|
56
|
+
healthy = true;
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
} catch {
|
|
60
|
+
// Not active yet, retry
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (!healthy) {
|
|
65
|
+
const msg = `Service ${service} did not become active within ${HEALTH_CHECK_ATTEMPTS} seconds`;
|
|
66
|
+
if (json) {
|
|
67
|
+
emitStep(service, 'failed', msg);
|
|
68
|
+
emitError(msg);
|
|
69
|
+
} else {
|
|
70
|
+
console.error(` ${chalk.yellow('timeout')}`);
|
|
71
|
+
console.error(` ${chalk.yellow(msg)}`);
|
|
72
|
+
}
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (json) {
|
|
77
|
+
emitStep(service, 'complete');
|
|
78
|
+
} else {
|
|
79
|
+
console.log(` ${chalk.green('ok')}`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (json) {
|
|
84
|
+
emitComplete({ restarted: services });
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* @param {number} ms
|
|
90
|
+
* @returns {Promise<void>}
|
|
91
|
+
*/
|
|
92
|
+
function sleep(ms) {
|
|
93
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
94
|
+
}
|