@lamalibre/create-lamaste 4.2.0 → 4.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/package.json +2 -2
  2. package/scripts/lamaste-priv +205 -2
  3. package/src/index.js +4 -4
  4. package/src/lib/node-version.js +114 -0
  5. package/src/lib/relay-services.js +15 -1
  6. package/src/lib/service-config.js +113 -0
  7. package/src/tasks/gatekeeper.js +8 -3
  8. package/src/tasks/node.js +34 -75
  9. package/src/tasks/redeploy.js +36 -0
  10. package/vendor/docs/00-introduction/how-it-works.md +2 -2
  11. package/vendor/docs/00-introduction/quickstart.md +5 -5
  12. package/vendor/docs/00-introduction/what-is-lamaste.md +5 -5
  13. package/vendor/docs/01-concepts/authentication.md +6 -3
  14. package/vendor/docs/01-concepts/authorization.md +29 -17
  15. package/vendor/docs/01-concepts/certificates.md +3 -3
  16. package/vendor/docs/01-concepts/mtls.md +2 -2
  17. package/vendor/docs/01-concepts/nginx-reverse-proxy.md +8 -3
  18. package/vendor/docs/01-concepts/security-model.md +98 -34
  19. package/vendor/docs/01-concepts/tickets.md +2 -2
  20. package/vendor/docs/01-concepts/tunneling.md +4 -4
  21. package/vendor/docs/02-guides/agent-setup.md +34 -16
  22. package/vendor/docs/02-guides/certificate-management.md +2 -2
  23. package/vendor/docs/02-guides/cloud-provisioning.md +3 -3
  24. package/vendor/docs/02-guides/desktop-app-setup.md +10 -10
  25. package/vendor/docs/02-guides/installation.md +3 -3
  26. package/vendor/docs/02-guides/onboarding.md +2 -2
  27. package/vendor/docs/02-guides/standalone-plugin-with-tickets.md +2 -2
  28. package/vendor/docs/03-architecture/installer-flow.md +2 -2
  29. package/vendor/docs/03-architecture/installer.md +4 -4
  30. package/vendor/docs/03-architecture/management-flow.md +1 -1
  31. package/vendor/docs/03-architecture/nginx-configuration.md +91 -26
  32. package/vendor/docs/03-architecture/overview.md +8 -8
  33. package/vendor/docs/03-architecture/panel-client.md +6 -6
  34. package/vendor/docs/03-architecture/panel-server.md +2 -2
  35. package/vendor/docs/03-architecture/state-management.md +2 -2
  36. package/vendor/docs/03-architecture/system-overview.md +1 -1
  37. package/vendor/docs/04-api-reference/agent-hosted-plugins.md +407 -0
  38. package/vendor/docs/04-api-reference/app-host.md +360 -0
  39. package/vendor/docs/04-api-reference/audit-and-notifications.md +57 -0
  40. package/vendor/docs/04-api-reference/certificates.md +95 -38
  41. package/vendor/docs/04-api-reference/gatekeeper.md +5 -3
  42. package/vendor/docs/04-api-reference/lifecycle-hooks.md +333 -0
  43. package/vendor/docs/04-api-reference/onboarding.md +1 -1
  44. package/vendor/docs/04-api-reference/overview.md +137 -111
  45. package/vendor/docs/04-api-reference/registry-and-oidc.md +7 -3
  46. package/vendor/docs/04-api-reference/tunnels.md +15 -13
  47. package/vendor/docs/05-operations/backup-and-restore.md +1 -1
  48. package/vendor/docs/05-operations/uninstalling.md +1 -1
  49. package/vendor/docs/05-operations/upgrades.md +25 -20
  50. package/vendor/docs/06-reference/config-files.md +190 -97
  51. package/vendor/docs/06-reference/glossary.md +3 -3
  52. package/vendor/docs/06-reference/installer-flags.md +13 -13
  53. package/vendor/docs/06-reference/ports-and-services.md +1 -1
  54. package/vendor/docs/06-reference/troubleshooting.md +33 -8
  55. package/vendor/docs/_index.json +20 -0
  56. package/vendor/docs/gatekeeper-design.md +22 -19
  57. package/vendor/docs/identity-directory-design.md +57 -56
  58. package/vendor/docs/package.json +1 -1
  59. package/vendor/docs/todo/lib-cli-desktop-refactor.md +27 -27
  60. package/vendor/gatekeeper/dist/lib/types.d.ts +12 -0
  61. package/vendor/gatekeeper/dist/lib/types.d.ts.map +1 -1
  62. package/vendor/gatekeeper/dist/server/index.d.ts +2 -0
  63. package/vendor/gatekeeper/dist/server/index.d.ts.map +1 -1
  64. package/vendor/gatekeeper/dist/server/index.js +47 -8
  65. package/vendor/gatekeeper/dist/server/index.js.map +1 -1
  66. package/vendor/gatekeeper/dist/server/routes/authz.d.ts.map +1 -1
  67. package/vendor/gatekeeper/dist/server/routes/authz.js +233 -141
  68. package/vendor/gatekeeper/dist/server/routes/authz.js.map +1 -1
  69. package/vendor/gatekeeper/package.json +4 -3
  70. package/vendor/server/bin/lamaste-server.js +18 -1
  71. package/vendor/server/package.json +2 -2
  72. package/vendor/server/src/commands/audit.js +60 -0
  73. package/vendor/server/src/commands/plugins.js +963 -12
  74. package/vendor/server/src/commands/push-install.js +317 -0
  75. package/vendor/server/src/commands/registry-credential.js +2 -1
  76. package/vendor/server/src/commands/uninstall.js +28 -1
  77. package/vendor/server/src/ndjson.js +4 -2
  78. package/vendor/server/src/panel-api.js +32 -4
  79. package/vendor/server-ui/dist/assets/index-Bc5NCMSv.css +1 -0
  80. package/vendor/server-ui/dist/assets/index-DBp3cmaU.js +84 -0
  81. package/vendor/server-ui/dist/index.html +2 -2
  82. package/vendor/serverd/package.json +2 -2
  83. package/vendor/serverd/src/index.js +56 -4
  84. package/vendor/serverd/src/lib/actor.js +24 -0
  85. package/vendor/serverd/src/lib/agent-jobs.js +330 -0
  86. package/vendor/serverd/src/lib/app-error.js +10 -0
  87. package/vendor/serverd/src/lib/audit.js +145 -0
  88. package/vendor/serverd/src/lib/hook-effects.js +123 -0
  89. package/vendor/serverd/src/lib/hook-pipelines.js +902 -0
  90. package/vendor/serverd/src/lib/migrations/state/0006_plugin_agent.sql +4 -0
  91. package/vendor/serverd/src/lib/migrations/state/0007_audit_and_notifications.sql +29 -0
  92. package/vendor/serverd/src/lib/migrations/state/0008_plugin_hooks.sql +70 -0
  93. package/vendor/serverd/src/lib/migrations/state/0009_plugin_agent_half.sql +53 -0
  94. package/vendor/serverd/src/lib/migrations/state/0010_plugin_routes_and_retained_data.sql +47 -0
  95. package/vendor/serverd/src/lib/migrations/state/0011_agent_job_signature.sql +14 -0
  96. package/vendor/serverd/src/lib/mtls.js +89 -8
  97. package/vendor/serverd/src/lib/nginx.js +298 -44
  98. package/vendor/serverd/src/lib/notifications.js +107 -0
  99. package/vendor/serverd/src/lib/plugin-hooks.js +560 -0
  100. package/vendor/serverd/src/lib/plugin-hosting.js +688 -0
  101. package/vendor/serverd/src/lib/plugin-lifecycle.js +603 -0
  102. package/vendor/serverd/src/lib/plugin-operations.js +295 -0
  103. package/vendor/serverd/src/lib/plugin-packages.js +249 -0
  104. package/vendor/serverd/src/lib/plugins.js +298 -68
  105. package/vendor/serverd/src/lib/push-install.js +88 -2
  106. package/vendor/serverd/src/lib/relay-hooks.js +100 -0
  107. package/vendor/serverd/src/lib/relay-signing.js +43 -0
  108. package/vendor/serverd/src/lib/request-hooks.js +79 -0
  109. package/vendor/serverd/src/lib/retained-data.js +99 -0
  110. package/vendor/serverd/src/lib/trust-proxy.js +34 -0
  111. package/vendor/serverd/src/lib/tunnel-hooks.js +88 -0
  112. package/vendor/serverd/src/lib/vhost-reconcile.js +260 -0
  113. package/vendor/serverd/src/middleware/twofa-session.js +11 -1
  114. package/vendor/serverd/src/routes/agent-forwarder.js +37 -0
  115. package/vendor/serverd/src/routes/management/agent-panel-authz.js +97 -0
  116. package/vendor/serverd/src/routes/management/agents.js +262 -1
  117. package/vendor/serverd/src/routes/management/audit.js +81 -0
  118. package/vendor/serverd/src/routes/management/certs.js +1 -15
  119. package/vendor/serverd/src/routes/management/plugins.js +569 -54
  120. package/vendor/serverd/src/routes/management/tunnels.js +26 -0
  121. package/vendor/serverd/src/routes/management.js +4 -0
  122. package/vendor/server-ui/dist/assets/index-B5Wp8lu-.js +0 -257
  123. package/vendor/server-ui/dist/assets/index-BqWWFlTU.css +0 -1
@@ -131,6 +131,48 @@ mTLS is stronger than a login page because:
131
131
  - There is no login endpoint to discover or attack
132
132
  - The rejection happens before any application code runs
133
133
 
134
+ #### 5c. The agent daemon (`lamaste-agentd`) and relay assertions
135
+
136
+ The agent daemon listens on `127.0.0.1:<port>` (9393 by default) of the agent machine. It is reached by the agent's owner directly and by the relay through two kinds of tunnel, and admits:
137
+
138
+ | Caller | Proof | Access |
139
+ | -------------------------------------------------------- | ------------------------------------------------------------------------ | --------------------------------------------------------------------------- |
140
+ | The agent's owning OS user (desktop app, scripts) | `Authorization: Bearer <token>` from `~/.lamalibre/lamaste/agentd.token` | Full |
141
+ | The admin certificate, on `agent-<label>.<domain>` | A relay assertion signed by the panel | Full |
142
+ | The agent's own certificate, on `agent-<label>.<domain>` | A relay assertion signed by the panel | The capabilities the panel lists for it; owner-only routes refused |
143
+ | An Authelia user, on a plugin tunnel (`type: 'plugin'`) | A relay assertion signed by Gatekeeper | That plugin's routes, never `/api/*`; the plugin sees the asserted identity |
144
+
145
+ **Threat model.** Binding to loopback is not an authentication boundary. Two kinds of process can open a connection to the daemon without going through nginx: any OS user on the agent machine (`127.0.0.1:<port>`), and any process on the relay (the tunnel's port on the relay's loopback, which Chisel carries to the agent). Up to 4.2 the daemon took its caller from request headers that nginx sets — `X-SSL-Client-Verify: SUCCESS` with `X-SSL-Client-DN: CN=admin` meant full control, plugin install included (code execution as the agent's user), and `Remote-User` meant any Authelia identity on plugin routes — so either kind of process could claim to be anyone. Its check of whether a request needs a caller also tested the raw URL, while the router decodes percent-escapes: `/%61pi/...` reached `/api/...` routes and `/%68erd/...` a plugin's routes with no credential at all.
146
+
147
+ **Relay assertions.** From 4.3 the relay vouches for each request it sends with `X-Lamalibre-Relay-Assertion` — an Ed25519 signature over the agent the request is for (`aud`), the tunnel port, the caller, the method, the target (the exact `$request_uri` for a certificate caller; the plugin route for an Authelia user, whose request nginx rewrites), the issue time, a nonce and the key id. The daemon verifies it against the relay's public key and refuses it for another agent or port, for another method or target, older than 60 seconds (5 seconds of clock skew), or seen before. The signing key, `/etc/lamalibre/lamaste/relay-signing-key.json` (0600, `lamaste`), is created by the panel at startup:
148
+
149
+ - **Agent-panel vhost:** nginx asks the panel about every request (`auth_request` to `GET /api/agent-panel/authz/<label>`, with the client certificate's verification result, the method and `$request_uri`). The panel's mTLS middleware refuses a revoked certificate — before, the agent panel could not check revocation at all — and the panel admits the admin certificate and the agent's own, then signs.
150
+ - **Plugin tunnels:** Gatekeeper, which already validates the Authelia session for them, signs for the user it found; the answer is never cached (an assertion is single use).
151
+
152
+ Agents hold only the public key. The daemon fetches it, with the label the relay knows the agent by (the audience of its assertions, which a local `--label` need not equal), from `GET /api/agents/me/relay-keys` over its pinned mTLS channel, keeps both in memory and fetches again when an assertion names an unknown key — at most once per 30 seconds. Nothing stored on an agent machine lets anyone mint an assertion, for that agent or another.
153
+
154
+ The owner's token is as good as the owner: the desktop app hands it to curl on stdin, never in a process's arguments, which other local users can read.
155
+
156
+ Headers that only claim a caller — `X-SSL-Client-*`, `Remote-*` and the assertion itself — are removed from every request before a route or plugin sees it; `Remote-*` are set again only from a verified user assertion, so a plugin reading identity headers (`@lamalibre/lamaste-identity@4.3.0`) reads the relay's word. Whether a request needs a caller follows the route the daemon matched, not the URL: every route does except the health check, the plugin bundles and the SPA's static files, and so does a path no route matches under an installed plugin's name (a plugin may answer it with its own not-found handler). The claim headers are removed from `rawHeaders` too.
157
+
158
+ **What remains.** The relay's loopback is still the relay's trust boundary: the panel itself trusts the `X-SSL-Client-*` headers nginx sends to `127.0.0.1:3100`, so a process on the relay that can forge those can obtain an assertion as well — what it could do to agents before 4.3. With panel 2FA on it reaches further into agents than into the panel: forged (or stolen) admin-certificate headers no longer pass the panel's own management routes, but the agent-panel authorization is exempt from 2FA (the browser on `agent-<label>.<domain>` carries no 2FA cookie), so they still mint admin assertions — full control of every agent with a panel tunnel. Agent panels are guarded by the client certificate alone, as before; authenticating nginx to the panel is the relay-wide fix. An assertion binds the agent, the port, the method and the target, not the request body: a process on the agent machine that holds the daemon's port while the daemon is down receives the relay's requests, and could replay one with another body if the daemon comes back within the assertion's 65 seconds.
159
+
160
+ #### 5d. The app host on agents
161
+
162
+ An agent half Lamaste installs runs on its agent as a service of its own, so something on the agent has to act as root: create an account and a unit, write under `/opt`, `/var/lib` and `/etc`, install system packages. That is `lamaste-apphost`, enabled once per machine by the agent's operator (`sudo lamaste-agent apphost enable`). See [App Host](../04-api-reference/app-host.md) for the helper, its files and the unit.
163
+
164
+ **What the sudoers rule grants.** The agent's user may run one root-owned program, `/usr/local/sbin/lamaste-apphost`, with any arguments, a reset environment and Node.js's code-loading variables deleted. The program validates every argument, derives every path from the route and the agent label, takes content on stdin, reads nothing the agent's user can write, and does five things: install a version, write a plugin config, switch the current version, remove, report status. It never runs plugin code: the agent half runs as its own account (`lamaste-app-<route>`) under a hardened unit — no capabilities, `NoNewPrivileges`, the file system read-only but its data directory, its code root-owned 0644. `enable` installs the program only from a root-owned `lamaste-agent` install, so root never runs or copies code another user could have changed.
165
+
166
+ **Why jobs are relay-signed.** Without a signature, the agent's user — or whoever takes its account — could have the helper install any package it can produce with any system packages, activate any kept version, or purge data, as root. Approval of what an agent half brings happens on the relay, so the relay signs every `app-install`, `app-activate` and `app-remove` with its Ed25519 relay key when it creates the job, over the agent's label, the route, the job id, the kind, the payload (the exact package, version, tarball integrity, service and system packages; the version and package to activate; the purge flag and package) and a deadline of at most an hour. The helper verifies it with the key pinned for that agent, compares the payload with what it is asked, and runs each job id once. The agent's sync carries the signature from `agent-config` to the helper; it can neither make nor change one.
167
+
168
+ **The pin is trust on first use.** `enable` and `pin` fetch the relay's key as the agent's user, over that agent's pinned mTLS channel, and root checks the label and the key's shape: a label of that user's agents — from the user's agent registry, which root reads itself as a file that user owns, and never a label root's own agents use when the user is another, since a pin is per label for the whole machine — and an Ed25519 key whose id is derived from it. An agent account that is already compromised at that moment could hand root its own key for its own labels. Compare the pinned id with the relay's once: on the relay the panel logs it at start-up (`Relay assertion signing key ready`, `kid`) and `GET /api/agents/me/relay-keys` with the admin certificate returns it; on the agent `sudo lamaste-agent apphost status` lists each label's pin. The panel shows the first 12 hex digits each agent reports pinned and does not let an agent whose pin is not the relay's key host an agent half. A pin is a root-owned file in root-owned directories; the helper refuses one anyone else could have written or linked. After the relay's key is replaced, the helper refuses its jobs until `sudo lamaste-agent apphost pin` runs.
169
+
170
+ **Owner records.** A route belongs to the agent label and the agent package that first installed it — a root-owned record outside the agent half's data. Another label, or another package, is refused that route and the data kept with it until the owner purges it; the account an agent half runs as names its route, and an account that does not is never reused, given files or deleted. A relay can therefore neither take over another relay's agent half on a shared machine nor hand one package's data to another.
171
+
172
+ **Several agents on one machine.** Agents of different relays may share a machine and its helper. The rule names one OS user, so they run as that user (or root) — `enable` for another user is refused unless `--force`, since the first user's agents could no longer manage their agent halves; each label has its own pin, and a relay's signature names its own label for the agent, so a job signed for one agent is not run as another's.
173
+
174
+ **What a compromised agent account can still do:** read and rewrite the plugin configs it already holds (`config` is not signed) — breaking the admin API of that agent half; run `status`; run a signed job it received when it likes before the job expires, or not at all; report results that are not true (the relay's view of what runs on the agent is the agent's report); stop its tunnel and its sync. **What it cannot:** run anything as root beyond those fixed operations; install, switch or remove anything the relay did not sign, or anything other than exactly what it signed; replay a job; pin a key (root only); read or write an agent half's data directory (0700, another account); act on a route another label or package owns. **A compromised relay** can do on its agents what its signature allows: install any `@lamalibre/` package from its registry at any version, with system packages from the agent's configured apt sources, running as a service account — never plugin code as root — and remove or purge the agent halves it owns.
175
+
134
176
  #### 6. TOTP 2FA for app access
135
177
 
136
178
  Visitors to your tunneled apps authenticate through Authelia with a password and a TOTP code from their phone. See [Authentication](authentication.md) for full details.
@@ -420,6 +462,10 @@ Sensitive files use restrictive permissions:
420
462
  | `~/.lamalibre/lamaste/agents/<label>/chisel.json`, `chisel.env` | `600` | Agent's Chisel credential (agent machine) |
421
463
  | `~/Library/LaunchAgents/com.lamalibre.lamaste.chisel-<label>.plist` | `600` | Carries `AUTH` in its environment (macOS agent) |
422
464
  | `/etc/lamalibre/lamaste/pki/` | `700` | PKI directory itself |
465
+ | `/etc/sudoers.d/lamaste-apphost` | `440` | The app host rule (agent machine) |
466
+ | `/etc/lamalibre/apps/<route>/plugin-config.json` | `600` | An agent half's port and secret; its account |
467
+ | `/var/lib/lamalibre/apps/<route>/` | `700` | An agent half's data; its account |
468
+ | `/var/lib/lamalibre/apps/.jobs/`, `.trash/` | `700` | Signed jobs that ran; purged data (root) |
423
469
 
424
470
  Mode `600` means only the file owner can read or write. Mode `640` adds read access for the file's group, `660` also write access. Mode `700` means only the directory owner can list, read, or modify contents. See [Configuration Files](../06-reference/config-files.md#file-permissions-table) for the owner of every directory.
425
471
 
@@ -441,15 +487,21 @@ There is no rule for `mv`, `cp`, `rm`, `chmod`, `chown`, `mkdir`, `cat`, `find`,
441
487
  - **Binaries, units, accounts.** Chisel and Authelia (pinned, digest-verified releases), their systemd units and the service accounts are installed by `create-lamaste`, which runs as root. The panel never writes a unit or a binary; onboarding only checks they exist.
442
488
  - **Code.** `/opt/lamalibre/lamaste` (panel, gatekeeper, `lamaste-server` CLI, UI, docs) is root-owned and read-only for `lamaste`, so a compromised panel cannot rewrite code that root later runs through `sudo lamaste-server`.
443
489
 
490
+ #### Plugin code on the relay: the hook runner
491
+
492
+ Server plugins run inside the panel (the `@lamalibre/` scope is their trust boundary). Agent-hosted plugins never do: the panel does not import them. Their **relay hooks** — code an administrator approves by digest — run in `lamalibre-lamaste-hooks@<route>.service`: a systemd dynamic user, a read-only file system without Lamaste's state, Authelia, certificates, nginx or SSH configuration, its own network namespace with Unix sockets only, no capabilities, Node's permission model allowing reads of the runner and the approved module only, an empty environment, and its output discarded. The panel calls it over a socket only the `lamaste` group can open; a hook can affect nothing but its answer and the rate-limited effects Lamaste performs for it. See [Lifecycle Hooks](../04-api-reference/lifecycle-hooks.md#the-hook-runner).
493
+
444
494
  #### `lamaste-priv`
445
495
 
446
496
  `/usr/local/sbin/lamaste-priv` is a root-owned (`0755`), zero-dependency Node.js program installed by `create-lamaste`. Content (vhost text, TOTP secrets) arrives on stdin, never on the command line, so it is not written to the sudo log. It reads nothing the `lamaste` user can write.
447
497
 
448
- | Operation | Does |
449
- | ----------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
450
- | `nginx-site write\|backup\|restore\|discard-backup\|enable\|disable\|remove <name>` | Manage `sites-available/<name>` (and its `.bak`) and its `sites-enabled` link. `write` validates the vhost on stdin first. |
451
- | `authelia-totp <username>` | Store the TOTP secret from stdin with Authelia's CLI, run as `lamaste-authelia` (the database is that account's file) |
452
- | `self-update <version>` | Start `npx @lamalibre/create-lamaste@<version> --yes` in a transient systemd unit two seconds later (`MAJOR.MINOR.PATCH` only) |
498
+ | Operation | Does |
499
+ | ----------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
500
+ | `nginx-site write\|backup\|restore\|discard-backup\|enable\|disable\|remove <name>` | Manage `sites-available/<name>` (and its `.bak`) and its `sites-enabled` link. `write` validates the vhost on stdin first. |
501
+ | `authelia-totp <username>` | Store the TOTP secret from stdin with Authelia's CLI, run as `lamaste-authelia` (the database is that account's file) |
502
+ | `self-update <version>` | Start `npx @lamalibre/create-lamaste@<version> --yes` in a transient systemd unit two seconds later (`MAJOR.MINOR.PATCH` only) |
503
+ | `hooks install <route> <sha256\|none>` | Write a plugin's approved relay hook module (stdin, checked against the digest) root-owned to `/opt/lamalibre/lamaste/hooks/<route>/`, enable its runner socket |
504
+ | `hooks activate\|restart\|remove <route> [sha256]` | Point the runner back at the kept previous module; stop a stuck runner; disable it and delete its modules |
453
505
 
454
506
  Site names are limited to the panel's own vhosts: `lamalibre-lamaste-panel-domain`, `-auth`, `-tunnel`, `-app-<subdomain>`, `-agent-panel-<label>` and `-site-<uuid>`. `lamalibre-lamaste-panel-ip` (written by `create-lamaste`) may only be enabled or disabled.
455
507
 
@@ -509,40 +561,52 @@ Configuration files that are read live by services (like Authelia's `users.yml`)
509
561
 
510
562
  ### Source files
511
563
 
512
- | File | Purpose |
513
- | ---------------------------------------------------------- | ------------------------------------------------------ |
514
- | `packages/create-lamaste/src/tasks/harden.js` | Swap, UFW, fail2ban, SSH hardening |
515
- | `packages/create-lamaste/src/tasks/mtls.js` | PKI generation, file permissions |
516
- | `packages/create-lamaste/src/tasks/nginx.js` | mTLS snippet, self-signed cert |
517
- | `packages/lamaste-serverd/src/lib/authelia.js` | bcrypt hashing, atomic writes |
518
- | `packages/lamaste-serverd/src/lib/mtls.js` | Certificate rotation with rollback |
519
- | `packages/lamaste-serverd/src/lib/nginx.js` | Vhost write-with-rollback pattern |
520
- | `packages/lamaste-serverd/src/middleware/mtls.js` | Request-level mTLS check |
521
- | `packages/lamaste-serverd/src/lib/totp.js` | Panel 2FA TOTP verification and replay protection |
522
- | `packages/lamaste-serverd/src/lib/session.js` | HMAC-SHA256 session cookie signing |
523
- | `packages/lamaste-serverd/src/middleware/twofa-session.js` | 2FA session enforcement (after mTLS, before roleGuard) |
564
+ | File | Purpose |
565
+ | ------------------------------------------------------------------- | ------------------------------------------------------ |
566
+ | `packages/create-lamaste/src/tasks/harden.js` | Swap, UFW, fail2ban, SSH hardening |
567
+ | `packages/create-lamaste/src/tasks/mtls.js` | PKI generation, file permissions |
568
+ | `packages/create-lamaste/src/tasks/nginx.js` | mTLS snippet, self-signed cert |
569
+ | `packages/lamaste-serverd/src/lib/authelia.js` | bcrypt hashing, atomic writes |
570
+ | `packages/lamaste-serverd/src/lib/mtls.js` | Certificate rotation with rollback |
571
+ | `packages/lamaste-serverd/src/lib/nginx.js` | Vhost write-with-rollback pattern |
572
+ | `packages/lamaste-serverd/src/middleware/mtls.js` | Request-level mTLS check |
573
+ | `packages/lamaste-serverd/src/lib/totp.js` | Panel 2FA TOTP verification and replay protection |
574
+ | `packages/lamaste-serverd/src/lib/session.js` | HMAC-SHA256 session cookie signing |
575
+ | `packages/lamaste-serverd/src/middleware/twofa-session.js` | 2FA session enforcement (after mTLS, before roleGuard) |
576
+ | `packages/core/lib/src/relay-assertion.ts` | Relay assertions: signing key file, sign, verify |
577
+ | `packages/core/lib/src/server/agent-panel-authz.ts` | Who may use an agent panel through the relay |
578
+ | `packages/server/daemon/src/routes/management/agent-panel-authz.js` | The agent-panel vhost's `auth_request` target |
579
+ | `packages/sdks/gatekeeper/src/server/routes/authz.ts` | Assertions for Authelia users on plugin tunnels |
580
+ | `packages/agent/daemon/src/lib/auth.js` | lamaste-agentd's authentication hook |
581
+ | `packages/core/lib/src/app-job-signature.ts` | Relay-signed app jobs: envelope, sign, verify |
582
+ | `packages/agent/cli/scripts/lamaste-apphost` | The app host helper (root, Node.js built-ins only) |
583
+ | `packages/agent/cli/src/lib/apphost-setup.js` | Root-owned install check, sudoers rule, relay key pins |
584
+ | `packages/server/daemon/src/lib/agent-jobs.js` | Agent jobs; app jobs signed when created |
524
585
 
525
586
  ## Quick Reference
526
587
 
527
588
  ### Security layers
528
589
 
529
- | Layer | Technology | Blocks |
530
- | -------------------- | ------------------------------------ | ------------------------------------- |
531
- | Firewall | UFW | Connections to non-allowed ports |
532
- | Intrusion prevention | fail2ban | Repeated failed login attempts |
533
- | SSH hardening | sshd_config | Password-based SSH access |
534
- | TLS encryption | Let's Encrypt / self-signed | Traffic interception and tampering |
535
- | Admin auth | mTLS client certificates | Unauthorized admin access |
536
- | Admin 2FA (optional) | Built-in TOTP | Stolen certificate abuse |
537
- | Agent-to-agent auth | Tickets (time-limited, single-use) | Unauthorized cross-agent access |
538
- | Tunnel ownership | Chisel authfile per-agent grants | One agent hijacking another's host |
539
- | Reserved ports | Tunnel port validation | Publishing internal services |
540
- | Privileged commands | No-wildcard sudoers, `lamaste-priv` | Root code execution via extra flags |
541
- | Relay authenticity | Agent verifies `tunnel.<domain>` TLS | Relay impersonation, credential theft |
542
- | App auth | Authelia TOTP 2FA | Unauthorized app access |
543
- | Service isolation | `127.0.0.1` binding | Direct access to internal services |
544
- | File permissions | `chmod 600` | Unauthorized secret access |
545
- | Atomic writes | `mv` pattern | Partial file reads by services |
590
+ | Layer | Technology | Blocks |
591
+ | -------------------- | --------------------------------------- | ------------------------------------- |
592
+ | Firewall | UFW | Connections to non-allowed ports |
593
+ | Intrusion prevention | fail2ban | Repeated failed login attempts |
594
+ | SSH hardening | sshd_config | Password-based SSH access |
595
+ | TLS encryption | Let's Encrypt / self-signed | Traffic interception and tampering |
596
+ | Admin auth | mTLS client certificates | Unauthorized admin access |
597
+ | Admin 2FA (optional) | Built-in TOTP | Stolen certificate abuse |
598
+ | Agent-to-agent auth | Tickets (time-limited, single-use) | Unauthorized cross-agent access |
599
+ | Tunnel ownership | Chisel authfile per-agent grants | One agent hijacking another's host |
600
+ | Reserved ports | Tunnel port validation | Publishing internal services |
601
+ | Privileged commands | No-wildcard sudoers, `lamaste-priv` | Root code execution via extra flags |
602
+ | Relay authenticity | Agent verifies `tunnel.<domain>` TLS | Relay impersonation, credential theft |
603
+ | Agent daemon auth | Relay assertions (Ed25519), owner token | Callers forged on an agent's loopback |
604
+ | Agent root helper | Relay-signed app jobs, pinned key | Root actions the relay did not sign |
605
+ | Agent half sandbox | Own account, hardened systemd unit | Plugin code as root |
606
+ | App auth | Authelia TOTP 2FA | Unauthorized app access |
607
+ | Service isolation | `127.0.0.1` binding | Direct access to internal services |
608
+ | File permissions | `chmod 600` | Unauthorized secret access |
609
+ | Atomic writes | `mv` pattern | Partial file reads by services |
546
610
 
547
611
  ### Firewall rules
548
612
 
@@ -264,9 +264,9 @@ The integration flow:
264
264
  }
265
265
  ```
266
266
 
267
- ### Client SDK (`@lamalibre/lamaste-tickets@4.2.0`)
267
+ ### Client SDK (`@lamalibre/lamaste-tickets@4.3.0`)
268
268
 
269
- The `@lamalibre/lamaste-tickets@4.2.0` package is a TypeScript SDK that provides the client-side ticket lifecycle for plugins and agents. It uses `undici` for HTTP with mTLS dispatcher support and has no other runtime dependencies.
269
+ The `@lamalibre/lamaste-tickets@4.3.0` package is a TypeScript SDK that provides the client-side ticket lifecycle for plugins and agents. It uses `undici` for HTTP with mTLS dispatcher support and has no other runtime dependencies.
270
270
 
271
271
  The SDK exports three main classes:
272
272
 
@@ -144,7 +144,7 @@ On the API side the same rule holds: `GET /api/tunnels/agent-config` returns onl
144
144
 
145
145
  ### Client configuration
146
146
 
147
- The Chisel client runs as a user-level service managed by `lamaste-agent` (and `lamaste-agentd`): a LaunchAgent plist on macOS, a systemd **user** unit on Linux. Both are generated by one implementation in `@lamalibre/lamaste@4.2.0/agent`. The client command looks like:
147
+ The Chisel client runs as a user-level service managed by `lamaste-agent` (and `lamaste-agentd`): a LaunchAgent plist on macOS, a systemd **user** unit on Linux. Both are generated by one implementation in `@lamalibre/lamaste@4.3.0/agent`. The client command looks like:
148
148
 
149
149
  ```bash
150
150
  chisel client \
@@ -208,7 +208,7 @@ location / {
208
208
 
209
209
  ### Concurrency and locking
210
210
 
211
- Every tunnel workflow — create, delete, toggle, assign, reconfigure, the combined `PATCH`, releasing a revoked agent's tunnels, and startup reconciliation — runs under one tunnel lock (`withTunnelLock` in `@lamalibre/lamaste@4.2.0/server`'s `tunnels.ts`), with its validation inside the lock. Two concurrent requests therefore cannot both pass a uniqueness check for the same subdomain or port. Authfile renders are additionally serialized with a keyed promise-chain mutex in `chisel-users.ts`, so enrollments and credential rotations never interleave authfile writes with tunnel changes.
211
+ Every tunnel workflow — create, delete, toggle, assign, reconfigure, the combined `PATCH`, releasing a revoked agent's tunnels, and startup reconciliation — runs under one tunnel lock (`withTunnelLock` in `@lamalibre/lamaste@4.3.0/server`'s `tunnels.ts`), with its validation inside the lock. Two concurrent requests therefore cannot both pass a uniqueness check for the same subdomain or port. Authfile renders are additionally serialized with a keyed promise-chain mutex in `chisel-users.ts`, so enrollments and credential rotations never interleave authfile writes with tunnel changes.
212
212
 
213
213
  ### Startup reconciliation
214
214
 
@@ -223,7 +223,7 @@ It **fails closed**: if the authfile cannot be established, Chisel is stopped **
223
223
 
224
224
  ### Installation
225
225
 
226
- On the server, Chisel is installed by `npx @lamalibre/create-lamaste@4.2.0` — as root, together with its hardened systemd unit (`User=nobody`, `Group=lamaste-chisel`, `NoNewPrivileges`, `ProtectSystem=strict`, empty `CapabilityBoundingSet`) — and kept at the pinned release by every redeploy. The panel never installs a binary or writes the unit; onboarding only checks the binary exists, creates the server key and authfile, and starts the service. Both the server installer and the agents download the pinned release (`CHISEL_RELEASE` in `@lamalibre/lamaste@4.2.0`'s `constants.ts`) from its fixed URL, with no GitHub API lookup and no "latest":
226
+ On the server, Chisel is installed by `npx @lamalibre/create-lamaste@4.3.0` — as root, together with its hardened systemd unit (`User=nobody`, `Group=lamaste-chisel`, `NoNewPrivileges`, `ProtectSystem=strict`, empty `CapabilityBoundingSet`) — and kept at the pinned release by every redeploy. The panel never installs a binary or writes the unit; onboarding only checks the binary exists, creates the server key and authfile, and starts the service. Both the server installer and the agents download the pinned release (`CHISEL_RELEASE` in `@lamalibre/lamaste@4.3.0`'s `constants.ts`) from its fixed URL, with no GitHub API lookup and no "latest":
227
227
 
228
228
  ```
229
229
  https://github.com/jpillora/chisel/releases/download/v1.12.0/chisel_1.12.0_<os>_<arch>.gz
@@ -320,7 +320,7 @@ sudo systemctl restart chisel
320
320
 
321
321
  ```bash
322
322
  # Install globally, then set up the agent (interactive)
323
- npm install -g @lamalibre/lamaste-agent@4.2.0
323
+ npm install -g @lamalibre/lamaste-agent@4.3.0
324
324
  lamaste-agent setup --label <label>
325
325
 
326
326
  # Apply tunnel changes now (the sync timer does it within 30 s anyway)
@@ -59,7 +59,7 @@ The token is single-use and expires after 10 minutes. `tunnels:read` is enough f
59
59
  Install the agent **globally** first:
60
60
 
61
61
  ```bash
62
- npm install -g @lamalibre/lamaste-agent@4.2.0
62
+ npm install -g @lamalibre/lamaste-agent@4.3.0
63
63
  ```
64
64
 
65
65
  A global install is required, not a convenience. Setup installs a sync timer (see [How tunnel changes reach the agent](#how-tunnel-changes-reach-the-agent)) that runs the installed `lamaste-agent` every 30 seconds for as long as the agent exists. An `npx` copy lives in npm's cache, which npm may clear at any time — the timer would silently stop. `setup` and `update` therefore refuse to run from `npx`, and they check this **before** an enrollment token is spent. For an installation npm does not manage, set `LAMALIBRE_LAMASTE_AGENT_CLI_PATH` to the absolute path of the `lamaste-agent` script the timer should run.
@@ -117,7 +117,7 @@ The relay only lets an agent open the ports of the enabled tunnels it carries, a
117
117
  4. The service definition lists exactly the tunnels the relay grants this agent.
118
118
  5. The client runs when there is something to carry, and is stopped when the agent carries no tunnel or the operator stopped it.
119
119
 
120
- When everything already matches, a run costs one panel request and changes nothing. The timer runs it:
120
+ When everything already matches, a run costs one panel request and changes nothing. After converging, a run also reports the agent's host facts when they changed and runs the jobs the relay listed for the plugins this agent hosts — lifecycle hook calls, and installing, switching or removing an agent half ([App Host](../04-api-reference/app-host.md)); an install may take up to 10 minutes, so systemd allows a run 20 (`TimeoutStartSec=20min`). The timer runs it:
121
121
 
122
122
  | Platform | Units | Schedule |
123
123
  | -------- | -------------------------------------------------------------------------------------------- | ------------------------------------------------------------- |
@@ -148,7 +148,7 @@ Each agent authenticates to the relay with its own chisel credential, stored in
148
148
  Upgrade agents **before** the server. Agents from before this version reject the new shape of the relay's Chisel arguments; a current agent works against an older server, keeping its credential until the server offers rotation.
149
149
 
150
150
  ```bash
151
- npm install -g @lamalibre/lamaste-agent@4.2.0
151
+ npm install -g @lamalibre/lamaste-agent@4.3.0
152
152
  lamaste-agent update
153
153
  ```
154
154
 
@@ -167,21 +167,39 @@ lamaste-agent switch <label> # change the default agent
167
167
  lamaste-agent uninstall --label <l> # stop and remove one agent, including its sync timer
168
168
  ```
169
169
 
170
+ ## Hosting app plugins (Linux)
171
+
172
+ To let the panel install the agent halves of app plugins on this machine — their accounts, units and directories — enable the app host helper once, from a root-owned install:
173
+
174
+ ```bash
175
+ sudo npm install -g @lamalibre/lamaste-agent@4.3.0
176
+ sudo lamaste-agent apphost enable # the agent runs as the user who ran sudo
177
+ lamaste-agent apphost status # helper, pinned relay keys, installed agent halves
178
+ sudo lamaste-agent apphost pin # after the relay rotated its signing key
179
+ ```
180
+
181
+ The agent reports it on its next sync, and the panel can then host plugins whose agent half Lamaste installs here, while this agent's push-install window is open. `lamaste-agent uninstall` refuses while agent halves are installed for the agents it would remove. See [App Host](../04-api-reference/app-host.md).
182
+
170
183
  ## Troubleshooting
171
184
 
172
- | Symptom | Cause and fix |
173
- | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
174
- | `setup` says the agent must be installed globally | It was run through `npx`. Run `npm install -g @lamalibre/lamaste-agent@4.2.0`, then `lamaste-agent setup ...` again. The enrollment token was not spent. |
175
- | Log shows a TLS / x509 error | `tunnel.<domain>` does not present a valid certificate for its name. Check the relay's certificate (`Certificates` page) and that the agent's clock is correct. |
176
- | Log shows authentication failed | The credential was rotated on the panel. The next sync picks it up; `lamaste-agent chisel refresh-credential` applies it now. |
177
- | Log shows `access denied` for a remote | The relay no longer grants this agent that port: the tunnel was disabled, deleted, or moved to another agent. The next sync drops it. If it persists, check `lamaste-agent status` — the sync timer may not be running. |
178
- | `status` shows `Sync: not installed` | The agent was set up before the timer existed. Install globally, then run `lamaste-agent update`. |
179
- | `status` shows `Sync: last ran ... not running its program` | The timer's last run is over 2 minutes old: the timer was unloaded, or the program it runs is gone (Node.js or the agent was moved or uninstalled). Run `lamaste-agent update` from the global install to rewrite it. |
180
- | `status` shows `Sync: failing since ...` | The error is the last sync's. `The panel reports domain X, but this agent is enrolled with Y` means the panel moved to another domain: re-enroll the agent. Otherwise check the panel is reachable. Details in `logs/sync.log`. |
181
- | Tunnels do not start although the agent carries some | The tunnels were stopped from the agent panel or desktop app, and the timer respects that. Start them there. |
182
- | Tunnel works until reboot | Lingering is off. See step 3. |
183
- | New tunnel returns 502 | The agent has not synced yet (up to ~30 s), or nothing listens on `127.0.0.1:<port>` on the agent. Run `lamaste-agent update` to apply at once. |
184
- | Upload fails with 413 | The request is larger than the tunnel's body limit. Raise **Largest request body** on the tunnel (`maxBodySizeMb`). |
185
+ | Symptom | Cause and fix |
186
+ | ----------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
187
+ | `setup` says the agent must be installed globally | It was run through `npx`. Run `npm install -g @lamalibre/lamaste-agent@4.3.0`, then `lamaste-agent setup ...` again. The enrollment token was not spent. |
188
+ | Log shows a TLS / x509 error | `tunnel.<domain>` does not present a valid certificate for its name. Check the relay's certificate (`Certificates` page) and that the agent's clock is correct. |
189
+ | Log shows authentication failed | The credential was rotated on the panel. The next sync picks it up; `lamaste-agent chisel refresh-credential` applies it now. |
190
+ | Log shows `access denied` for a remote | The relay no longer grants this agent that port: the tunnel was disabled, deleted, or moved to another agent. The next sync drops it. If it persists, check `lamaste-agent status` — the sync timer may not be running. |
191
+ | `status` shows `Sync: not installed` | The agent was set up before the timer existed. Install globally, then run `lamaste-agent update`. |
192
+ | `status` shows `Sync: last ran ... not running its program` | The timer's last run is over 2 minutes old: the timer was unloaded, or the program it runs is gone (Node.js or the agent was moved or uninstalled). Run `lamaste-agent update` from the global install to rewrite it. |
193
+ | `status` shows `Sync: failing since ...` | The error is the last sync's. `The panel reports domain X, but this agent is enrolled with Y` means the panel moved to another domain: re-enroll the agent. Otherwise check the panel is reachable. Details in `logs/sync.log`. |
194
+ | Tunnels do not start although the agent carries some | The tunnels were stopped from the agent panel or desktop app, and the timer respects that. Start them there. |
195
+ | Tunnel works until reboot | Lingering is off. See step 3. |
196
+ | New tunnel returns 502 | The agent has not synced yet (up to ~30 s), or nothing listens on `127.0.0.1:<port>` on the agent. Run `lamaste-agent update` to apply at once. |
197
+ | Upload fails with 413 | The request is larger than the tunnel's body limit. Raise **Largest request body** on the tunnel (`maxBodySizeMb`). |
198
+ | `uninstall` refuses: agent halves are installed | Agent halves run here for this agent. Unhost or uninstall those plugins on the panel (the next sync removes them), or `sudo lamaste-agent apphost disable --force` (data is kept). |
199
+ | `apphost enable` says the install is not root-owned | Root installs the helper only from code no other user can change: `sudo npm install -g @lamalibre/lamaste-agent@4.3.0`, then `sudo lamaste-agent apphost enable` from that install. |
200
+ | `apphost enable` says the helper serves another user | App hosting serves one OS user per machine. Unhost or uninstall the plugins that user's agents host (`sudo lamaste-agent apphost status` lists their agent halves), then `sudo lamaste-agent apphost enable --user <name> --force`. |
201
+ | `apphost pin` says a label is not an agent of the user | Root pins only labels the agent user's registry lists. Pin as the user the agent runs as (`--user <name>`; `--user root` for an agent running as root). |
202
+ | Hosting refused: the helper trusts another relay key | The relay's signing key was replaced, or the agent enrolled again. Run `sudo lamaste-agent apphost pin`; compare the key id with the relay's ([App Host](../04-api-reference/app-host.md#pinning-the-relay-key)). |
185
203
 
186
204
  ## For Developers
187
205
 
@@ -196,7 +196,7 @@ The admin certificate (`CN=admin`) has unrestricted access to all panel endpoint
196
196
 
197
197
  ### Distribute to the Agent Machine
198
198
 
199
- Send the `.p12` file and password to the agent machine (macOS or Linux) securely, and install the agent there with `npm install -g @lamalibre/lamaste-agent@4.2.0` and run `lamaste-agent setup --label <label>` — or import it into the Desktop App. An enrollment token is preferable where possible, because the private key is then generated on the agent and never travels. See [Agent Setup](agent-setup.md) for details.
199
+ Send the `.p12` file and password to the agent machine (macOS or Linux) securely, and install the agent there with `npm install -g @lamalibre/lamaste-agent@4.3.0` and run `lamaste-agent setup --label <label>` — or import it into the Desktop App. An enrollment token is preferable where possible, because the private key is then generated on the agent and never travels. See [Agent Setup](agent-setup.md) for details.
200
200
 
201
201
  ### Update Capabilities
202
202
 
@@ -293,7 +293,7 @@ Revocation is immediate — the certificate serial is added to a revocation list
293
293
 
294
294
  ### mTLS Rotation Internals
295
295
 
296
- Admin certificate rotation lives only in `lamaste-server reset-admin` (`packages/server/cli/src/commands/reset-admin.js`), run as root on the server. The panel's `rotateClientCert()` in `lib/mtls.js` is a stub that returns `503` with that instruction; `@lamalibre/lamaste@4.2.0/server` no longer exports a rotation function. The PKI directory belongs to `lamaste`, so `reset-admin` runs every file operation inside it (`openssl genrsa`/`req`/`x509 -req`/`pkcs12`, `cp`, `chmod`) as `lamaste`, and writes `panel.json` and `revoked.json` as fresh files renamed into place — root never writes through a path the panel could have replaced with a symlink.
296
+ Admin certificate rotation lives only in `lamaste-server reset-admin` (`packages/server/cli/src/commands/reset-admin.js`), run as root on the server. The panel's `rotateClientCert()` in `lib/mtls.js` is a stub that returns `503` with that instruction; `@lamalibre/lamaste@4.3.0/server` no longer exports a rotation function. The PKI directory belongs to `lamaste`, so `reset-admin` runs every file operation inside it (`openssl genrsa`/`req`/`x509 -req`/`pkcs12`, `cp`, `chmod`) as `lamaste`, and writes `panel.json` and `revoked.json` as fresh files renamed into place — root never writes through a path the panel could have replaced with a symlink.
297
297
 
298
298
  The PKCS12 bundle uses legacy encryption settings (`-keypbe PBE-SHA1-3DES -certpbe PBE-SHA1-3DES -macalg sha1`) because newer OpenSSL defaults produce files that macOS Keychain Access cannot import.
299
299
 
@@ -14,7 +14,7 @@ Before you start, make sure you have:
14
14
 
15
15
  | Requirement | Details |
16
16
  | ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
17
- | **Desktop app installed** | `npx @lamalibre/create-lamaste-desktop@4.2.0` ([setup guide](desktop-app-setup.md)) |
17
+ | **Desktop app installed** | `npx @lamalibre/create-lamaste-desktop@4.3.0` ([setup guide](desktop-app-setup.md)) |
18
18
  | **DigitalOcean account** | A free account at [cloud.digitalocean.com](https://cloud.digitalocean.com). **If you have other infrastructure on DO, create a dedicated team first** (see below) |
19
19
  | **Payment method** | A credit card or PayPal on file in DigitalOcean (required to create droplets) |
20
20
  | **Domain name** | Optional for initial setup — you can add a domain later through the panel |
@@ -221,7 +221,7 @@ The wizard shows up to 12 steps with live progress indicators. Each step shows a
221
221
  | **Waiting for boot** | Polls DigitalOcean until the droplet has a public IP and is ready (up to 5 minutes) |
222
222
  | **Setting up DNS records** | Creates A and wildcard A records in DigitalOcean DNS (only if a domain was selected in the Domain step) |
223
223
  | **Connecting via SSH** | Establishes an SSH connection using the temporary key |
224
- | **Installing Lamaste** | Runs `npx @lamalibre/create-lamaste@4.2.0` on the droplet (up to 10 minutes) |
224
+ | **Installing Lamaste** | Runs `npx @lamalibre/create-lamaste@4.3.0` on the droplet (up to 10 minutes) |
225
225
  | **Retrieving credentials** | Downloads the admin certificate (`.p12` file) from the droplet |
226
226
  | **Enrolling admin certificate** | Imports the certificate into your OS credential store |
227
227
  | **Saving configuration** | Writes the server entry to `~/.lamalibre/lamaste/servers.json` |
@@ -315,7 +315,7 @@ If you already have a Lamaste server set up (via the manual SSH method), you can
315
315
 
316
316
  The app checks that the panel is reachable before adding it. The server is added with `active: false` — click **Set Active** on its card to start using it.
317
317
 
318
- > **Note:** Adding an existing server registers it in the app but does not configure authentication. You will still need to set up your agent certificate separately using `lamaste-agent setup` (after `npm install -g @lamalibre/lamaste-agent@4.2.0`) or by importing a certificate manually.
318
+ > **Note:** Adding an existing server registers it in the app but does not configure authentication. You will still need to set up your agent certificate separately using `lamaste-agent setup` (after `npm install -g @lamalibre/lamaste-agent@4.3.0`) or by importing a certificate manually.
319
319
 
320
320
  ---
321
321
 
@@ -26,7 +26,7 @@ The Lamaste Desktop app is a native application (built with Tauri) that wraps th
26
26
  Run the installer:
27
27
 
28
28
  ```bash
29
- npx @lamalibre/create-lamaste-desktop@4.2.0
29
+ npx @lamalibre/create-lamaste-desktop@4.3.0
30
30
  ```
31
31
 
32
32
  The installer:
@@ -56,11 +56,11 @@ After the app launches, it shows a setup screen with two options:
56
56
  Install the agent globally and run its setup command:
57
57
 
58
58
  ```bash
59
- npm install -g @lamalibre/lamaste-agent@4.2.0
59
+ npm install -g @lamalibre/lamaste-agent@4.3.0
60
60
  lamaste-agent setup --label my-server
61
61
  ```
62
62
 
63
- The agent must be a global install — setup installs a sync timer that runs it every 30 seconds to pick up tunnel changes, and refuses to run from `npx`. (When the app sets up an agent itself, it installs `@lamalibre/lamaste-agent@4.2.0` globally for you.) The `--label` flag names this agent connection (used for multi-agent support). This command connects to your VPS panel using the agent certificate and configures the local Chisel client. During setup, the client certificate and key are extracted from the P12 bundle for mTLS client authentication. Once setup completes, the app detects the configuration and switches to the main interface.
63
+ The agent must be a global install — setup installs a sync timer that runs it every 30 seconds to pick up tunnel changes, and refuses to run from `npx`. (When the app sets up an agent itself, it installs `@lamalibre/lamaste-agent@4.3.0` globally for you.) The `--label` flag names this agent connection (used for multi-agent support). This command connects to your VPS panel using the agent certificate and configures the local Chisel client. During setup, the client certificate and key are extracted from the P12 bundle for mTLS client authentication. Once setup completes, the app detects the configuration and switches to the main interface.
64
64
 
65
65
  **Option B: Create a new server**
66
66
 
@@ -72,7 +72,7 @@ The desktop app has two modes, toggled via a pill switch in the sidebar header.
72
72
 
73
73
  ### Agent Mode (default)
74
74
 
75
- Agent mode provides local agent management. The agent-mode pages (Dashboard, Tunnels, Services, Plugins, Logs, Settings) are imported from the shared `@lamalibre/lamaste-agent-ui@4.2.0` package. The landing page is the **Agents** list, showing all configured agents with their connection status. Click an agent to drill into its management pages:
75
+ Agent mode provides local agent management. The agent-mode pages (Dashboard, Tunnels, Services, Plugins, Logs, Settings) are imported from the shared `@lamalibre/lamaste-agent-ui@4.3.0` package. The landing page is the **Agents** list, showing all configured agents with their connection status. Click an agent to drill into its management pages:
76
76
 
77
77
  - **Agents** (landing) — list of all configured agents with start/stop controls and status
78
78
  - **Dashboard** — per-agent connection status, Chisel version, start/stop/restart controls
@@ -84,7 +84,7 @@ Agent mode provides local agent management. The agent-mode pages (Dashboard, Tun
84
84
 
85
85
  ### Server Mode
86
86
 
87
- Server mode provides the full admin panel — the same management pages available in the browser-based panel UI. These pages are imported from the shared `@lamalibre/lamaste-server-ui@4.2.0` package:
87
+ Server mode provides the full admin panel — the same management pages available in the browser-based panel UI. These pages are imported from the shared `@lamalibre/lamaste-server-ui@4.3.0` package:
88
88
 
89
89
  - **Dashboard** — system stats (CPU, RAM, disk, uptime) + service health
90
90
  - **Tunnels** — tunnel CRUD, carrying agent, access mode, body limit
@@ -169,7 +169,7 @@ Stopping an agent's tunnels from the app is remembered: the agent's sync timer k
169
169
  Run the installer again to get the latest version:
170
170
 
171
171
  ```bash
172
- npx @lamalibre/create-lamaste-desktop@4.2.0
172
+ npx @lamalibre/create-lamaste-desktop@4.3.0
173
173
  ```
174
174
 
175
175
  If a new release is available, it downloads and installs the update. The previous version is replaced.
@@ -202,9 +202,9 @@ Deleting `~/.lamalibre/lamaste/` by hand instead would leave the agents' service
202
202
 
203
203
  | Action | Command / Location |
204
204
  | -------------------- | ------------------------------------------------------------------------------------ |
205
- | **Install** | `npx @lamalibre/create-lamaste-desktop@4.2.0` |
206
- | **Update** | `npx @lamalibre/create-lamaste-desktop@4.2.0` |
207
- | **Agent setup** | `npm install -g @lamalibre/lamaste-agent@4.2.0`, then `lamaste-agent setup --label <name>` |
205
+ | **Install** | `npx @lamalibre/create-lamaste-desktop@4.3.0` |
206
+ | **Update** | `npx @lamalibre/create-lamaste-desktop@4.3.0` |
207
+ | **Agent setup** | `npm install -g @lamalibre/lamaste-agent@4.3.0`, then `lamaste-agent setup --label <name>` |
208
208
  | **App location** | `/Applications/Lamaste.app` (macOS) |
209
209
  | **Agent registry** | `~/.lamalibre/lamaste/agents.json` |
210
210
  | **Per-agent data** | `~/.lamalibre/lamaste/agents/<label>/` |
@@ -215,7 +215,7 @@ Deleting `~/.lamalibre/lamaste/` by hand instead would leave the agents' service
215
215
  | **Chisel logs** | `~/.lamalibre/lamaste/agents/<label>/logs/chisel.log` |
216
216
  | **Cloud tokens** | OS credential store (`com.lamalibre.cloud`) |
217
217
  | **P12 passwords** | OS credential store (`com.lamalibre.lamaste.server`) |
218
- | **npm package** | `@lamalibre/create-lamaste-desktop@4.2.0` |
218
+ | **npm package** | `@lamalibre/create-lamaste-desktop@4.3.0` |
219
219
 
220
220
  ### Related Documentation
221
221
 
@@ -62,7 +62,7 @@ Execute the Lamaste installer:
62
62
 
63
63
  ```bash
64
64
  apt install -y npm
65
- npx @lamalibre/create-lamaste@4.2.0
65
+ npx @lamalibre/create-lamaste@4.3.0
66
66
  ```
67
67
 
68
68
  The installer displays a confirmation banner before making any changes:
@@ -90,7 +90,7 @@ The installer displays a confirmation banner before making any changes:
90
90
  Press Enter to continue or Ctrl+C to abort...
91
91
  ```
92
92
 
93
- Press **Enter** to proceed. To skip the confirmation prompt, use `npx @lamalibre/create-lamaste@4.2.0 --yes`.
93
+ Press **Enter** to proceed. To skip the confirmation prompt, use `npx @lamalibre/create-lamaste@4.3.0 --yes`.
94
94
 
95
95
  ### 4. What the Installer Does
96
96
 
@@ -380,7 +380,7 @@ The service includes security hardening: `ProtectHome=true`, `PrivateTmp=true`,
380
380
 
381
381
  | Item | Value |
382
382
  | ------------------------ | ------------------------------------------------------------ |
383
- | **Install command** | `npx @lamalibre/create-lamaste@4.2.0` |
383
+ | **Install command** | `npx @lamalibre/create-lamaste@4.3.0` |
384
384
  | **Minimum VPS** | Ubuntu 24.04, 512 MB RAM |
385
385
  | **Required ports** | 22 (SSH), 80 (HTTP/Let's Encrypt), 443 (HTTPS), 9292 (panel) |
386
386
  | **Certificate location** | `/etc/lamalibre/lamaste/pki/client.p12` |
@@ -113,7 +113,7 @@ Chisel is the tunneling engine. The installer already put the pinned Chisel rele
113
113
 
114
114
  Authelia provides two-factor authentication for your tunneled apps. The installer already put the pinned Authelia release, its systemd unit and its `lamaste-authelia` service account in place; the provisioner checks the binary is there, writes the configuration file (using your domain), creates the initial admin user with a random password, and starts the service. Authelia listens on `127.0.0.1:9091`.
115
115
 
116
- Onboarding downloads nothing. If either task fails with "… is not installed. Run `npx @lamalibre/create-lamaste@4.2.0` on the server to repair the installation.", re-run the installer on the server (it is idempotent) and start provisioning again.
116
+ Onboarding downloads nothing. If either task fails with "… is not installed. Run `npx @lamalibre/create-lamaste@4.3.0` on the server to repair the installation.", re-run the installer on the server (it is idempotent) and start provisioning again.
117
117
 
118
118
  Authelia uses **bcrypt** for password hashing (not argon2id) because argon2id uses approximately 93 MB per hash, which causes out-of-memory kills on a 512 MB droplet.
119
119
 
@@ -213,7 +213,7 @@ Each provisioning task calls library functions in `packages/lamaste-serverd/src/
213
213
 
214
214
  The DNS check resolves these hostnames using Node's `dns.resolve4()`:
215
215
 
216
- 1. `panel.example.com`, `auth.example.com`, `tunnel.example.com` — must resolve to the server IP (required; `CORE_SUBDOMAINS` in `@lamalibre/lamaste@4.2.0`)
216
+ 1. `panel.example.com`, `auth.example.com`, `tunnel.example.com` — must resolve to the server IP (required; `CORE_SUBDOMAINS` in `@lamalibre/lamaste@4.3.0`)
217
217
  2. `test-lamaste-check.example.com` — tests wildcard resolution (optional)
218
218
  3. `example.com` — reported, not required
219
219
 
@@ -27,7 +27,7 @@ Using the desktop app's cloud provisioning wizard:
27
27
 
28
28
  - Enter your DigitalOcean API token
29
29
  - Select a region and droplet size
30
- - The wizard calls `@lamalibre/lamaste-cloud@4.2.0` to create the droplet
30
+ - The wizard calls `@lamalibre/lamaste-cloud@4.3.0` to create the droplet
31
31
  - Progress streams back as NDJSON events in the desktop UI
32
32
  - Complete onboarding in the browser panel
33
33
 
@@ -96,7 +96,7 @@ The Sync server now runs as a local service on the Mac. It operates independentl
96
96
 
97
97
  ### 5. Register Sync Scopes and Instance on Lamaste
98
98
 
99
- The Sync server detects that the Mac has a Lamaste agent certificate. Using that cert (mTLS), it registers a ticket scope and instance with the Lamaste panel. This is handled automatically by `TicketInstanceManager` from `@lamalibre/lamaste-tickets@4.2.0` — the admin does not need to do anything.
99
+ The Sync server detects that the Mac has a Lamaste agent certificate. Using that cert (mTLS), it registers a ticket scope and instance with the Lamaste panel. This is handled automatically by `TicketInstanceManager` from `@lamalibre/lamaste-tickets@4.3.0` — the admin does not need to do anything.
100
100
 
101
101
  ```mermaid
102
102
  sequenceDiagram
@@ -1,6 +1,6 @@
1
1
  # Installer Flow
2
2
 
3
- ## What `npx @lamalibre/create-lamaste@4.2.0` Does
3
+ ## What `npx @lamalibre/create-lamaste@4.3.0` Does
4
4
 
5
5
  The installer is a **zero-prompt, fire-and-forget** command. It assumes:
6
6
 
@@ -13,7 +13,7 @@ It requires **no user input**. All configuration happens later through the brows
13
13
  ## Execution Flow
14
14
 
15
15
  ```
16
- $ npx @lamalibre/create-lamaste@4.2.0
16
+ $ npx @lamalibre/create-lamaste@4.3.0
17
17
 
18
18
  Lamaste Installer v1.0.0
19
19
 
@@ -4,14 +4,14 @@
4
4
 
5
5
  ## In Plain English
6
6
 
7
- When you run `npx @lamalibre/create-lamaste@4.2.0` on a fresh VPS, the installer takes over and does everything automatically: hardens the OS, installs dependencies, generates security certificates, configures nginx, deploys the panel, and starts the service. It prints a summary with instructions for downloading your client certificate and accessing the panel. No prompts, no questions, no configuration files to edit.
7
+ When you run `npx @lamalibre/create-lamaste@4.3.0` on a fresh VPS, the installer takes over and does everything automatically: hardens the OS, installs dependencies, generates security certificates, configures nginx, deploys the panel, and starts the service. It prints a summary with instructions for downloading your client certificate and accessing the panel. No prompts, no questions, no configuration files to edit.
8
8
 
9
9
  The installer is deliberately limited in scope. It sets up just enough to serve the panel UI over a secure connection. Everything else — domain configuration, tunnel setup, user management — happens through the browser-based onboarding wizard after the installer finishes.
10
10
 
11
11
  ## Overview
12
12
 
13
13
  ```
14
- npx @lamalibre/create-lamaste@4.2.0
14
+ npx @lamalibre/create-lamaste@4.3.0
15
15
  │
16
16
  ├── bin/create-lamaste.js ← CLI entry point
17
17
  │ └── calls main() from src/index.js
@@ -181,7 +181,7 @@ Six task groups run sequentially through Listr2:
181
181
  **Chisel and Authelia** (`lib/relay-services.js`, run on install and on every redeploy):
182
182
 
183
183
  - Creates the `lamaste-chisel` group (and adds `lamaste` to it) and the `lamaste-authelia` system account (no login shell, no home)
184
- - Installs Chisel 1.12.0 and Authelia 4.39.28 to `/usr/local/bin` (`lib/binaries.js`): downloaded from fixed release URLs, verified against the SHA-256 digests pinned in `@lamalibre/lamaste@4.2.0` (`CHISEL_RELEASE`, `AUTHELIA_RELEASE`) before anything is unpacked, staged in a `mkdtemp` directory and renamed into place. A binary already at the pinned version is left alone; an Authelia **newer** than the pin is kept, never downgraded (its database may use a schema the pinned release cannot read)
184
+ - Installs Chisel 1.12.0 and Authelia 4.39.28 to `/usr/local/bin` (`lib/binaries.js`): downloaded from fixed release URLs, verified against the SHA-256 digests pinned in `@lamalibre/lamaste@4.3.0` (`CHISEL_RELEASE`, `AUTHELIA_RELEASE`) before anything is unpacked, staged in a `mkdtemp` directory and renamed into place. A binary already at the pinned version is left alone; an Authelia **newer** than the pin is kept, never downgraded (its database may use a schema the pinned release cannot read)
185
185
  - Writes `/etc/systemd/system/chisel.service` (`User=nobody`, `Group=lamaste-chisel`) and `/etc/systemd/system/authelia.service` (`User=lamaste-authelia`, `UMask=027`, `Environment=X_AUTHELIA_CONFIG_FILTERS=template`, `ProtectSystem=strict` with `ReadWritePaths=/etc/authelia /var/log/authelia`, `SystemCallFilter=@system-service`, empty `CapabilityBoundingSet`, `NoNewPrivileges`); both are enabled and started later by onboarding
186
186
  - Generates the OpenID Connect issuer key `/etc/authelia/oidc-issuer.pem` if it is missing (`lib/oidc-issuer-key.js`): RSA 4096 generated in the installer process, written by a child process running as `lamaste-authelia` (`wx`, 0600, renamed into place) so root never writes through `/etc/authelia`. The panel references it from Authelia's configuration with a `{{ secret "…" }}` template expression — which is why the unit and `lamaste-priv authelia-totp` set the `template` configuration filter — and can never read it. A key owned by anyone else is replaced; one owned by the Authelia account is kept (its mode fixed through a descriptor if it drifted)
187
187
  - Sets ownership (`lib/ownership.js`): `/var/www/lamaste` `lamaste:www-data 2750`; `/etc/authelia` `lamaste:lamaste-authelia 2770` (setgid) with `configuration.yml` `0640`, `users.yml` `0660`, `.secrets.json` `0600` (lamaste), `db.sqlite3*` and `notifications.txt` `lamaste-authelia 0600`; `/var/log/authelia` `lamaste-authelia 0750`; `chisel-server.key` (if present) `lamaste:lamaste-chisel 0640`; the PKI directory `lamaste:lamaste` (the panel is the CA)
@@ -253,7 +253,7 @@ When the installer detects an existing Lamaste installation (`/etc/lamalibre/lam
253
253
 
254
254
  Redeploy mode stops the panel and gatekeeper, secures the install directory (a non-root-owned tree is moved aside and removed at the end), redeploys the panel server, `lamaste-server` CLI, UI, docs, gatekeeper and cert help page, merges `panel.json`, runs the same Chisel/Authelia step as a fresh install (stopping a running Authelia while its files change hands), rewrites the panel's systemd unit, the root-owned programs and the sudoers rules, ensures the port-80 redirect, then restarts Chisel if its binary or unit changed, starts Authelia and the gatekeeper if they were running, and restarts the panel. It does not touch OS hardening, mTLS certificates or the panel's vhosts. The panel's self-update (`sudo lamaste-priv self-update <version>`) runs this path as root, so it never writes through a path the `lamaste` user controls.
255
255
 
256
- This provides a fast upgrade path: re-running `npx @lamalibre/create-lamaste@4.2.0` on an existing installation updates the code and the pinned services while preserving all configuration and certificates. Use `--force-full` to bypass this and run the complete installer.
256
+ This provides a fast upgrade path: re-running `npx @lamalibre/create-lamaste@4.3.0` on an existing installation updates the code and the pinned services while preserving all configuration and certificates. Use `--force-full` to bypass this and run the complete installer.
257
257
 
258
258
  The redeploy logic lives in `tasks/redeploy.js`, with shared systemd unit and sudoers content generators in `lib/service-config.js`.
259
259
 
@@ -276,7 +276,7 @@ Everything else the panel does needs no rule: the PKI directory (agent CSR signi
276
276
 
277
277
  The certbot wrappers accept only lowercase DNS hostnames (no wildcards or path characters, at most 100 per certificate) and a syntactically valid email address. Renewals the panel triggers pass `--no-random-sleep-on-renew`, because certbot otherwise sleeps up to about 8 minutes before a non-interactive renewal (a delay meant for `certbot.timer`) while an operator waits.
278
278
 
279
- Redeploying (`npx @lamalibre/create-lamaste@4.2.0` on an existing install) reinstalls the programs, removes retired ones (`lamaste-sign-csr`, `lamaste-pki-rename`), and replaces this file atomically after `visudo -c` accepts it, so upgrades pick up new rules.
279
+ Redeploying (`npx @lamalibre/create-lamaste@4.3.0` on an existing install) reinstalls the programs, removes retired ones (`lamaste-sign-csr`, `lamaste-pki-rename`), and replaces this file atomically after `visudo -c` accepts it, so upgrades pick up new rules.
280
280
 
281
281
  ## File Operation Safety
282
282