@lamalibre/create-lamaste 4.2.0 → 4.3.1
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/package.json +2 -2
- package/scripts/bundle-vendor.js +13 -8
- package/scripts/lamaste-priv +205 -2
- package/src/index.js +8 -4
- package/src/lib/node-version.js +114 -0
- package/src/lib/panel-pin.js +41 -0
- package/src/lib/relay-services.js +15 -1
- package/src/lib/service-config.js +113 -0
- package/src/tasks/gatekeeper.js +8 -3
- package/src/tasks/node.js +34 -75
- package/src/tasks/redeploy.js +36 -0
- package/vendor/docs/00-introduction/how-it-works.md +2 -2
- package/vendor/docs/00-introduction/quickstart.md +5 -5
- package/vendor/docs/00-introduction/what-is-lamaste.md +5 -5
- package/vendor/docs/01-concepts/authentication.md +6 -3
- package/vendor/docs/01-concepts/authorization.md +29 -17
- package/vendor/docs/01-concepts/certificates.md +3 -3
- package/vendor/docs/01-concepts/mtls.md +2 -2
- package/vendor/docs/01-concepts/nginx-reverse-proxy.md +8 -3
- package/vendor/docs/01-concepts/security-model.md +110 -35
- package/vendor/docs/01-concepts/tickets.md +2 -2
- package/vendor/docs/01-concepts/tunneling.md +4 -4
- package/vendor/docs/02-guides/agent-setup.md +34 -16
- package/vendor/docs/02-guides/certificate-management.md +2 -2
- package/vendor/docs/02-guides/cloud-provisioning.md +4 -4
- package/vendor/docs/02-guides/desktop-app-setup.md +11 -10
- package/vendor/docs/02-guides/installation.md +3 -3
- package/vendor/docs/02-guides/onboarding.md +2 -2
- package/vendor/docs/02-guides/standalone-plugin-with-tickets.md +2 -2
- package/vendor/docs/03-architecture/installer-flow.md +2 -2
- package/vendor/docs/03-architecture/installer.md +4 -4
- package/vendor/docs/03-architecture/management-flow.md +1 -1
- package/vendor/docs/03-architecture/nginx-configuration.md +91 -26
- package/vendor/docs/03-architecture/overview.md +8 -8
- package/vendor/docs/03-architecture/panel-client.md +6 -6
- package/vendor/docs/03-architecture/panel-server.md +2 -2
- package/vendor/docs/03-architecture/state-management.md +2 -2
- package/vendor/docs/03-architecture/system-overview.md +1 -1
- package/vendor/docs/04-api-reference/agent-hosted-plugins.md +407 -0
- package/vendor/docs/04-api-reference/app-host.md +360 -0
- package/vendor/docs/04-api-reference/audit-and-notifications.md +57 -0
- package/vendor/docs/04-api-reference/certificates.md +95 -38
- package/vendor/docs/04-api-reference/gatekeeper.md +5 -3
- package/vendor/docs/04-api-reference/lifecycle-hooks.md +333 -0
- package/vendor/docs/04-api-reference/onboarding.md +1 -1
- package/vendor/docs/04-api-reference/overview.md +137 -111
- package/vendor/docs/04-api-reference/registry-and-oidc.md +8 -4
- package/vendor/docs/04-api-reference/tunnels.md +15 -13
- package/vendor/docs/05-operations/backup-and-restore.md +3 -1
- package/vendor/docs/05-operations/uninstalling.md +1 -1
- package/vendor/docs/05-operations/upgrades.md +25 -20
- package/vendor/docs/06-reference/config-files.md +195 -97
- package/vendor/docs/06-reference/glossary.md +3 -3
- package/vendor/docs/06-reference/installer-flags.md +13 -13
- package/vendor/docs/06-reference/ports-and-services.md +1 -1
- package/vendor/docs/06-reference/troubleshooting.md +33 -8
- package/vendor/docs/_index.json +20 -0
- package/vendor/docs/gatekeeper-design.md +22 -19
- package/vendor/docs/identity-directory-design.md +57 -56
- package/vendor/docs/package.json +1 -1
- package/vendor/docs/todo/lib-cli-desktop-refactor.md +27 -27
- package/vendor/gatekeeper/dist/cli/index.js +0 -0
- package/vendor/gatekeeper/dist/lib/types.d.ts +12 -0
- package/vendor/gatekeeper/dist/lib/types.d.ts.map +1 -1
- package/vendor/gatekeeper/dist/server/index.d.ts +2 -0
- package/vendor/gatekeeper/dist/server/index.d.ts.map +1 -1
- package/vendor/gatekeeper/dist/server/index.js +47 -8
- package/vendor/gatekeeper/dist/server/index.js.map +1 -1
- package/vendor/gatekeeper/dist/server/routes/authz.d.ts.map +1 -1
- package/vendor/gatekeeper/dist/server/routes/authz.js +233 -141
- package/vendor/gatekeeper/dist/server/routes/authz.js.map +1 -1
- package/vendor/gatekeeper/package.json +4 -3
- package/vendor/server/bin/lamaste-server.js +25 -1
- package/vendor/server/package.json +2 -2
- package/vendor/server/src/commands/audit.js +60 -0
- package/vendor/server/src/commands/panel-pin.js +40 -0
- package/vendor/server/src/commands/plugins.js +963 -12
- package/vendor/server/src/commands/push-install.js +317 -0
- package/vendor/server/src/commands/registry-credential.js +2 -1
- package/vendor/server/src/commands/uninstall.js +28 -1
- package/vendor/server/src/ndjson.js +4 -2
- package/vendor/server/src/panel-api.js +277 -34
- package/vendor/server/src/panel-identity.js +165 -0
- package/vendor/server-ui/dist/assets/index-BTlBt_cD.js +84 -0
- package/vendor/server-ui/dist/assets/index-Bc5NCMSv.css +1 -0
- package/vendor/server-ui/dist/index.html +2 -2
- package/vendor/serverd/package.json +2 -2
- package/vendor/serverd/src/index.js +56 -4
- package/vendor/serverd/src/lib/actor.js +24 -0
- package/vendor/serverd/src/lib/agent-jobs.js +330 -0
- package/vendor/serverd/src/lib/app-error.js +10 -0
- package/vendor/serverd/src/lib/audit.js +145 -0
- package/vendor/serverd/src/lib/hook-effects.js +123 -0
- package/vendor/serverd/src/lib/hook-pipelines.js +902 -0
- package/vendor/serverd/src/lib/migrations/state/0006_plugin_agent.sql +4 -0
- package/vendor/serverd/src/lib/migrations/state/0007_audit_and_notifications.sql +29 -0
- package/vendor/serverd/src/lib/migrations/state/0008_plugin_hooks.sql +70 -0
- package/vendor/serverd/src/lib/migrations/state/0009_plugin_agent_half.sql +53 -0
- package/vendor/serverd/src/lib/migrations/state/0010_plugin_routes_and_retained_data.sql +47 -0
- package/vendor/serverd/src/lib/migrations/state/0011_agent_job_signature.sql +14 -0
- package/vendor/serverd/src/lib/mtls.js +89 -8
- package/vendor/serverd/src/lib/nginx.js +298 -44
- package/vendor/serverd/src/lib/notifications.js +107 -0
- package/vendor/serverd/src/lib/plugin-hooks.js +560 -0
- package/vendor/serverd/src/lib/plugin-hosting.js +688 -0
- package/vendor/serverd/src/lib/plugin-lifecycle.js +603 -0
- package/vendor/serverd/src/lib/plugin-operations.js +295 -0
- package/vendor/serverd/src/lib/plugin-packages.js +249 -0
- package/vendor/serverd/src/lib/plugins.js +298 -68
- package/vendor/serverd/src/lib/push-install.js +88 -2
- package/vendor/serverd/src/lib/relay-hooks.js +100 -0
- package/vendor/serverd/src/lib/relay-signing.js +43 -0
- package/vendor/serverd/src/lib/request-hooks.js +79 -0
- package/vendor/serverd/src/lib/retained-data.js +99 -0
- package/vendor/serverd/src/lib/trust-proxy.js +34 -0
- package/vendor/serverd/src/lib/tunnel-hooks.js +88 -0
- package/vendor/serverd/src/lib/vhost-reconcile.js +260 -0
- package/vendor/serverd/src/middleware/twofa-session.js +11 -1
- package/vendor/serverd/src/routes/agent-forwarder.js +37 -0
- package/vendor/serverd/src/routes/management/agent-panel-authz.js +97 -0
- package/vendor/serverd/src/routes/management/agents.js +262 -1
- package/vendor/serverd/src/routes/management/audit.js +81 -0
- package/vendor/serverd/src/routes/management/certs.js +1 -15
- package/vendor/serverd/src/routes/management/plugins.js +569 -54
- package/vendor/serverd/src/routes/management/tunnels.js +26 -0
- package/vendor/serverd/src/routes/management.js +4 -0
- package/vendor/server-ui/dist/assets/index-B5Wp8lu-.js +0 -257
- package/vendor/server-ui/dist/assets/index-BqWWFlTU.css +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lamalibre/create-lamaste",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.3.1",
|
|
4
4
|
"description": "One-command setup for secure reverse tunnels with a management dashboard",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"mtls"
|
|
44
44
|
],
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@lamalibre/lamaste": "^4.
|
|
46
|
+
"@lamalibre/lamaste": "^4.3.1",
|
|
47
47
|
"chalk": "^5.3.0",
|
|
48
48
|
"execa": "^9.0.0",
|
|
49
49
|
"listr2": "^8.0.0"
|
package/scripts/bundle-vendor.js
CHANGED
|
@@ -12,6 +12,7 @@ import { existsSync } from 'node:fs';
|
|
|
12
12
|
import { execFileSync } from 'node:child_process';
|
|
13
13
|
import { dirname, join } from 'node:path';
|
|
14
14
|
import { fileURLToPath } from 'node:url';
|
|
15
|
+
import { assertProductionBundle } from '../../../../scripts/assert-production-bundle.js';
|
|
15
16
|
|
|
16
17
|
const thisDir = dirname(fileURLToPath(import.meta.url));
|
|
17
18
|
const packageRoot = join(thisDir, '..');
|
|
@@ -61,6 +62,8 @@ async function main() {
|
|
|
61
62
|
);
|
|
62
63
|
}
|
|
63
64
|
|
|
65
|
+
await assertProductionBundle(clientDist, { label: 'lamaste-server-ui/dist', monorepoRoot });
|
|
66
|
+
|
|
64
67
|
const clientDest = join(vendorDir, 'server-ui');
|
|
65
68
|
await mkdir(join(clientDest, 'dist'), { recursive: true });
|
|
66
69
|
await cp(clientDist, join(clientDest, 'dist'), { recursive: true });
|
|
@@ -70,15 +73,17 @@ async function main() {
|
|
|
70
73
|
// --- gatekeeper: package.json + dist/ (compiled TypeScript) ---
|
|
71
74
|
const gatekeeperSrc = join(monorepoRoot, 'packages', 'sdks', 'gatekeeper');
|
|
72
75
|
const gatekeeperDist = join(gatekeeperSrc, 'dist');
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
console.error('Bundled vendor/gatekeeper (package.json + dist/)');
|
|
79
|
-
} else {
|
|
80
|
-
console.error('Skipped vendor/gatekeeper (dist/ not found — run "npm run build" first)');
|
|
76
|
+
// An installer without Gatekeeper cannot deploy it: refuse, never skip.
|
|
77
|
+
if (!existsSync(gatekeeperDist)) {
|
|
78
|
+
throw new Error(
|
|
79
|
+
`lamaste-gatekeeper/dist/ not found at ${gatekeeperDist}. Run "npm run build" first.`,
|
|
80
|
+
);
|
|
81
81
|
}
|
|
82
|
+
const gatekeeperDest = join(vendorDir, 'gatekeeper');
|
|
83
|
+
await mkdir(gatekeeperDest, { recursive: true });
|
|
84
|
+
await cp(join(gatekeeperSrc, 'package.json'), join(gatekeeperDest, 'package.json'));
|
|
85
|
+
await cp(gatekeeperDist, join(gatekeeperDest, 'dist'), { recursive: true });
|
|
86
|
+
console.error('Bundled vendor/gatekeeper (package.json + dist/)');
|
|
82
87
|
|
|
83
88
|
// --- docs: version-stamped markdown ---
|
|
84
89
|
const docsStampScript = join(
|
package/scripts/lamaste-priv
CHANGED
|
@@ -17,6 +17,11 @@
|
|
|
17
17
|
* nginx-site remove <name> delete <name>, its link and its backup
|
|
18
18
|
* authelia-totp <username> stdin: base32 secret
|
|
19
19
|
* self-update <version> run create-lamaste@<version> detached
|
|
20
|
+
* hooks install <route> <sha256|none> stdin: the plugin's approved relay hook
|
|
21
|
+
* module (none: shared hooks only); start its runner
|
|
22
|
+
* hooks activate <route> <sha256> point the runner back at a module kept from before
|
|
23
|
+
* hooks restart <route> stop the plugin's hook runner (the socket stays)
|
|
24
|
+
* hooks remove <route> disable the runner and delete its modules
|
|
20
25
|
*
|
|
21
26
|
* Why vhosts are validated: nginx's master process runs as root and opens the
|
|
22
27
|
* files a configuration names — `access_log /etc/cron.d/x` with a crafted
|
|
@@ -484,7 +489,7 @@ function isRegularFile(p) {
|
|
|
484
489
|
}
|
|
485
490
|
}
|
|
486
491
|
|
|
487
|
-
function
|
|
492
|
+
function readStdinBytes(maxBytes) {
|
|
488
493
|
const chunks = [];
|
|
489
494
|
let total = 0;
|
|
490
495
|
const buf = Buffer.alloc(65536);
|
|
@@ -502,7 +507,11 @@ function readStdin(maxBytes) {
|
|
|
502
507
|
if (total > maxBytes) throw new UsageError('input is too large');
|
|
503
508
|
chunks.push(Buffer.from(buf.subarray(0, read)));
|
|
504
509
|
}
|
|
505
|
-
return Buffer.concat(chunks)
|
|
510
|
+
return Buffer.concat(chunks);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
function readStdin(maxBytes) {
|
|
514
|
+
return readStdinBytes(maxBytes).toString('utf8');
|
|
506
515
|
}
|
|
507
516
|
|
|
508
517
|
// ---------------------------------------------------------------------------
|
|
@@ -668,6 +677,193 @@ function selfUpdate(version) {
|
|
|
668
677
|
process.stdout.write(`${unit}\n`);
|
|
669
678
|
}
|
|
670
679
|
|
|
680
|
+
// ---------------------------------------------------------------------------
|
|
681
|
+
// Relay hook runners
|
|
682
|
+
// ---------------------------------------------------------------------------
|
|
683
|
+
|
|
684
|
+
const HOOKS_DIR = '/opt/lamalibre/lamaste/hooks';
|
|
685
|
+
const HOOKS_UNIT_PREFIX = 'lamalibre-lamaste-hooks';
|
|
686
|
+
const SYSTEMCTL = '/usr/bin/systemctl';
|
|
687
|
+
const HOOK_ROUTE_RE = /^[a-z0-9]([a-z0-9-]{0,98}[a-z0-9])?$/;
|
|
688
|
+
const SHA256_RE = /^[a-f0-9]{64}$/;
|
|
689
|
+
const MAX_HOOK_MODULE_BYTES = 4 * 1024 * 1024;
|
|
690
|
+
|
|
691
|
+
function assertHookRoute(route) {
|
|
692
|
+
if (typeof route !== 'string' || !HOOK_ROUTE_RE.test(route)) {
|
|
693
|
+
throw new UsageError('invalid plugin route');
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
function assertSha256(digest) {
|
|
698
|
+
if (typeof digest !== 'string' || !SHA256_RE.test(digest)) {
|
|
699
|
+
throw new UsageError('digest must be a SHA-256 hex string');
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
/** Create `dir` (0755, root) unless it exists; refuse anything but a root-owned directory. */
|
|
704
|
+
function ensureRootDirectory(dir) {
|
|
705
|
+
try {
|
|
706
|
+
fs.mkdirSync(dir, { mode: 0o755 });
|
|
707
|
+
} catch (err) {
|
|
708
|
+
if (err.code !== 'EEXIST') throw err;
|
|
709
|
+
}
|
|
710
|
+
const st = fs.lstatSync(dir);
|
|
711
|
+
if (!st.isDirectory() || st.uid !== 0) {
|
|
712
|
+
throw new Error(`${dir} is not a root-owned directory`);
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
function sha256(bytes) {
|
|
717
|
+
return crypto.createHash('sha256').update(bytes).digest('hex');
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
function systemctl(args, { allowFailure = false } = {}) {
|
|
721
|
+
const result = spawnSync(SYSTEMCTL, args, {
|
|
722
|
+
cwd: '/',
|
|
723
|
+
env: { PATH: '/usr/sbin:/usr/bin:/sbin:/bin' },
|
|
724
|
+
encoding: 'utf8',
|
|
725
|
+
timeout: 30_000,
|
|
726
|
+
});
|
|
727
|
+
if (result.error) throw result.error;
|
|
728
|
+
if (result.status !== 0 && !allowFailure) {
|
|
729
|
+
throw new Error(
|
|
730
|
+
`systemctl ${args.join(' ')} exited with status ${result.status}: ${(result.stderr || '').trim()}`,
|
|
731
|
+
);
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
function runnerEnvPath(route) {
|
|
736
|
+
return path.join(HOOKS_DIR, route, 'runner.env');
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
/** The module the runner currently loads (from runner.env), or null. */
|
|
740
|
+
function activeHookModule(route) {
|
|
741
|
+
let text;
|
|
742
|
+
try {
|
|
743
|
+
text = fs.readFileSync(runnerEnvPath(route), 'utf8');
|
|
744
|
+
} catch (err) {
|
|
745
|
+
if (err.code === 'ENOENT') return null;
|
|
746
|
+
throw err;
|
|
747
|
+
}
|
|
748
|
+
const m = /^LAMASTE_HOOK_RUNNER_ARGS=--entry (\S+)$/m.exec(text);
|
|
749
|
+
return m ? m[1] : null;
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
/**
|
|
753
|
+
* runner.env: the node flag that lets the runner read the module, and the
|
|
754
|
+
* runner flag that names it. Both empty when the plugin uses shared hooks
|
|
755
|
+
* only. Paths are built here, never taken from the caller.
|
|
756
|
+
*/
|
|
757
|
+
function writeRunnerEnv(route, modulePath) {
|
|
758
|
+
const nodeArgs = modulePath ? `--allow-fs-read=${modulePath}` : '';
|
|
759
|
+
const runnerArgs = modulePath ? `--entry ${modulePath}` : '';
|
|
760
|
+
atomicWrite(
|
|
761
|
+
runnerEnvPath(route),
|
|
762
|
+
`# Managed by lamaste-priv: the approved relay hook module of plugin ${route}.\n` +
|
|
763
|
+
`LAMASTE_HOOK_NODE_ARGS=${nodeArgs}\nLAMASTE_HOOK_RUNNER_ARGS=${runnerArgs}\n`,
|
|
764
|
+
0o644,
|
|
765
|
+
);
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
/** Keep the active module and the one before it (for `hooks activate`); delete the rest. */
|
|
769
|
+
function pruneHookModules(route, keep) {
|
|
770
|
+
const dir = path.join(HOOKS_DIR, route);
|
|
771
|
+
for (const name of fs.readdirSync(dir)) {
|
|
772
|
+
if (!/^[a-f0-9]{64}\.mjs$/.test(name)) continue;
|
|
773
|
+
const file = path.join(dir, name);
|
|
774
|
+
if (!keep.includes(file)) unlinkIfPresent(file);
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
function restartRunner(route) {
|
|
779
|
+
// The socket stays; the next call starts a runner with the new module.
|
|
780
|
+
systemctl(['stop', `${HOOKS_UNIT_PREFIX}@${route}.service`]);
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
function hooksInstall(route, digest) {
|
|
784
|
+
assertHookRoute(route);
|
|
785
|
+
if (digest !== 'none') assertSha256(digest);
|
|
786
|
+
ensureRootDirectory(HOOKS_DIR);
|
|
787
|
+
ensureRootDirectory(path.join(HOOKS_DIR, route));
|
|
788
|
+
const previous = activeHookModule(route);
|
|
789
|
+
|
|
790
|
+
let modulePath = null;
|
|
791
|
+
if (digest !== 'none') {
|
|
792
|
+
const bytes = readStdinBytes(MAX_HOOK_MODULE_BYTES);
|
|
793
|
+
if (bytes.length === 0) throw new UsageError('the hook module arrives on stdin');
|
|
794
|
+
if (sha256(bytes) !== digest) {
|
|
795
|
+
throw new Error('the hook module does not match the approved digest');
|
|
796
|
+
}
|
|
797
|
+
modulePath = path.join(HOOKS_DIR, route, `${digest}.mjs`);
|
|
798
|
+
if (!isRegularFile(modulePath) || sha256(fs.readFileSync(modulePath)) !== digest) {
|
|
799
|
+
atomicWrite(modulePath, bytes, 0o644);
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
writeRunnerEnv(route, modulePath);
|
|
803
|
+
pruneHookModules(route, [modulePath, previous].filter(Boolean));
|
|
804
|
+
systemctl(['enable', '--now', `${HOOKS_UNIT_PREFIX}@${route}.socket`]);
|
|
805
|
+
restartRunner(route);
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
function hooksActivate(route, digest) {
|
|
809
|
+
assertHookRoute(route);
|
|
810
|
+
assertSha256(digest);
|
|
811
|
+
const modulePath = path.join(HOOKS_DIR, route, `${digest}.mjs`);
|
|
812
|
+
if (!isRegularFile(modulePath) || sha256(fs.readFileSync(modulePath)) !== digest) {
|
|
813
|
+
throw new Error('no kept hook module has that digest');
|
|
814
|
+
}
|
|
815
|
+
const previous = activeHookModule(route);
|
|
816
|
+
writeRunnerEnv(route, modulePath);
|
|
817
|
+
pruneHookModules(route, [modulePath, previous].filter(Boolean));
|
|
818
|
+
systemctl(['enable', '--now', `${HOOKS_UNIT_PREFIX}@${route}.socket`]);
|
|
819
|
+
restartRunner(route);
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
function hooksRestart(route) {
|
|
823
|
+
assertHookRoute(route);
|
|
824
|
+
restartRunner(route);
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
function hooksRemove(route) {
|
|
828
|
+
assertHookRoute(route);
|
|
829
|
+
systemctl(['disable', '--now', `${HOOKS_UNIT_PREFIX}@${route}.socket`], { allowFailure: true });
|
|
830
|
+
systemctl(['stop', `${HOOKS_UNIT_PREFIX}@${route}.service`], { allowFailure: true });
|
|
831
|
+
const dir = path.join(HOOKS_DIR, route);
|
|
832
|
+
let st = null;
|
|
833
|
+
try {
|
|
834
|
+
st = fs.lstatSync(dir);
|
|
835
|
+
} catch (err) {
|
|
836
|
+
if (err.code !== 'ENOENT') throw err;
|
|
837
|
+
}
|
|
838
|
+
if (st) {
|
|
839
|
+
if (!st.isDirectory() || st.uid !== 0) throw new Error(`${dir} is not a root-owned directory`);
|
|
840
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
function hooks(action, rest) {
|
|
845
|
+
switch (action) {
|
|
846
|
+
case 'install':
|
|
847
|
+
if (rest.length !== 2) throw new UsageError('usage: hooks install <route> <sha256|none>');
|
|
848
|
+
hooksInstall(rest[0], rest[1]);
|
|
849
|
+
return;
|
|
850
|
+
case 'activate':
|
|
851
|
+
if (rest.length !== 2) throw new UsageError('usage: hooks activate <route> <sha256>');
|
|
852
|
+
hooksActivate(rest[0], rest[1]);
|
|
853
|
+
return;
|
|
854
|
+
case 'restart':
|
|
855
|
+
if (rest.length !== 1) throw new UsageError('usage: hooks restart <route>');
|
|
856
|
+
hooksRestart(rest[0]);
|
|
857
|
+
return;
|
|
858
|
+
case 'remove':
|
|
859
|
+
if (rest.length !== 1) throw new UsageError('usage: hooks remove <route>');
|
|
860
|
+
hooksRemove(rest[0]);
|
|
861
|
+
return;
|
|
862
|
+
default:
|
|
863
|
+
throw new UsageError(`unknown hooks action: ${action ?? '(none)'}`);
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
|
|
671
867
|
function main(argv) {
|
|
672
868
|
if (typeof process.getuid !== 'function' || process.getuid() !== 0) {
|
|
673
869
|
throw new UsageError('must run as root (via sudo)');
|
|
@@ -687,6 +883,10 @@ function main(argv) {
|
|
|
687
883
|
if (rest.length !== 1) throw new UsageError('usage: self-update <version>');
|
|
688
884
|
selfUpdate(rest[0]);
|
|
689
885
|
return;
|
|
886
|
+
case 'hooks':
|
|
887
|
+
if (rest.length < 1) throw new UsageError('usage: hooks <action> <route> [...]');
|
|
888
|
+
hooks(rest[0], rest.slice(1));
|
|
889
|
+
return;
|
|
690
890
|
default:
|
|
691
891
|
throw new UsageError(`unknown operation: ${op ?? '(none)'}`);
|
|
692
892
|
}
|
|
@@ -708,5 +908,8 @@ if (require.main === module) {
|
|
|
708
908
|
PANEL_SITE_RE,
|
|
709
909
|
UsageError,
|
|
710
910
|
AUTHELIA_CLI_ENV,
|
|
911
|
+
assertHookRoute,
|
|
912
|
+
HOOK_ROUTE_RE,
|
|
913
|
+
main,
|
|
711
914
|
};
|
|
712
915
|
}
|
package/src/index.js
CHANGED
|
@@ -14,6 +14,7 @@ import chalk from 'chalk';
|
|
|
14
14
|
import { execa } from 'execa';
|
|
15
15
|
import { detectOS, detectIP, checkRoot } from './lib/env.js';
|
|
16
16
|
import { printSummary } from './lib/summary.js';
|
|
17
|
+
import { readPanelKeyPin } from './lib/panel-pin.js';
|
|
17
18
|
import { hardenTasks } from './tasks/harden.js';
|
|
18
19
|
import { nodeTasks } from './tasks/node.js';
|
|
19
20
|
import { mtlsTasks } from './tasks/mtls.js';
|
|
@@ -85,7 +86,7 @@ ${b('SYSTEM MODIFICATIONS')}
|
|
|
85
86
|
• Hardens SSH (disables password authentication)
|
|
86
87
|
|
|
87
88
|
${y('Packages')}
|
|
88
|
-
• Installs Node.js
|
|
89
|
+
• Installs Node.js 22, nginx, certbot
|
|
89
90
|
|
|
90
91
|
${y('Certificates')}
|
|
91
92
|
• Generates mTLS CA, server, and client certificates
|
|
@@ -330,7 +331,7 @@ ${chalk.cyan.bold('│')} The following changes will be made:
|
|
|
330
331
|
${chalk.cyan.bold('│')} ${chalk.cyan.bold('│')}
|
|
331
332
|
${chalk.cyan.bold('│')} ${chalk.yellow('•')} Reset UFW firewall (allow ports 22, 80, 443, 9292) ${chalk.cyan.bold('│')}
|
|
332
333
|
${chalk.cyan.bold('│')} ${chalk.yellow('•')} Harden SSH (disable password authentication) ${chalk.cyan.bold('│')}
|
|
333
|
-
${chalk.cyan.bold('│')} ${chalk.yellow('•')} Install fail2ban, Node.js
|
|
334
|
+
${chalk.cyan.bold('│')} ${chalk.yellow('•')} Install fail2ban, Node.js 22, nginx, certbot ${chalk.cyan.bold('│')}
|
|
334
335
|
${chalk.cyan.bold('│')} ${chalk.yellow('•')} Generate mTLS certificates for browser access ${chalk.cyan.bold('│')}
|
|
335
336
|
${chalk.cyan.bold('│')} ${chalk.yellow('•')} Create lamaste user and systemd service ${chalk.cyan.bold('│')}
|
|
336
337
|
${chalk.cyan.bold('│')} ${chalk.yellow('•')} Deploy panel to /opt/lamalibre/lamaste/ ${chalk.cyan.bold('│')}
|
|
@@ -556,7 +557,7 @@ export async function main() {
|
|
|
556
557
|
// nginx, certbot and openssl. Its hardening subtasks (UFW, fail2ban,
|
|
557
558
|
// SSH) skip themselves — the same as the interactive install.
|
|
558
559
|
{ key: 'harden_system', title: 'Hardening operating system', fn: hardenTasks },
|
|
559
|
-
{ key: 'install_node', title: 'Installing Node.js
|
|
560
|
+
{ key: 'install_node', title: 'Installing Node.js 22', fn: nodeTasks },
|
|
560
561
|
{ key: 'generate_certs', title: 'Generating mTLS certificates', fn: mtlsTasks },
|
|
561
562
|
{ key: 'configure_nginx', title: 'Configuring nginx', fn: nginxTasks },
|
|
562
563
|
{ key: 'deploy_panel', title: 'Deploying Lamaste panel', fn: panelTasks },
|
|
@@ -574,7 +575,7 @@ export async function main() {
|
|
|
574
575
|
task: (_ctx, task) => hardenTasks(ctx, task),
|
|
575
576
|
},
|
|
576
577
|
{
|
|
577
|
-
title: 'Installing Node.js
|
|
578
|
+
title: 'Installing Node.js 22',
|
|
578
579
|
task: (_ctx, task) => nodeTasks(ctx, task),
|
|
579
580
|
},
|
|
580
581
|
{
|
|
@@ -630,6 +631,7 @@ export async function main() {
|
|
|
630
631
|
}
|
|
631
632
|
|
|
632
633
|
if (flags.json) {
|
|
634
|
+
const panelServerPubkeySha256 = await readPanelKeyPin(ctx.pkiDir);
|
|
633
635
|
emitJson({
|
|
634
636
|
event: 'complete',
|
|
635
637
|
server: {
|
|
@@ -637,6 +639,8 @@ export async function main() {
|
|
|
637
639
|
panelUrl: `https://${ctx.ip}:9292`,
|
|
638
640
|
p12Path: `${ctx.pkiDir}/client.p12`,
|
|
639
641
|
p12PasswordPath: `${ctx.pkiDir}/.p12-password`,
|
|
642
|
+
// The key pin of the panel's IP certificate, for the desktop app.
|
|
643
|
+
...(panelServerPubkeySha256 ? { panelServerPubkeySha256 } : {}),
|
|
640
644
|
},
|
|
641
645
|
});
|
|
642
646
|
} else {
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { execa } from 'execa';
|
|
2
|
+
import { mkdtemp, rm } from 'node:fs/promises';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
|
|
6
|
+
/** The Node.js line the relay runs, from NodeSource. */
|
|
7
|
+
export const NODE_MAJOR = 22;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The oldest Node.js the relay accepts: 22.5 brought `node:sqlite`
|
|
11
|
+
* (lamaste-serverd's state database) and 22.13 the stable permission model
|
|
12
|
+
* (`--permission`), which sandboxes the relay hook runner. NodeSource's 22.x
|
|
13
|
+
* line moves on its own, so the minor is checked, not only the major.
|
|
14
|
+
*/
|
|
15
|
+
export const MIN_NODE_VERSION = '22.13.0';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* lamaste-serverd's `engines` stops below 25: Node 25 adds network
|
|
19
|
+
* permissions to `--permission`, which the hook runner's unit does not grant.
|
|
20
|
+
*/
|
|
21
|
+
export const MAX_NODE_MAJOR_EXCLUSIVE = 25;
|
|
22
|
+
|
|
23
|
+
/** The binary every Lamaste unit runs (`ExecStart=/usr/bin/node …`). */
|
|
24
|
+
export const NODE_BIN = '/usr/bin/node';
|
|
25
|
+
|
|
26
|
+
/** `v22.13.1` → [22, 13, 1]; null when it is not a version. */
|
|
27
|
+
function parseNodeVersion(version) {
|
|
28
|
+
const m = /^v?(\d+)\.(\d+)\.(\d+)/.exec(String(version).trim());
|
|
29
|
+
return m ? [Number(m[1]), Number(m[2]), Number(m[3])] : null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Whether `version` is at least {@link MIN_NODE_VERSION} and below {@link MAX_NODE_MAJOR_EXCLUSIVE}. */
|
|
33
|
+
export function nodeVersionSupported(version) {
|
|
34
|
+
const v = parseNodeVersion(version);
|
|
35
|
+
const min = parseNodeVersion(MIN_NODE_VERSION);
|
|
36
|
+
if (!v || !min) return false;
|
|
37
|
+
if (v[0] >= MAX_NODE_MAJOR_EXCLUSIVE) return false;
|
|
38
|
+
for (let i = 0; i < 3; i++) {
|
|
39
|
+
if (v[i] !== min[i]) return v[i] > min[i];
|
|
40
|
+
}
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Whether `version` is too new to be installed over (only an operator should downgrade). */
|
|
45
|
+
export function nodeVersionTooNew(version) {
|
|
46
|
+
const v = parseNodeVersion(version);
|
|
47
|
+
return v !== null && v[0] >= MAX_NODE_MAJOR_EXCLUSIVE;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** The refusal of a {@link nodeVersionTooNew} Node.js. */
|
|
51
|
+
export function nodeTooNewError(version) {
|
|
52
|
+
return new Error(
|
|
53
|
+
`Node.js ${version} at ${NODE_BIN} is not supported: Lamaste needs ${MIN_NODE_VERSION} or later, below ${MAX_NODE_MAJOR_EXCLUSIVE}.`,
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** The version of {@link NODE_BIN}, or null when it is missing. */
|
|
58
|
+
export async function installedNodeVersion() {
|
|
59
|
+
try {
|
|
60
|
+
const { stdout } = await execa(NODE_BIN, ['--version']);
|
|
61
|
+
return stdout.trim();
|
|
62
|
+
} catch {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Install or upgrade Node.js from NodeSource's {@link NODE_MAJOR}.x line.
|
|
69
|
+
*
|
|
70
|
+
* The setup script runs as root, so it is downloaded into a directory of
|
|
71
|
+
* its own (`mkdtemp`, 0700) — never a fixed path in the shared /tmp, where
|
|
72
|
+
* another user could have put a file or a link first — and the directory
|
|
73
|
+
* goes on every path.
|
|
74
|
+
*
|
|
75
|
+
* @param {(line: string) => void} report
|
|
76
|
+
* @param {{ exec?: typeof execa, tmpRoot?: string }} [options] - replaces the commands and the
|
|
77
|
+
* temporary directory's parent (tests)
|
|
78
|
+
*/
|
|
79
|
+
export async function installNodeFromNodeSource(report, options = {}) {
|
|
80
|
+
const exec = options.exec ?? execa;
|
|
81
|
+
const dir = await mkdtemp(path.join(options.tmpRoot ?? os.tmpdir(), 'lamaste-nodesource-'));
|
|
82
|
+
const setupScript = path.join(dir, 'nodesource_setup.sh');
|
|
83
|
+
try {
|
|
84
|
+
report('Downloading the NodeSource setup script...');
|
|
85
|
+
try {
|
|
86
|
+
await exec('curl', [
|
|
87
|
+
'-fsSL',
|
|
88
|
+
`https://deb.nodesource.com/setup_${NODE_MAJOR}.x`,
|
|
89
|
+
'-o',
|
|
90
|
+
setupScript,
|
|
91
|
+
]);
|
|
92
|
+
} catch (error) {
|
|
93
|
+
throw new Error(
|
|
94
|
+
`Failed to download the NodeSource setup script. Check your internet connection.\n${error.stderr || error.message}`,
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
report('Running the NodeSource setup...');
|
|
98
|
+
try {
|
|
99
|
+
await exec('bash', [setupScript]);
|
|
100
|
+
} catch (error) {
|
|
101
|
+
throw new Error(`NodeSource setup script failed.\n${error.stderr || error.message}`);
|
|
102
|
+
}
|
|
103
|
+
} finally {
|
|
104
|
+
await rm(dir, { recursive: true, force: true });
|
|
105
|
+
}
|
|
106
|
+
report('Running apt-get install nodejs...');
|
|
107
|
+
try {
|
|
108
|
+
await exec('apt-get', ['install', '-y', 'nodejs']);
|
|
109
|
+
} catch (error) {
|
|
110
|
+
throw new Error(
|
|
111
|
+
`Failed to install Node.js. Try running 'apt-get update' manually and retrying.\n${error.stderr || error.message}`,
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The key pin of the panel's IP certificate, for the installer's JSON
|
|
3
|
+
* `complete` event: the desktop app's local install stores it with the
|
|
4
|
+
* server and verifies `https://<ip>:9292` by it from its first call on —
|
|
5
|
+
* no trust on first use.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { X509Certificate, createHash } from 'node:crypto';
|
|
9
|
+
import { readFile } from 'node:fs/promises';
|
|
10
|
+
import { join } from 'node:path';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Base64 SHA-256 of the certificate's SubjectPublicKeyInfo — curl's
|
|
14
|
+
* `--pinnedpubkey sha256//<pin>` form, the one agents and the desktop app
|
|
15
|
+
* store (`panelServerPubkeySha256`).
|
|
16
|
+
*
|
|
17
|
+
* @param {string | Buffer} certificatePem
|
|
18
|
+
* @returns {string}
|
|
19
|
+
*/
|
|
20
|
+
export function panelKeyPin(certificatePem) {
|
|
21
|
+
const spki = new X509Certificate(certificatePem).publicKey.export({
|
|
22
|
+
type: 'spki',
|
|
23
|
+
format: 'der',
|
|
24
|
+
});
|
|
25
|
+
return createHash('sha256').update(spki).digest('base64');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The pin of `<pkiDir>/self-signed.pem` (written by `tasks/nginx.js`), or
|
|
30
|
+
* undefined when it cannot be read — the desktop then pins on first use.
|
|
31
|
+
*
|
|
32
|
+
* @param {string} pkiDir
|
|
33
|
+
* @returns {Promise<string | undefined>}
|
|
34
|
+
*/
|
|
35
|
+
export async function readPanelKeyPin(pkiDir) {
|
|
36
|
+
try {
|
|
37
|
+
return panelKeyPin(await readFile(join(pkiDir, 'self-signed.pem')));
|
|
38
|
+
} catch {
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -17,9 +17,14 @@ import {
|
|
|
17
17
|
AUTHELIA_USER,
|
|
18
18
|
CHISEL_GROUP,
|
|
19
19
|
CHISEL_UNIT_PATH,
|
|
20
|
+
HOOKS_DIR,
|
|
21
|
+
HOOKS_SERVICE_UNIT_PATH,
|
|
22
|
+
HOOKS_SOCKET_UNIT_PATH,
|
|
20
23
|
WEB_ROOT,
|
|
21
24
|
generateAutheliaUnit,
|
|
22
25
|
generateChiselUnit,
|
|
26
|
+
generateHooksServiceUnit,
|
|
27
|
+
generateHooksSocketUnit,
|
|
23
28
|
} from './service-config.js';
|
|
24
29
|
|
|
25
30
|
async function writeIfChanged(path, content) {
|
|
@@ -44,7 +49,7 @@ async function writeIfChanged(path, content) {
|
|
|
44
49
|
* `systemctl daemon-reload` and restarts what changed. Onboarding enables
|
|
45
50
|
* and starts chisel and Authelia on a fresh install.
|
|
46
51
|
*
|
|
47
|
-
* @param {{ configDir: string, pkiDir: string }} ctx
|
|
52
|
+
* @param {{ configDir: string, pkiDir: string, installDir: string }} ctx
|
|
48
53
|
* @param {(line: string) => void} report
|
|
49
54
|
* @returns {Promise<{ chiselChanged: boolean, autheliaChanged: boolean }>}
|
|
50
55
|
* whether the binary or unit of each changed (a running service needs a
|
|
@@ -100,6 +105,15 @@ export async function provisionRelayServices(ctx, report) {
|
|
|
100
105
|
const chiselUnitChanged = await writeIfChanged(CHISEL_UNIT_PATH, generateChiselUnit(ctx));
|
|
101
106
|
const autheliaUnitChanged = await writeIfChanged(AUTHELIA_UNIT_PATH, generateAutheliaUnit());
|
|
102
107
|
|
|
108
|
+
// Relay hook runners: template units only. lamaste-priv enables an
|
|
109
|
+
// instance per plugin and places its approved module under HOOKS_DIR,
|
|
110
|
+
// which stays root's; a running runner picks a new unit up when it next
|
|
111
|
+
// starts (it exits when idle).
|
|
112
|
+
report('Writing the relay hook runner units...');
|
|
113
|
+
await ensureDirectory(HOOKS_DIR, { uid: 0, gid: 0, mode: 0o755 });
|
|
114
|
+
await writeIfChanged(HOOKS_SOCKET_UNIT_PATH, generateHooksSocketUnit());
|
|
115
|
+
await writeIfChanged(HOOKS_SERVICE_UNIT_PATH, generateHooksServiceUnit(ctx));
|
|
116
|
+
|
|
103
117
|
return {
|
|
104
118
|
chiselChanged: chisel.changed || chiselUnitChanged,
|
|
105
119
|
autheliaChanged: authelia.changed || autheliaUnitChanged || autheliaDir.migrated,
|
|
@@ -301,6 +301,119 @@ WantedBy=multi-user.target
|
|
|
301
301
|
`;
|
|
302
302
|
}
|
|
303
303
|
|
|
304
|
+
/**
|
|
305
|
+
* Relay hook runners — one per plugin that brings relay lifecycle hooks
|
|
306
|
+
* (`lamalibre-lamaste-hooks@<route>`). The panel never imports plugin code;
|
|
307
|
+
* it calls the plugin's runner over a Unix socket, and the runner runs the
|
|
308
|
+
* plugin's approved hook module in the sandbox below. Template units, written
|
|
309
|
+
* by create-lamaste; `lamaste-priv hooks install|remove|restart <route>`
|
|
310
|
+
* enables an instance and places its approved module under
|
|
311
|
+
* {@link HOOKS_DIR}/<route>/ (root-owned). Must match the server subpath's
|
|
312
|
+
* `HOOK_RUNNER_SOCKET_DIR` in @lamalibre/lamaste.
|
|
313
|
+
*/
|
|
314
|
+
export const HOOKS_UNIT_PREFIX = 'lamalibre-lamaste-hooks';
|
|
315
|
+
export const HOOKS_SOCKET_UNIT_PATH = `/etc/systemd/system/${HOOKS_UNIT_PREFIX}@.socket`;
|
|
316
|
+
export const HOOKS_SERVICE_UNIT_PATH = `/etc/systemd/system/${HOOKS_UNIT_PREFIX}@.service`;
|
|
317
|
+
export const HOOKS_SOCKET_DIR = '/run/lamalibre-lamaste-hooks';
|
|
318
|
+
export const HOOKS_DIR = '/opt/lamalibre/lamaste/hooks';
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* The hook runner's socket: created by systemd, reachable by the panel's
|
|
322
|
+
* group only. The first connection starts the runner (socket activation),
|
|
323
|
+
* which exits when idle.
|
|
324
|
+
*
|
|
325
|
+
* @returns {string}
|
|
326
|
+
*/
|
|
327
|
+
export function generateHooksSocketUnit() {
|
|
328
|
+
return `[Unit]
|
|
329
|
+
Description=Lamaste relay hook runner socket (plugin %i)
|
|
330
|
+
|
|
331
|
+
[Socket]
|
|
332
|
+
ListenStream=${HOOKS_SOCKET_DIR}/%i.sock
|
|
333
|
+
SocketUser=root
|
|
334
|
+
SocketGroup=lamaste
|
|
335
|
+
SocketMode=0660
|
|
336
|
+
DirectoryMode=0755
|
|
337
|
+
RemoveOnStop=yes
|
|
338
|
+
Accept=no
|
|
339
|
+
Backlog=64
|
|
340
|
+
|
|
341
|
+
[Install]
|
|
342
|
+
WantedBy=sockets.target
|
|
343
|
+
`;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* The hook runner: Lamaste's runner program and one plugin's approved hook
|
|
348
|
+
* module, in a sandbox — a dynamic user with nothing of the relay's, a
|
|
349
|
+
* read-only file system with the panel's state, Authelia, certificates and
|
|
350
|
+
* nginx out of reach, no network (Unix sockets only, its own network
|
|
351
|
+
* namespace), and Node's permission model allowing reads of the runner and
|
|
352
|
+
* the module only (no child processes, workers, addons or writes).
|
|
353
|
+
* Standard output and error are discarded so nothing a hook prints — it sees
|
|
354
|
+
* application data — reaches the journal. `runner.env`, written by
|
|
355
|
+
* lamaste-priv, names the module.
|
|
356
|
+
*
|
|
357
|
+
* @param {{ installDir: string }} ctx
|
|
358
|
+
* @returns {string}
|
|
359
|
+
*/
|
|
360
|
+
export function generateHooksServiceUnit(ctx) {
|
|
361
|
+
const lib = `${ctx.installDir}/serverd/node_modules/@lamalibre/lamaste/dist/hooks`;
|
|
362
|
+
return `[Unit]
|
|
363
|
+
Description=Lamaste relay hook runner (plugin %i)
|
|
364
|
+
Requires=${HOOKS_UNIT_PREFIX}@%i.socket
|
|
365
|
+
After=${HOOKS_UNIT_PREFIX}@%i.socket
|
|
366
|
+
ConditionPathExists=${HOOKS_DIR}/%i/runner.env
|
|
367
|
+
|
|
368
|
+
[Service]
|
|
369
|
+
Type=simple
|
|
370
|
+
EnvironmentFile=${HOOKS_DIR}/%i/runner.env
|
|
371
|
+
ExecStart=/usr/bin/node --permission --allow-fs-read=${lib}/* --max-old-space-size=160 $LAMASTE_HOOK_NODE_ARGS ${lib}/runner.js --plugin %i --listen-fd 3 $LAMASTE_HOOK_RUNNER_ARGS
|
|
372
|
+
Restart=no
|
|
373
|
+
TimeoutStopSec=2
|
|
374
|
+
StandardOutput=null
|
|
375
|
+
StandardError=null
|
|
376
|
+
|
|
377
|
+
DynamicUser=yes
|
|
378
|
+
UMask=0077
|
|
379
|
+
NoNewPrivileges=yes
|
|
380
|
+
CapabilityBoundingSet=
|
|
381
|
+
AmbientCapabilities=
|
|
382
|
+
ProtectSystem=strict
|
|
383
|
+
ProtectHome=yes
|
|
384
|
+
PrivateTmp=yes
|
|
385
|
+
PrivateDevices=yes
|
|
386
|
+
PrivateIPC=yes
|
|
387
|
+
PrivateNetwork=yes
|
|
388
|
+
ProtectHostname=yes
|
|
389
|
+
ProtectClock=yes
|
|
390
|
+
ProtectKernelTunables=yes
|
|
391
|
+
ProtectKernelModules=yes
|
|
392
|
+
ProtectKernelLogs=yes
|
|
393
|
+
ProtectControlGroups=yes
|
|
394
|
+
ProtectProc=invisible
|
|
395
|
+
ProcSubset=pid
|
|
396
|
+
RestrictNamespaces=yes
|
|
397
|
+
RestrictRealtime=yes
|
|
398
|
+
RestrictSUIDSGID=yes
|
|
399
|
+
LockPersonality=yes
|
|
400
|
+
RemoveIPC=yes
|
|
401
|
+
RestrictAddressFamilies=AF_UNIX
|
|
402
|
+
IPAddressDeny=any
|
|
403
|
+
SystemCallArchitectures=native
|
|
404
|
+
SystemCallFilter=@system-service
|
|
405
|
+
SystemCallFilter=~@privileged
|
|
406
|
+
SystemCallErrorNumber=EPERM
|
|
407
|
+
TemporaryFileSystem=/run:ro /var:ro /srv:ro /mnt:ro /media:ro
|
|
408
|
+
InaccessiblePaths=-/etc/lamalibre -/etc/authelia -/etc/letsencrypt -/etc/nginx -/etc/ssh -/etc/sudoers.d -/etc/sudoers -/etc/shadow -/etc/gshadow
|
|
409
|
+
MemoryMax=256M
|
|
410
|
+
MemorySwapMax=0
|
|
411
|
+
CPUQuota=50%
|
|
412
|
+
TasksMax=32
|
|
413
|
+
LimitNOFILE=1024
|
|
414
|
+
`;
|
|
415
|
+
}
|
|
416
|
+
|
|
304
417
|
/** sites-available name of the port-80 catch-all. */
|
|
305
418
|
export const HTTP_REDIRECT_SITE = 'lamalibre-lamaste-http-redirect';
|
|
306
419
|
|