@lamalibre/create-lamaste 4.4.0 → 4.4.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.
Files changed (50) hide show
  1. package/package.json +2 -2
  2. package/vendor/docs/00-introduction/how-it-works.md +2 -2
  3. package/vendor/docs/00-introduction/quickstart.md +5 -5
  4. package/vendor/docs/00-introduction/what-is-lamaste.md +5 -5
  5. package/vendor/docs/01-concepts/certificates.md +3 -3
  6. package/vendor/docs/01-concepts/mtls.md +2 -2
  7. package/vendor/docs/01-concepts/security-model.md +2 -2
  8. package/vendor/docs/01-concepts/tickets.md +2 -2
  9. package/vendor/docs/01-concepts/tunneling.md +4 -4
  10. package/vendor/docs/02-guides/agent-setup.md +5 -5
  11. package/vendor/docs/02-guides/certificate-management.md +2 -2
  12. package/vendor/docs/02-guides/cloud-provisioning.md +3 -3
  13. package/vendor/docs/02-guides/desktop-app-setup.md +10 -10
  14. package/vendor/docs/02-guides/installation.md +3 -3
  15. package/vendor/docs/02-guides/onboarding.md +2 -2
  16. package/vendor/docs/02-guides/standalone-plugin-with-tickets.md +2 -2
  17. package/vendor/docs/03-architecture/installer-flow.md +2 -2
  18. package/vendor/docs/03-architecture/installer.md +4 -4
  19. package/vendor/docs/03-architecture/management-flow.md +1 -1
  20. package/vendor/docs/03-architecture/overview.md +5 -5
  21. package/vendor/docs/03-architecture/panel-client.md +6 -6
  22. package/vendor/docs/03-architecture/panel-server.md +2 -2
  23. package/vendor/docs/03-architecture/state-management.md +2 -2
  24. package/vendor/docs/03-architecture/system-overview.md +1 -1
  25. package/vendor/docs/04-api-reference/agent-hosted-plugins.md +11 -11
  26. package/vendor/docs/04-api-reference/app-host.md +3 -3
  27. package/vendor/docs/04-api-reference/lifecycle-hooks.md +5 -5
  28. package/vendor/docs/04-api-reference/onboarding.md +1 -1
  29. package/vendor/docs/04-api-reference/registry-and-oidc.md +3 -1
  30. package/vendor/docs/04-api-reference/tunnels.md +1 -1
  31. package/vendor/docs/05-operations/backup-and-restore.md +1 -1
  32. package/vendor/docs/05-operations/uninstalling.md +1 -1
  33. package/vendor/docs/05-operations/upgrades.md +5 -5
  34. package/vendor/docs/06-reference/config-files.md +2 -2
  35. package/vendor/docs/06-reference/glossary.md +3 -3
  36. package/vendor/docs/06-reference/installer-flags.md +13 -13
  37. package/vendor/docs/06-reference/ports-and-services.md +1 -1
  38. package/vendor/docs/06-reference/troubleshooting.md +8 -8
  39. package/vendor/docs/gatekeeper-design.md +8 -8
  40. package/vendor/docs/identity-directory-design.md +2 -2
  41. package/vendor/docs/package.json +1 -1
  42. package/vendor/docs/todo/lib-cli-desktop-refactor.md +27 -27
  43. package/vendor/gatekeeper/package.json +2 -2
  44. package/vendor/server/package.json +2 -2
  45. package/vendor/server-ui/dist/assets/{index-CESyyK8V.js → index-roDHz3dM.js} +1 -1
  46. package/vendor/server-ui/dist/index.html +1 -1
  47. package/vendor/serverd/package.json +2 -2
  48. package/vendor/serverd/src/lib/plugin-packages.js +30 -22
  49. package/vendor/serverd/src/lib/plugins.js +35 -11
  50. package/vendor/serverd/src/lib/registry-credential.js +37 -0
@@ -10,9 +10,9 @@ The client has two modes. On first visit, it shows an onboarding wizard that wal
10
10
 
11
11
  ## Shared Panel Packages
12
12
 
13
- The management-mode admin pages (Dashboard, Tunnels, Sites, Users, Certificates, Services, Plugins, Tickets, Logs, Settings) live in `@lamalibre/lamaste-server-ui@4.4.0`. This package provides both the browser SPA build and the shared admin components consumed by `lamaste-desktop` (Tauri app).
13
+ The management-mode admin pages (Dashboard, Tunnels, Sites, Users, Certificates, Services, Plugins, Tickets, Logs, Settings) live in `@lamalibre/lamaste-server-ui@4.4.1`. This package provides both the browser SPA build and the shared admin components consumed by `lamaste-desktop` (Tauri app).
14
14
 
15
- The agent-mode pages (Dashboard, Tunnels, Services, Plugins, Logs, Settings) are extracted into a separate shared package: `@lamalibre/lamaste-agent-ui@4.4.0`. This package is consumed by `lamaste-desktop` for agent mode.
15
+ The agent-mode pages (Dashboard, Tunnels, Services, Plugins, Logs, Settings) are extracted into a separate shared package: `@lamalibre/lamaste-agent-ui@4.4.1`. This package is consumed by `lamaste-desktop` for agent mode.
16
16
 
17
17
  The shared package exposes an `AdminClientContext` abstraction that decouples admin UI pages from their data transport. Each host provides its own client implementation:
18
18
 
@@ -118,7 +118,7 @@ The shell renders the appropriate step component and shows a progress indicator
118
118
 
119
119
  ### Management Mode
120
120
 
121
- React Router handles navigation within the `Layout` component (sidebar + content area). Management page components are imported from the shared `@lamalibre/lamaste-server-ui@4.4.0` package and wrapped with the `AdminClientProvider` (which supplies the web-specific data client):
121
+ React Router handles navigation within the `Layout` component (sidebar + content area). Management page components are imported from the shared `@lamalibre/lamaste-server-ui@4.4.1` package and wrapped with the `AdminClientProvider` (which supplies the web-specific data client):
122
122
 
123
123
  | Path | Component | Description |
124
124
  | ------------------------ | ------------------- | ------------------------------------------------------------------- |
@@ -350,9 +350,9 @@ The app root wraps all content in five providers:
350
350
  ```
351
351
 
352
352
  1. **QueryClientProvider** — react-query cache with global defaults (`retry: 1`, `refetchOnWindowFocus: false`)
353
- 2. **AdminClientProvider** — from `@lamalibre/lamaste-server-ui@4.4.0`, supplies the host-specific data client (`webAdminClient` for browser fetch, Tauri `invoke` for desktop)
354
- 3. **ToastProvider** — from `@lamalibre/lamaste-server-ui@4.4.0`, notification toast system
355
- 4. **TwoFaProvider** — from `@lamalibre/lamaste-server-ui@4.4.0`, handles 2FA verification prompts when the panel has 2FA enabled
353
+ 2. **AdminClientProvider** — from `@lamalibre/lamaste-server-ui@4.4.1`, supplies the host-specific data client (`webAdminClient` for browser fetch, Tauri `invoke` for desktop)
354
+ 3. **ToastProvider** — from `@lamalibre/lamaste-server-ui@4.4.1`, notification toast system
355
+ 4. **TwoFaProvider** — from `@lamalibre/lamaste-server-ui@4.4.1`, handles 2FA verification prompts when the panel has 2FA enabled
356
356
  5. **BrowserRouter** — React Router for client-side navigation
357
357
 
358
358
  ## Documentation Viewer
@@ -453,7 +453,7 @@ See [nginx-configuration.md](./nginx-configuration.md) for detailed coverage.
453
453
 
454
454
  ### chisel-users.js — Chisel Credentials and Grants
455
455
 
456
- - Thin shim over `@lamalibre/lamaste@4.4.0/server`: per-agent credentials in `chisel-credentials.json` (0600)
456
+ - Thin shim over `@lamalibre/lamaste@4.4.1/server`: per-agent credentials in `chisel-credentials.json` (0600)
457
457
  - Renders the authfile `chisel-users` from the credentials and `tunnels.json`: each `agent-<label>` may bind exactly `^R:127\.0\.0\.1:<port>$` for its enabled tunnels
458
458
  - Adds the `lamaste-no-grants` sentinel user (password in `chisel-sentinel`, 0600) so the authfile is never empty — Chisel disables authentication on an empty authfile
459
459
  - Never grants a reserved port (3100, 9090, 9091, 9292, 9294) or a port claimed by two tunnels
@@ -535,7 +535,7 @@ Agent-to-agent authorization with scopes, instances, tickets, and sessions. Prov
535
535
  - **Periodic cleanup** — stale/dead instances, expired tickets, dead sessions
536
536
  - **Concurrency** — promise-chain mutex with atomic file writes (temp → fsync → rename)
537
537
 
538
- Client SDK: `@lamalibre/lamaste-tickets@4.4.0` (TypeScript, undici) provides `TicketClient`, `TicketInstanceManager` (source side), and `TicketSessionManager` (target side) for plugin integration.
538
+ Client SDK: `@lamalibre/lamaste-tickets@4.4.1` (TypeScript, undici) provides `TicketClient`, `TicketInstanceManager` (source side), and `TicketSessionManager` (target side) for plugin integration.
539
539
 
540
540
  State files: `/etc/lamalibre/lamaste/ticket-scopes.json` (scopes, instances, assignments) and `/etc/lamalibre/lamaste/tickets.json` (tickets, sessions).
541
541
 
@@ -331,7 +331,7 @@ Read returns `[]` for missing files (ENOENT), enabling the system to start with
331
331
  5. `DELETE /api/certs/agent/:label` (revocation) — the agent's app and plugin tunnels lose their `agentLabel` (unassigned), and its `agent-<label>` panel tunnel is removed
332
332
  6. At startup (background, fail closed): an ownerless `agent-<label>` panel tunnel is bound to the agent it names when that agent is active; other ownerless tunnels (from versions before tunnel ownership) are bound to the sole active agent, or left unassigned when there are several. Then the pinned Chisel binary, the unit and the authfile are re-established — and Chisel is stopped if they cannot be
333
333
 
334
- Every one of these workflows runs under the single tunnel lock (`withTunnelLock` in `@lamalibre/lamaste@4.4.0/server`), with its validation inside the lock.
334
+ Every one of these workflows runs under the single tunnel lock (`withTunnelLock` in `@lamalibre/lamaste@4.4.1/server`), with its validation inside the lock.
335
335
 
336
336
  ## Site State (`sites.json`)
337
337
 
@@ -486,7 +486,7 @@ The PKI directory belongs to `lamaste` because the panel is the CA (it signs age
486
486
 
487
487
  The Panel Server is a single-process Node.js application, but a single-threaded event loop does not serialize asynchronous workflows: two requests that each read `tunnels.json`, await a certificate or an nginx test, and then write it back would interleave, and both could pass the same uniqueness check. Serialization is therefore explicit:
488
488
 
489
- **Tunnel state** is guarded by one promise-chain lock, `withTunnelLock` in `@lamalibre/lamaste@4.4.0/server`'s `tunnels.ts`. Every tunnel workflow — create, delete, toggle, assign, reconfigure, the combined `PATCH /api/tunnels/:id`, releasing a revoked agent's tunnels, and startup reconciliation — takes it for its whole duration, and does its validation (subdomain and port uniqueness, reserved ports, agent checks, authorization against the current entry) inside it.
489
+ **Tunnel state** is guarded by one promise-chain lock, `withTunnelLock` in `@lamalibre/lamaste@4.4.1/server`'s `tunnels.ts`. Every tunnel workflow — create, delete, toggle, assign, reconfigure, the combined `PATCH /api/tunnels/:id`, releasing a revoked agent's tunnels, and startup reconciliation — takes it for its whole duration, and does its validation (subdomain and port uniqueness, reserved ports, agent checks, authorization against the current entry) inside it.
490
490
 
491
491
  **Chisel authfile renders** are serialized by a promise-chain mutex in `chisel-users.ts`, keyed by the credentials file, around every render (enroll, revoke, rotate, tunnel change). The daemon's `syncChisel()` renders after the tunnel state is written and restarts Chisel (`systemctl try-restart`) only when the render withdrew something.
492
492
 
@@ -58,7 +58,7 @@
58
58
  │ │ │ │
59
59
  │ │ Lamaste Desktop (Tauri v2) — dual mode │ │
60
60
  │ │ ├─ Agent mode: service discovery + tunnel management │ │
61
- │ │ │ (pages from @lamalibre/lamaste-agent-ui@4.4.0) │ │
61
+ │ │ │ (pages from @lamalibre/lamaste-agent-ui@4.4.1) │ │
62
62
  │ │ ├─ Server mode: full admin panel (via lamaste-server-ui) │ │
63
63
  │ │ ├─ Multi-server registry (~/.lamalibre/lamaste/servers.json) │ │
64
64
  │ │ ├─ Cloud provisioning (DigitalOcean via lamaste-cloud) │ │
@@ -45,7 +45,7 @@ There is no port in the manifest: Lamaste assigns it when the plugin is hosted.
45
45
 
46
46
  **Routes are unique.** A plugin's route — its name without a trailing `-server` or `-agent` — belongs to one installed plugin: installing another plugin with the same route is refused (`409`, `code: "route_taken"`, with `route` and `owner`). Plugins installed before the rule that share a route keep it for the one installed first; the others hold no route and can be uninstalled but not hosted (`409`, `code: "route_conflict"`) until they are reinstalled under a route of their own.
47
47
 
48
- **Pinned at install.** Installing (or staging an update of) a plugin with `agent.package` reads the agent package's integrity from the registry the panel installs from (`npm view <package>@<version> dist.integrity`) and pins `{ package, version, integrity }` with the plugin: the approval covers it and the agent installs only a tarball that matches. A version that is not semver without build metadata, an agent package not published at the plugin's version (`400`) or a registry without a SHA-512 integrity for it is refused.
48
+ **Pinned at install.** Installing (or staging an update of) a plugin with `agent.package` reads the agent package's integrity from the registry the panel installs from (`npm view <package>@<version> dist.integrity`, authenticated with the panel's [registry credential](registry-and-oidc.md) when one is configured) and pins `{ package, version, integrity }` with the plugin: the approval covers it and the agent installs only a tarball that matches. A version that is not semver without build metadata, an agent package not published at the plugin's version (`400`) or a registry without a SHA-512 integrity for it is refused.
49
49
 
50
50
  ## Hosting endpoints
51
51
 
@@ -133,7 +133,7 @@ Every app job is signed by the relay with its signing key when it is created, an
133
133
 
134
134
  ### Host facts
135
135
 
136
- Each agent reports its host on its sync when the facts change, when the relay does not hold them, and once a day: `PUT /api/agents/me/host-facts` (agent certificates only; 10 per minute, 4 KiB). What the relay holds it tells the agent in every [`GET /api/tunnels/agent-config`](tunnels.md#get-api-tunnels-agent-config) answer: `hostFactsHash`, the SHA-256 of the facts stored on the agent's record (`hostFactsHash` in `@lamalibre/lamaste@4.4.0`, the same function the agent hashes its own with), `null` when it stores none. When that is not the hash of the agent's facts, the sync reports them whatever it last reported — so an agent enrolled again on its label (a new record, without host facts) or a relay restored from a backup can host again after one sync, not a day later. Behind a relay that does not send the field, the agent reports as before.
136
+ Each agent reports its host on its sync when the facts change, when the relay does not hold them, and once a day: `PUT /api/agents/me/host-facts` (agent certificates only; 10 per minute, 4 KiB). What the relay holds it tells the agent in every [`GET /api/tunnels/agent-config`](tunnels.md#get-api-tunnels-agent-config) answer: `hostFactsHash`, the SHA-256 of the facts stored on the agent's record (`hostFactsHash` in `@lamalibre/lamaste@4.4.1`, the same function the agent hashes its own with), `null` when it stores none. When that is not the hash of the agent's facts, the sync reports them whatever it last reported — so an agent enrolled again on its label (a new record, without host facts) or a relay restored from a backup can host again after one sync, not a day later. Behind a relay that does not send the field, the agent reports as before.
137
137
 
138
138
  ```json
139
139
  {
@@ -337,7 +337,7 @@ What the agent's host offers — see [Host facts](#host-facts).
337
337
 
338
338
  `lamaste-agent sync` (the 30-second timer) fetches a plugin's config when it does not hold the secret issued at `secretIssuedAt`, writes `~/.lamalibre/lamaste/agents/<label>/app-plugins/<route>.json` (0600, atomic), acknowledges it, and removes the file of a plugin the relay no longer lists. For an agent half Lamaste installed it first writes the service's own copy, `/etc/lamalibre/apps/<route>/plugin-config.json`, and acknowledges only once both are written. `sync.log` records `plugin config fetched: <route>` and `plugin config delivered to its service: <route>`.
339
339
 
340
- The agent half reads its plugin config — `LAMASTE_APP_CONFIG` for an agent half Lamaste installs, the file above for one installed by hand (`LAMALIBRE_LAMASTE_APP_PLUGIN_CONFIG` by convention) — and verifies every request on `/admin/*` with `createCallerAssertionVerifier` from `@lamalibre/lamaste@4.4.0/agent`, which re-reads the file when a signature does not verify, so a rotation needs no restart. It listens where `agentHalfListenTarget` says: for an agent half Lamaste installs, systemd holds the port in a socket unit and passes the listening socket (descriptor 3), so the port stays bound while the agent half restarts or is updated and no other local process can take it in between — such an agent half must listen this way, and one that binds its port itself fails with `EADDRINUSE`; for one installed by hand, the config's port on `127.0.0.1` ([App Host](app-host.md#writing-an-agent-half)). Every answer it gives a verified request carries the responder signature ([below](#authentication-between-the-relay-and-the-agent-half)):
340
+ The agent half reads its plugin config — `LAMASTE_APP_CONFIG` for an agent half Lamaste installs, the file above for one installed by hand (`LAMALIBRE_LAMASTE_APP_PLUGIN_CONFIG` by convention) — and verifies every request on `/admin/*` with `createCallerAssertionVerifier` from `@lamalibre/lamaste@4.4.1/agent`, which re-reads the file when a signature does not verify, so a rotation needs no restart. It listens where `agentHalfListenTarget` says: for an agent half Lamaste installs, systemd holds the port in a socket unit and passes the listening socket (descriptor 3), so the port stays bound while the agent half restarts or is updated and no other local process can take it in between — such an agent half must listen this way, and one that binds its port itself fails with `EADDRINUSE`; for one installed by hand, the config's port on `127.0.0.1` ([App Host](app-host.md#writing-an-agent-half)). Every answer it gives a verified request carries the responder signature ([below](#authentication-between-the-relay-and-the-agent-half)):
341
341
 
342
342
  ```js
343
343
  import {
@@ -345,7 +345,7 @@ import {
345
345
  createCallerAssertionVerifier,
346
346
  createHealthHandler,
347
347
  responderHeaderFor,
348
- } from '@lamalibre/lamaste@4.4.0/agent';
348
+ } from '@lamalibre/lamaste@4.4.1/agent';
349
349
 
350
350
  const verifier = createCallerAssertionVerifier({ configPath, plugin: 'market-indicators' });
351
351
  const health = createHealthHandler({ verifier, check: () => db.isOpen() });
@@ -411,7 +411,7 @@ Answers given before or instead of a verification — a `401` for a bad assertio
411
411
 
412
412
  **Secret settings travel sealed** to the one request that carries them ([Between the relay and the agent half](#between-the-relay-and-the-agent-half)).
413
413
 
414
- **Agent halves built against `@lamalibre/lamaste@4.4.0` 4.3 must be rebuilt** against this version: an agent half that does not sign its answers is down (`app`) to the health monitor, its pages and API answer `502`, and its settings and hooks fail as `unauthenticated`; one that expects secrets in clear refuses sealed ones.
414
+ **Agent halves built against `@lamalibre/lamaste@4.4.1` 4.3 must be rebuilt** against this version: an agent half that does not sign its answers is down (`app`) to the health monitor, its pages and API answer `502`, and its settings and hooks fail as `unauthenticated`; one that expects secrets in clear refuses sealed ones.
415
415
 
416
416
  ## Health
417
417
 
@@ -555,13 +555,13 @@ The panel checks the change against the installed schema — without `pattern`
555
555
  | 422 | — | The agent half refused it: `errors` per field — its `pattern`, its own schema, or a sealed secret that did not open ("Could not be decrypted") |
556
556
  | 502 | `settings_unsupported` | The agent half does not answer `/admin/settings` (`404`) — it predates settings; update the plugin |
557
557
  | 502 | `refused` | Nothing answers on the internal tunnel's port |
558
- | 502 | `unauthenticated` | The answer did not carry a valid responder signature: something else may hold the tunnel's port, or the agent half was built against an older `@lamalibre/lamaste@4.4.0` and must be rebuilt. The change may or may not have been stored — read the settings again |
558
+ | 502 | `unauthenticated` | The answer did not carry a valid responder signature: something else may hold the tunnel's port, or the agent half was built against an older `@lamalibre/lamaste@4.4.1` and must be rebuilt. The change may or may not have been stored — read the settings again |
559
559
  | 502 | `invalid`, `other` | The agent half answered too large a body or in another shape (reload the settings to see what it stored), or could not be reached |
560
560
  | 502 | `agent_half_error` | The agent half answered another status (`status`) |
561
561
  | 503 | `hosting_offline` | The plugin is not hosted, its hosting is not `active`, or its health is `down` — with `hosting` as in `GET` (`null` when not hosted) |
562
562
  | 504 | `timeout` | No answer within 15 s — the change may or may not have been stored; read the settings again |
563
563
 
564
- Once the plugin and its settings block are found, the checks run in this order: the body (`400`), a running operation (`409`), the hosting (`503`), then the agent half. A refusal answers `{ error, code?, errors?, revision?, operationId?, hosting?, status? }`, built from what Lamaste found and never from the request body. The agent half's per-field messages are passed on only under the keys of the installed schema (or `_`), and only when they are messages Lamaste's own validation produces — the fixed ones (`Is required`, `Does not match the required format`, …) and the templated ones (`Must be at most 64 characters`, `Must be at least 0`, `2 unknown settings`), matched exactly; anything else reads `Invalid value` (`normalizeSettingsErrorMessage` in `@lamalibre/lamaste@4.4.0`). The agent half is Lamaste code that never repeats a value, but the relay does not answer an administrator with text it cannot vouch for.
564
+ Once the plugin and its settings block are found, the checks run in this order: the body (`400`), a running operation (`409`), the hosting (`503`), then the agent half. A refusal answers `{ error, code?, errors?, revision?, operationId?, hosting?, status? }`, built from what Lamaste found and never from the request body. The agent half's per-field messages are passed on only under the keys of the installed schema (or `_`), and only when they are messages Lamaste's own validation produces — the fixed ones (`Is required`, `Does not match the required format`, …) and the templated ones (`Must be at most 64 characters`, `Must be at least 0`, `2 unknown settings`), matched exactly; anything else reads `Invalid value` (`normalizeSettingsErrorMessage` in `@lamalibre/lamaste@4.4.1`). The agent half is Lamaste code that never repeats a value, but the relay does not answer an administrator with text it cannot vouch for.
565
565
 
566
566
  A stored change is recorded in the [audit log](audit-and-notifications.md) as `plugin.settings.updated` with key names only — `changed`, `cleared`, `secretsSet`, `secretsCleared` — the new `revision` and the certificate serial. A change whose answer was lost (`504 timeout`, `502 unauthenticated` or `invalid`) is remembered by its key names; when a later read finds the agent half at exactly its revision, changed in those keys only, it is recorded then, with `confirmedLater: true`.
567
567
 
@@ -587,7 +587,7 @@ An update's preview — `pendingUpdate.settings`, in `GET /api/plugins/:name/hoo
587
587
 
588
588
  ### Between the relay and the agent half
589
589
 
590
- The relay calls the agent half with `callAgentHalf` (`@lamalibre/lamaste@4.4.0`, also exported from `@lamalibre/lamaste@4.4.0/server`): one JSON request to `http://127.0.0.1:<port>/admin/…` — the relay end of the internal tunnel — with a caller assertion naming Lamaste itself (`{ "type": "lamaste" }`) that binds the body's SHA-256, answered with whatever status the agent half gives once the answer carries a valid responder signature over that status and its exact body ([Authentication](#authentication-between-the-relay-and-the-agent-half)). It calls only paths under `/admin/` without `.` or `..` segments, with `GET`, `PUT`, `POST` or `DELETE`, and throws before sending anything otherwise; it sends a body as `application/json` — `body` may be a function of the assertion's nonce, chosen before anything is signed, which is how a change seals its secrets to the one call — bounds the time and the response size, and resolves `{ ok: true, status, body }` (the parsed JSON, or `null`) or `{ ok: false, code, error }` — `refused` (nothing listens), `timeout`, `invalid` (a response over the size limit), `unauthenticated` (no valid responder signature: not the agent half, or one built against an older core) or `other` — with an error that never contains the request body. The hook call `callAgentHalfHook` is built on the same exchange.
590
+ The relay calls the agent half with `callAgentHalf` (`@lamalibre/lamaste@4.4.1`, also exported from `@lamalibre/lamaste@4.4.1/server`): one JSON request to `http://127.0.0.1:<port>/admin/…` — the relay end of the internal tunnel — with a caller assertion naming Lamaste itself (`{ "type": "lamaste" }`) that binds the body's SHA-256, answered with whatever status the agent half gives once the answer carries a valid responder signature over that status and its exact body ([Authentication](#authentication-between-the-relay-and-the-agent-half)). It calls only paths under `/admin/` without `.` or `..` segments, with `GET`, `PUT`, `POST` or `DELETE`, and throws before sending anything otherwise; it sends a body as `application/json` — `body` may be a function of the assertion's nonce, chosen before anything is signed, which is how a change seals its secrets to the one call — bounds the time and the response size, and resolves `{ ok: true, status, body }` (the parsed JSON, or `null`) or `{ ok: false, code, error }` — `refused` (nothing listens), `timeout`, `invalid` (a response over the size limit), `unauthenticated` (no valid responder signature: not the agent half, or one built against an older core) or `other` — with an error that never contains the request body. The hook call `callAgentHalfHook` is built on the same exchange.
591
591
 
592
592
  | Call | Body | Answer |
593
593
  | ----------------------------- | ----------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
@@ -610,7 +610,7 @@ The state, which every answer of the relay's reads and changes is built from:
610
610
 
611
611
  `values` are the explicitly stored non-secret values — no defaults; `secrets` names the secrets that are set, and when — never a value; `setAt` must be an ISO 8601 UTC timestamp as `Date.prototype.toISOString` writes it (`2026-10-16T10:00:00.000Z`), so not even that field can carry free text. In a change, `schemaVersion` is the relay's installed manifest's, `baseRevision` the revision the administrator edited, `values` the non-secret change the panel validated and `secrets` each secret **sealed** (`null` clears). Bodies and responses are 2 MiB at most — the largest valid change, 50 fields of 4096 characters that JSON escapes to six bytes apiece, is about 1.23 MB. The relay parses every state with `parseAgentHalfSettingsState`, which builds a fresh object of the contract's fields: nothing else the agent half sent — a secret's value in particular — survives into the cache.
612
612
 
613
- **Sealed secrets.** Each secret of a change crosses the tunnel as `{ "v": 1, "iv": "<base64url, 12 bytes>", "ct": "<base64url ciphertext and 16-byte tag>" }` — AES-256-GCM under a key derived from the installation secret (HKDF-SHA256 over its 32 bytes, salt `lamaste/settings-secrets/v1`, info the plugin's route), a random IV per value, and as additional data the JSON array `[plugin, nonce, key, baseRevision]`: the plugin, the call's assertion nonce, the field and the revision the change was made on. `encryptSettingsSecret` and `decryptSettingsSecret` in `@lamalibre/lamaste@4.4.0` are the two halves; the relay seals with the secret it signs the call with, and the agent half opens with the secret that verified it (`decryptSettingsSecret` on the verify result — the secret never leaves the verifier). A sealed secret opens for no other plugin, call, field or revision; one that does not open is a `422` for its key, and an error never repeats it. Whatever takes the tunnel's port receives no secret in clear — the non-secret values of a change, which the relay stores in clear anyway, are not sealed.
613
+ **Sealed secrets.** Each secret of a change crosses the tunnel as `{ "v": 1, "iv": "<base64url, 12 bytes>", "ct": "<base64url ciphertext and 16-byte tag>" }` — AES-256-GCM under a key derived from the installation secret (HKDF-SHA256 over its 32 bytes, salt `lamaste/settings-secrets/v1`, info the plugin's route), a random IV per value, and as additional data the JSON array `[plugin, nonce, key, baseRevision]`: the plugin, the call's assertion nonce, the field and the revision the change was made on. `encryptSettingsSecret` and `decryptSettingsSecret` in `@lamalibre/lamaste@4.4.1` are the two halves; the relay seals with the secret it signs the call with, and the agent half opens with the secret that verified it (`decryptSettingsSecret` on the verify result — the secret never leaves the verifier). A sealed secret opens for no other plugin, call, field or revision; one that does not open is a `422` for its key, and an error never repeats it. Whatever takes the tunnel's port receives no secret in clear — the non-secret values of a change, which the relay stores in clear anyway, are not sealed.
614
614
 
615
615
  Only Lamaste: a caller assertion that does not verify is `401`, one naming an administrator or an agent `403`; another path under `/admin/settings` is `404`, a method a path does not take `405`. The **forwarder reserves `/admin/settings`** and everything under it as it reserves `/admin/hooks/*`: `/api/<route>/settings`, `/api/<route>/Settings` and `/api/<route>/%73ettings/x` all answer `404` and reach nothing. No forwarded caller — an administrator, or an agent holding one of the plugin's capabilities — reads or changes the settings behind the panel's validation, and the relay's cache cannot drift from the agent half. An agent half's own admin routes cannot live there.
616
616
 
@@ -618,7 +618,7 @@ Only Lamaste: a caller assertion that does not verify is `401`, one naming an ad
618
618
 
619
619
  ### In the agent half
620
620
 
621
- `@lamalibre/lamaste@4.4.0/agent` holds the store and the handler, so that no plugin reimplements how a secret is stored:
621
+ `@lamalibre/lamaste@4.4.1/agent` holds the store and the handler, so that no plugin reimplements how a secret is stored:
622
622
 
623
623
  - **`settingsFromManifest(manifest)`** — the `settings` block of the agent half's own copy of its manifest (bundled with it), validated — here each string default is also tested against its field's `pattern`, which the relay never runs; throws `ManifestValidationError` when it is missing or invalid. The agent half checks values against this schema, not the relay's.
624
624
  - **`createSettingsStore({ dataDir, settings })`** — the store over two files in `dataDir`, which must exist: for an agent half Lamaste installs, `LAMASTE_APP_DATA_DIR`, the only directory its unit may write ([App Host](app-host.md)). One store per process. `load()` reads and reconciles the files and must run before anything else; then `state()` is what `GET` answers, `values()` the values the plugin runs with (defaults overlaid by the stored values, never a secret), `secret(key)` a secret's value for the agent half's own use (`undefined` when it is not set), `configured()`, `update(change)`, `reload()`, and `onChange(listener)` — called with the new state after every update and reload, returning its unsubscribe function; a listener that throws does not fail the update, which is stored.
@@ -641,7 +641,7 @@ import {
641
641
  createSettingsHandler,
642
642
  createSettingsStore,
643
643
  settingsFromManifest,
644
- } from '@lamalibre/lamaste@4.4.0/agent';
644
+ } from '@lamalibre/lamaste@4.4.1/agent';
645
645
  import manifest from '../lamaste-plugin.json' with { type: 'json' }; // bundled into the agent half
646
646
 
647
647
  const verifier = createCallerAssertionVerifier({
@@ -15,12 +15,12 @@ The helper does only what the relay **signed**. When it is enabled it remembers
15
15
  Once per agent machine (Linux with systemd; macOS agents cannot host an agent half Lamaste installs):
16
16
 
17
17
  ```bash
18
- sudo npm install -g @lamalibre/lamaste-agent@4.4.0 # a root-owned install
18
+ sudo npm install -g @lamalibre/lamaste-agent@4.4.1 # a root-owned install
19
19
  sudo lamaste-agent apphost enable # the agent runs as the user who ran sudo
20
20
  sudo lamaste-agent apphost enable --user root # the agent runs as root: no sudoers rule
21
21
  ```
22
22
 
23
- `enable` refuses to act from an install another user could have changed: the package, the helper it ships and the core library it loads — and every directory above them — must be root-owned and writable by no group or other user. A user-level install (`nvm`, a home-directory prefix) is not; install it root-owned with `sudo npm install -g @lamalibre/lamaste-agent@4.4.0` and run `enable` from that install. It also needs `/usr/bin/node` (the helper's interpreter and the Node.js every agent half runs on — from the distribution or NodeSource), systemd as the system manager (`/run/systemd/system`), and `sudo` and `visudo` unless the agent runs as root.
23
+ `enable` refuses to act from an install another user could have changed: the package, the helper it ships and the core library it loads — and every directory above them — must be root-owned and writable by no group or other user. A user-level install (`nvm`, a home-directory prefix) is not; install it root-owned with `sudo npm install -g @lamalibre/lamaste-agent@4.4.1` and run `enable` from that install. It also needs `/usr/bin/node` (the helper's interpreter and the Node.js every agent half runs on — from the distribution or NodeSource), systemd as the system manager (`/run/systemd/system`), and `sudo` and `visudo` unless the agent runs as root.
24
24
 
25
25
  What `enable` does:
26
26
 
@@ -357,7 +357,7 @@ The contract:
357
357
 
358
358
  ```js
359
359
  import Fastify from 'fastify';
360
- import { agentHalfListenTarget, createCallerAssertionVerifier } from '@lamalibre/lamaste@4.4.0/agent'; // bundled
360
+ import { agentHalfListenTarget, createCallerAssertionVerifier } from '@lamalibre/lamaste@4.4.1/agent'; // bundled
361
361
 
362
362
  const verifier = createCallerAssertionVerifier({
363
363
  configPath: process.env.LAMASTE_APP_CONFIG,
@@ -163,7 +163,7 @@ Approving a pending update approves everything it waits for (`approvalRequired`)
163
163
 
164
164
  ### `POST /api/plugins/:name/update`
165
165
 
166
- `{ "version"?: "1.2.3" | "latest" }` (default `latest`). For agent-hosted plugins. The panel fetches the version with `npm pack` and reads its manifest and relay module **in memory** — nothing of it is installed yet. Then:
166
+ `{ "version"?: "1.2.3" | "latest" }` (default `latest`). For agent-hosted plugins. The panel fetches the version with `npm pack` (from the registry its [registry credential](registry-and-oidc.md) names, authenticated as the panel, when one is configured) and reads its manifest and relay module **in memory** — nothing of it is installed yet. Then:
167
167
 
168
168
  - `200 { upToDate: true }` — nothing to do;
169
169
  - `202 { pendingUpdate }` — what it brings (relay hook set, agent half) differs from what was approved and auto-approve is off or withheld, or it turns a secret setting into a plain one (always); `approvalRequired` says which, and the administrators are notified. Approve it with the digest shown, or by its version;
@@ -250,13 +250,13 @@ Relay hooks run in `lamalibre-lamaste-hooks@<route>.service`, started by its soc
250
250
 
251
251
  `lamaste-priv hooks install <route> <sha256|none>` (module on stdin, checked against the approved digest) writes the module root-owned to `/opt/lamalibre/lamaste/hooks/<route>/<sha256>.mjs` and enables the socket; `hooks activate` points back at the previous module (an update's revert); `hooks restart` stops a stuck runner; `hooks remove` disables it and deletes its modules. The panel reinstalls an approved runner at start-up if it is missing.
252
252
 
253
- The protocol between the panel and the runner is one length-prefixed JSON frame each way per connection (`@lamalibre/lamaste@4.4.0/hooks`, `protocol.ts`).
253
+ The protocol between the panel and the runner is one length-prefixed JSON frame each way per connection (`@lamalibre/lamaste@4.4.1/hooks`, `protocol.ts`).
254
254
 
255
255
  ## Writing hooks
256
256
 
257
257
  ```js
258
258
  // hooks/relay.mjs — bundled into one file (esbuild), Node built-ins only
259
- import { defineRelayHooks } from '@lamalibre/lamaste@4.4.0/hooks';
259
+ import { defineRelayHooks } from '@lamalibre/lamaste@4.4.1/hooks';
260
260
 
261
261
  export default defineRelayHooks({
262
262
  'install:pre-tunnel': async (input) =>
@@ -285,10 +285,10 @@ Every decision may carry `effects` (at most 10): `{ type: 'audit', event, data?
285
285
 
286
286
  ### Agent hooks
287
287
 
288
- The agent half answers `POST /admin/hooks/<stage>` (body `{ stage, input }`, answer `{ ok: true, output? }` or `{ ok: false, reason }`). `createAgentHookHandler` from `@lamalibre/lamaste@4.4.0/agent` does the checking — the caller assertion must name Lamaste itself (`{ type: 'lamaste' }`), which no forwarded caller can, and bind the SHA-256 of the exact body (`wrong_body` otherwise), so it needs the body **as received** (a `Buffer` or a string; a parsed object is refused `400` "The body must be passed as received"); the forwarder never reaches `/admin/hooks*`. Send the `status`, `headers` and `bodyText` it returns, `bodyText` byte for byte: its `X-Lamalibre-Responder` header signs those bytes, and the relay counts a hook answer without a valid one as a failure (`unauthenticated`) — see [Authentication between the relay and the agent half](agent-hosted-plugins.md#authentication-between-the-relay-and-the-agent-half):
288
+ The agent half answers `POST /admin/hooks/<stage>` (body `{ stage, input }`, answer `{ ok: true, output? }` or `{ ok: false, reason }`). `createAgentHookHandler` from `@lamalibre/lamaste@4.4.1/agent` does the checking — the caller assertion must name Lamaste itself (`{ type: 'lamaste' }`), which no forwarded caller can, and bind the SHA-256 of the exact body (`wrong_body` otherwise), so it needs the body **as received** (a `Buffer` or a string; a parsed object is refused `400` "The body must be passed as received"); the forwarder never reaches `/admin/hooks*`. Send the `status`, `headers` and `bodyText` it returns, `bodyText` byte for byte: its `X-Lamalibre-Responder` header signs those bytes, and the relay counts a hook answer without a valid one as a failure (`unauthenticated`) — see [Authentication between the relay and the agent half](agent-hosted-plugins.md#authentication-between-the-relay-and-the-agent-half):
289
289
 
290
290
  ```js
291
- import { createAgentHookHandler, createCallerAssertionVerifier } from '@lamalibre/lamaste@4.4.0/agent';
291
+ import { createAgentHookHandler, createCallerAssertionVerifier } from '@lamalibre/lamaste@4.4.1/agent';
292
292
 
293
293
  const hooks = createAgentHookHandler({
294
294
  verifier: createCallerAssertionVerifier({ configPath, plugin: 'fx' }),
@@ -255,7 +255,7 @@ The wildcard check probes `test-lamaste-check.<domain>`. Wildcard DNS is optiona
255
255
 
256
256
  ### `POST /api/onboarding/provision`
257
257
 
258
- Starts the full stack provisioning process in the background. This configures and starts Chisel and Authelia, issues certbot certificates, and writes nginx vhosts. The Chisel and Authelia binaries and units are installed by `create-lamaste`, not here: if one is missing, the task fails with the error "Chisel is not installed. Run `npx @lamalibre/create-lamaste@4.4.0` on the server to repair the installation." (likewise for Authelia). Task IDs are unchanged from earlier versions; only the titles changed.
258
+ Starts the full stack provisioning process in the background. This configures and starts Chisel and Authelia, issues certbot certificates, and writes nginx vhosts. The Chisel and Authelia binaries and units are installed by `create-lamaste`, not here: if one is missing, the task fails with the error "Chisel is not installed. Run `npx @lamalibre/create-lamaste@4.4.1` on the server to repair the installation." (likewise for Authelia). Task IDs are unchanged from earlier versions; only the titles changed.
259
259
 
260
260
  Provisioning runs asynchronously. This endpoint returns immediately with a 202 status. Use the WebSocket stream endpoint to follow progress in real time.
261
261
 
@@ -6,6 +6,8 @@ Two generic mechanisms a plugin can build on, first used by Feria (the Lamalibre
6
6
 
7
7
  Agents install plugins with `npm install` from the Lamalibre registry. A networked registry accepts nothing anonymously, and an agent must never hold a long-lived registry credential. So the panel holds one credential for the registry and, each time an agent is about to install a plugin, asks the registry for a one-hour read-only token owned by that agent. The agent writes it to a throw-away npm configuration file for that one install and deletes it afterwards.
8
8
 
9
+ The panel's own npm calls use the same credential: installing a plugin, fetching a version to stage an update, and reading an agent package's integrity to pin it all run with npm routed to the credential's registry for its scope and authenticated with the panel's service token — through a throw-away userconfig of the same kind (0600 in a private temporary directory, named to npm as `npm_config_userconfig`, removed once npm has run), never the service account's `~/.npmrc`. A registry that answers `401` has the token obtained again once; a token still refused, or `403`, fails the operation naming the registry and the status. Without a credential the panel's npm runs as the machine configures it (the public registry by default).
10
+
9
11
  The panel itself can authenticate to the registry in two ways: with a long-lived token an administrator pasted in, or — the recommended way — as its own **service principal**: an OpenID Connect client on the relay's Authelia named `lamaste-server`. In that mode nothing long-lived is pasted anywhere; the panel proves its identity to Authelia with a secret it generated itself, Authelia issues a short-lived JWT, and the registry exchanges that JWT for a registry token.
10
12
 
11
13
  Plugins use the same Authelia to let people and programs log in. A plugin's manifest declares its OIDC clients — a CLI that logs in with the device-code flow, a service that authenticates with client credentials — and the panel writes them into Authelia's configuration when the plugin is enabled.
@@ -156,7 +158,7 @@ Admin only. What is configured — never a token.
156
158
 
157
159
  ### `PUT /api/registry-credential`
158
160
 
159
- Admin only, 10 per minute. Verifies the whole chain against the registry, then stores the credential at `/etc/lamalibre/lamaste/registry-credential.json` (0600).
161
+ Admin only, 10 per minute. Verifies the whole chain against the registry, then stores the credential at `/etc/lamalibre/lamaste/registry-credential.json` (0600). From then on the panel's own installs, update staging and agent-half pinning authenticate to `registryUrl` for `scope` with the service token (see In Plain English); a package published only there installs from the panel.
160
162
 
161
163
  ```json
162
164
  { "registryUrl": "https://feria.example.com", "auth": { "kind": "oidc" } }
@@ -316,7 +316,7 @@ curl -s --cert client.p12:password \
316
316
  | `chiselArgs` | `string[]` | Chisel client arguments: `client`, the server URL, and one `R:127.0.0.1:<port>:127.0.0.1:<port>` remote per tunnel. No `--tls-skip-verify` (the agent verifies the relay's certificate) and no credential — the agent supplies its own through the service environment, never argv |
317
317
  | `chiselCredentialIssuedAt` | `string \| null` | When the agent's current Chisel credential was issued — a timestamp, never the credential. An agent holding a credential with a different issue time (an administrator rotated it) fetches the current one from `GET /api/agents/me/chisel-credential` |
318
318
  | `tunnels` | `array` | The agent's enabled tunnels with port and subdomain |
319
- | `hostFactsHash` | `string \| null` | Agent certificates only: the SHA-256 of the [host facts](agent-hosted-plugins.md#host-facts) the relay stores for the agent, computed as the agent computes it over its own (`hostFactsHash` in `@lamalibre/lamaste@4.4.0`); `null` when it stores none. The agent's sync reports its facts again when this is not the hash of its own, so a record replaced since its last report — the agent enrolled again on its label, the relay restored from a backup — has them again after one sync, not a day. An agent behind a relay without the field reports as before |
319
+ | `hostFactsHash` | `string \| null` | Agent certificates only: the SHA-256 of the [host facts](agent-hosted-plugins.md#host-facts) the relay stores for the agent, computed as the agent computes it over its own (`hostFactsHash` in `@lamalibre/lamaste@4.4.1`); `null` when it stores none. The agent's sync reports its facts again when this is not the hash of its own, so a record replaced since its last report — the agent enrolled again on its label, the relay restored from a backup — has them again after one sync, not a day. An agent behind a relay without the field reports as before |
320
320
 
321
321
  **Errors:**
322
322
 
@@ -197,7 +197,7 @@ sudo chown lamaste-authelia:lamaste-authelia /etc/authelia/db.sqlite3* /etc/auth
197
197
  sudo chmod 600 /etc/authelia/db.sqlite3* /etc/authelia/notifications.txt
198
198
  ```
199
199
 
200
- A backup taken on a version before the privilege boundary holds root-owned Authelia files and older unit files. After restoring one, re-run `npx @lamalibre/create-lamaste@4.4.0 --yes`: the redeploy rewrites the `chisel` and `authelia` units and converts the ownership of `/etc/authelia`, the Chisel key and the PKI directory (see [Upgrades](upgrades.md)).
200
+ A backup taken on a version before the privilege boundary holds root-owned Authelia files and older unit files. After restoring one, re-run `npx @lamalibre/create-lamaste@4.4.1 --yes`: the redeploy rewrites the `chisel` and `authelia` units and converts the ownership of `/etc/authelia`, the Chisel key and the PKI directory (see [Upgrades](upgrades.md)).
201
201
 
202
202
  The panel re-applies the authfile's group and mode itself on every start, so a Chisel that fails to read it before the panel is up is started again by the panel's reconciliation.
203
203
 
@@ -29,7 +29,7 @@ It stops and disables the panel, gatekeeper, Chisel and Authelia; removes their
29
29
  The manual guide is also available from the CLI:
30
30
 
31
31
  ```bash
32
- npx @lamalibre/create-lamaste@4.4.0 --uninstall
32
+ npx @lamalibre/create-lamaste@4.4.1 --uninstall
33
33
  ```
34
34
 
35
35
  This prints the same steps listed below.
@@ -27,7 +27,7 @@ Coming from 3.x, upgrade the **agents first**, as 4.0 required: agents from befo
27
27
  On each agent machine, after the server:
28
28
 
29
29
  ```bash
30
- sudo npm install -g @lamalibre/lamaste-agent@4.4.0
30
+ sudo npm install -g @lamalibre/lamaste-agent@4.4.1
31
31
  lamaste-agent update
32
32
  ```
33
33
 
@@ -84,7 +84,7 @@ exit
84
84
 
85
85
  ### Updating Chisel
86
86
 
87
- Chisel is the tunnel server binary at `/usr/local/bin/chisel`. Lamaste pins it: server and agents run Chisel 1.12.0 (`CHISEL_RELEASE` in `@lamalibre/lamaste@4.4.0`), downloaded from the fixed release URL and verified against a pinned SHA-256 before it is unpacked. **Do not replace the binary by hand.** On the server, `create-lamaste` installs it (as root) and replaces a binary that reports any other version; the panel cannot install binaries, and only logs an error at startup when the installed Chisel is not the pinned release (it then restarts Chisel on every authfile change). Each agent's sync replaces its own copy.
87
+ Chisel is the tunnel server binary at `/usr/local/bin/chisel`. Lamaste pins it: server and agents run Chisel 1.12.0 (`CHISEL_RELEASE` in `@lamalibre/lamaste@4.4.1`), downloaded from the fixed release URL and verified against a pinned SHA-256 before it is unpacked. **Do not replace the binary by hand.** On the server, `create-lamaste` installs it (as root) and replaces a binary that reports any other version; the panel cannot install binaries, and only logs an error at startup when the installed Chisel is not the pinned release (it then restarts Chisel on every authfile change). Each agent's sync replaces its own copy.
88
88
 
89
89
  A new Chisel version therefore arrives with a Lamaste release that changes the pin: re-run the installer, which installs it and restarts Chisel. To check what is running:
90
90
 
@@ -98,7 +98,7 @@ Tunnel clients reconnect on their own after the brief interruption (they retry a
98
98
 
99
99
  ### Updating Authelia
100
100
 
101
- Authelia is the authentication server binary at `/usr/local/bin/authelia`. Lamaste pins it too: Authelia 4.39.28 (`AUTHELIA_RELEASE` in `@lamalibre/lamaste@4.4.0`), downloaded by `create-lamaste` from its fixed release URL and verified against a pinned SHA-256 per architecture. A new Authelia version arrives with a Lamaste release that changes the pin — re-run the installer. It stops Authelia while it replaces the binary and starts it again.
101
+ Authelia is the authentication server binary at `/usr/local/bin/authelia`. Lamaste pins it too: Authelia 4.39.28 (`AUTHELIA_RELEASE` in `@lamalibre/lamaste@4.4.1`), downloaded by `create-lamaste` from its fixed release URL and verified against a pinned SHA-256 per architecture. A new Authelia version arrives with a Lamaste release that changes the pin — re-run the installer. It stops Authelia while it replaces the binary and starts it again.
102
102
 
103
103
  An installed Authelia **newer** than the pin is kept, never downgraded: its database may already use a schema the pinned release cannot read, and Authelia refuses to start on a database newer than itself. If you install a newer release by hand, stop the service, replace the binary as root with a `root:root 0755` file, and start it again; check the [Authelia changelog](https://github.com/authelia/authelia/blob/master/CHANGELOG.md) first, since the configuration format may change between versions.
104
104
 
@@ -237,7 +237,7 @@ The panel server, CLI, client, docs and gatekeeper are always redeployed (files
237
237
 
238
238
  ### Version Pinning
239
239
 
240
- Chisel and Authelia are pinned by version and SHA-256 in `packages/core/lib/src/constants.ts` (`CHISEL_RELEASE`, `AUTHELIA_RELEASE`); changing a pin means updating the version and every digest there. The downloads are verified by `downloadVerifiedChisel` / `downloadVerifiedAuthelia` (exported from `@lamalibre/lamaste@4.4.0`) before anything is unpacked; the server installer uses them in `packages/provisioners/server/src/lib/binaries.js`. The configuration the panel writes needs Authelia 4.38 or later.
240
+ Chisel and Authelia are pinned by version and SHA-256 in `packages/core/lib/src/constants.ts` (`CHISEL_RELEASE`, `AUTHELIA_RELEASE`); changing a pin means updating the version and every digest there. The downloads are verified by `downloadVerifiedChisel` / `downloadVerifiedAuthelia` (exported from `@lamalibre/lamaste@4.4.1`) before anything is unpacked; the server installer uses them in `packages/provisioners/server/src/lib/binaries.js`. The configuration the panel writes needs Authelia 4.38 or later.
241
241
 
242
242
  ### Automated Updates
243
243
 
@@ -259,7 +259,7 @@ sudo dpkg-reconfigure -plow unattended-upgrades
259
259
  | Panel server | `/opt/lamalibre/lamaste/lamaste-serverd/` | Re-run `npx @lamalibre/create-lamaste@latest` |
260
260
  | Panel client | `/opt/lamalibre/lamaste/lamaste-server-ui/` | Re-run `npx @lamalibre/create-lamaste@latest` |
261
261
  | Chisel | `/usr/local/bin/chisel` | Pinned; installed by `create-lamaste` |
262
- | Agents | `sudo npm install -g @lamalibre/lamaste-agent@4.4.0` | Upgrade after the server, then `lamaste-agent update` |
262
+ | Agents | `sudo npm install -g @lamalibre/lamaste-agent@4.4.1` | Upgrade after the server, then `lamaste-agent update` |
263
263
  | Authelia | `/usr/local/bin/authelia` | Pinned; installed by `create-lamaste` (newer is kept) |
264
264
  | nginx | System package | `sudo apt-get update && sudo apt-get upgrade` |
265
265
  | certbot | System package | `sudo apt-get update && sudo apt-get upgrade` |
@@ -733,7 +733,7 @@ The RSA-4096 private key (PKCS#8) Authelia signs ID and JWT access tokens with.
733
733
 
734
734
  ## `/etc/lamalibre/lamaste/registry-credential.json`
735
735
 
736
- The panel's service credential on the Lamalibre npm registry (Feria), used to broker one-hour install tokens for agents (`GET /api/agents/me/registry-credential`) and to revoke a user's tokens when a `feria-*` group is lost.
736
+ The panel's service credential on the Lamalibre npm registry (Feria), used for the panel's own npm calls (plugin installs, update staging, agent-half pinning — through a throw-away userconfig, never a `.npmrc` here), to broker one-hour install tokens for agents (`GET /api/agents/me/registry-credential`) and to revoke a user's tokens when a `feria-*` group is lost.
737
737
 
738
738
  ```json
739
739
  {
@@ -926,7 +926,7 @@ Service files use per-agent names:
926
926
  - macOS: `com.lamalibre.lamaste.chisel-<label>` (plist label), `~/Library/LaunchAgents/com.lamalibre.lamaste.chisel-<label>.plist` — mode 0600, because it carries `AUTH` in `EnvironmentVariables`
927
927
  - Linux: `lamalibre-lamaste-chisel-<label>` (unit name), `~/.config/systemd/user/lamalibre-lamaste-chisel-<label>.service` — a systemd **user** unit with `EnvironmentFile=` pointing at `chisel.env`; enable lingering (`sudo loginctl enable-linger <user>`) for it to start at boot without a login
928
928
 
929
- Both run `chisel client --max-retry-interval 30s https://tunnel.<domain>:443 R:127.0.0.1:<port>:127.0.0.1:<port> ...` with TLS verification and no credential in argv. They are generated by `@lamalibre/lamaste@4.4.0/agent` (`chisel-service.ts`) and rewritten by `lamaste-agent setup`, `update` and `sync` — do not edit them by hand. An agent carrying no tunnels has its service unloaded (Chisel cannot run without a remote).
929
+ Both run `chisel client --max-retry-interval 30s https://tunnel.<domain>:443 R:127.0.0.1:<port>:127.0.0.1:<port> ...` with TLS verification and no credential in argv. They are generated by `@lamalibre/lamaste@4.4.1/agent` (`chisel-service.ts`) and rewritten by `lamaste-agent setup`, `update` and `sync` — do not edit them by hand. An agent carrying no tunnels has its service unloaded (Chisel cannot run without a remote).
930
930
 
931
931
  The sync timer, which runs `<node> <installed lamaste-agent> sync --label <label> --quiet` (`<node>` is a stable path such as `/opt/homebrew/bin/node` when one on `PATH` resolves to the running binary, so a Node.js upgrade does not break it):
932
932
 
@@ -24,7 +24,7 @@ If you are reading documentation and hit an unfamiliar term, this is the place t
24
24
 
25
25
  **Where it appears:**
26
26
 
27
- - Enrolled from the panel (**Certificates → Enrollment Token**) and `lamaste-agent setup` (installed globally with `npm install -g @lamalibre/lamaste-agent@4.4.0`)
27
+ - Enrolled from the panel (**Certificates → Enrollment Token**) and `lamaste-agent setup` (installed globally with `npm install -g @lamalibre/lamaste-agent@4.4.1`)
28
28
  - Agent registry: `/etc/lamalibre/lamaste/agents.json` on the server; `~/.lamalibre/lamaste/agents/<label>/` on the agent
29
29
  - The **Agent** column and field on the panel's Tunnels page
30
30
 
@@ -242,7 +242,7 @@ See also: [mTLS](#mtls-mutual-tls), [CA](#ca-certificate-authority), [PKCS12](#p
242
242
 
243
243
  **Where it appears:**
244
244
 
245
- - Generated by `lamaste-agent setup` / `update` (implementation in `@lamalibre/lamaste@4.4.0/agent`)
245
+ - Generated by `lamaste-agent setup` / `update` (implementation in `@lamalibre/lamaste@4.4.1/agent`)
246
246
  - Installed to `~/Library/LaunchAgents/com.lamalibre.lamaste.chisel-<label>.plist` on the agent
247
247
  - Manages the Chisel client process lifecycle
248
248
 
@@ -373,7 +373,7 @@ See also: [Client Certificate](#client-certificate), [CA](#ca-certificate-author
373
373
 
374
374
  **Plain English:** A small scheduled job on every agent that checks with the panel every 30 seconds and adjusts the tunnel client when the agent's tunnels changed — so tunnels created, disabled or moved in the panel reach the agent without anyone running a command on it.
375
375
 
376
- **Technical:** Runs `lamaste-agent sync --label <label>`, which fetches `GET /api/tunnels/agent-config` and converges the agent's Chisel client with it (relay URL, credential, pinned binary, granted remotes, running or stopped). It exists because Chisel rejects a client's whole session when one requested remote is not granted: an agent must drop a revoked tunnel promptly or all its other tunnels go dark. Requires a global install of `@lamalibre/lamaste-agent@4.4.0`.
376
+ **Technical:** Runs `lamaste-agent sync --label <label>`, which fetches `GET /api/tunnels/agent-config` and converges the agent's Chisel client with it (relay URL, credential, pinned binary, granted remotes, running or stopped). It exists because Chisel rejects a client's whole session when one requested remote is not granted: an agent must drop a revoked tunnel promptly or all its other tunnels go dark. Requires a global install of `@lamalibre/lamaste-agent@4.4.1`.
377
377
 
378
378
  **Where it appears:**
379
379
 
@@ -1,11 +1,11 @@
1
1
  # Installer Flags
2
2
 
3
- > CLI flags, environment variables, and exit codes for `npx @lamalibre/create-lamaste@4.4.0`.
3
+ > CLI flags, environment variables, and exit codes for `npx @lamalibre/create-lamaste@4.4.1`.
4
4
 
5
5
  ## Usage
6
6
 
7
7
  ```bash
8
- npx @lamalibre/create-lamaste@4.4.0 [flags]
8
+ npx @lamalibre/create-lamaste@4.4.1 [flags]
9
9
  ```
10
10
 
11
11
  The installer runs as root on a fresh Ubuntu 24.04 server. It is completely non-interactive by default — all configuration happens later through the browser-based onboarding wizard.
@@ -26,7 +26,7 @@ The installer runs as root on a fresh Ubuntu 24.04 server. It is completely non-
26
26
  Prints a description of what the installer does, lists all flags, and exits with code 0. Does not modify anything on the system.
27
27
 
28
28
  ```bash
29
- npx @lamalibre/create-lamaste@4.4.0 --help
29
+ npx @lamalibre/create-lamaste@4.4.1 --help
30
30
  ```
31
31
 
32
32
  ### `--yes`, `-y`
@@ -34,7 +34,7 @@ npx @lamalibre/create-lamaste@4.4.0 --help
34
34
  Skips the interactive confirmation prompt that appears before installation begins. Useful for automated deployments or scripts where you cannot press Enter.
35
35
 
36
36
  ```bash
37
- npx @lamalibre/create-lamaste@4.4.0 --yes
37
+ npx @lamalibre/create-lamaste@4.4.1 --yes
38
38
  ```
39
39
 
40
40
  Without this flag, the installer displays a banner listing all changes it will make and waits for the user to press Enter or Ctrl+C.
@@ -51,7 +51,7 @@ Skips all OS hardening tasks:
51
51
  Everything else (Node.js, nginx, mTLS, panel deployment) still runs.
52
52
 
53
53
  ```bash
54
- npx @lamalibre/create-lamaste@4.4.0 --skip-harden
54
+ npx @lamalibre/create-lamaste@4.4.1 --skip-harden
55
55
  ```
56
56
 
57
57
  Use this if you manage your own firewall rules, already have fail2ban configured, or are running on a platform that handles security at a different layer.
@@ -61,7 +61,7 @@ Use this if you manage your own firewall rules, already have fail2ban configured
61
61
  Allows the installer to run on a machine with a private or non-routable IP address (e.g., `192.168.x.x`, `10.x.x.x`). By default, the installer requires a public IP.
62
62
 
63
63
  ```bash
64
- npx @lamalibre/create-lamaste@4.4.0 --dev
64
+ npx @lamalibre/create-lamaste@4.4.1 --dev
65
65
  ```
66
66
 
67
67
  This is useful for:
@@ -76,7 +76,7 @@ When `--dev` is active, the installer prints the detected IP with a note: `(dev
76
76
  Forces the installer to run the complete installation pipeline, ignoring skip guards that would normally detect an existing installation. This is useful when an installation is in an inconsistent state and you want to ensure all components are fully deployed.
77
77
 
78
78
  ```bash
79
- npx @lamalibre/create-lamaste@4.4.0 --force-full
79
+ npx @lamalibre/create-lamaste@4.4.1 --force-full
80
80
  ```
81
81
 
82
82
  Use this if:
@@ -90,7 +90,7 @@ Use this if:
90
90
  Prints a step-by-step guide for manually removing Lamaste from the server, then exits with code 0. Does not modify anything on the system.
91
91
 
92
92
  ```bash
93
- npx @lamalibre/create-lamaste@4.4.0 --uninstall
93
+ npx @lamalibre/create-lamaste@4.4.1 --uninstall
94
94
  ```
95
95
 
96
96
  The printed guide first points to the automated removal, `sudo lamaste-server uninstall`, then covers:
@@ -207,25 +207,25 @@ Components that are always redeployed on re-run:
207
207
 
208
208
  ```bash
209
209
  ssh root@203.0.113.42
210
- npx @lamalibre/create-lamaste@4.4.0
210
+ npx @lamalibre/create-lamaste@4.4.1
211
211
  ```
212
212
 
213
213
  **Automated install (no confirmation prompt):**
214
214
 
215
215
  ```bash
216
- npx @lamalibre/create-lamaste@4.4.0 --yes
216
+ npx @lamalibre/create-lamaste@4.4.1 --yes
217
217
  ```
218
218
 
219
219
  **Install without OS hardening:**
220
220
 
221
221
  ```bash
222
- npx @lamalibre/create-lamaste@4.4.0 --yes --skip-harden
222
+ npx @lamalibre/create-lamaste@4.4.1 --yes --skip-harden
223
223
  ```
224
224
 
225
225
  **Test in a local VM:**
226
226
 
227
227
  ```bash
228
- npx @lamalibre/create-lamaste@4.4.0 --dev
228
+ npx @lamalibre/create-lamaste@4.4.1 --dev
229
229
  ```
230
230
 
231
231
  **Update an existing installation:**
@@ -237,7 +237,7 @@ npx @lamalibre/create-lamaste@latest --yes
237
237
  **View the uninstall guide:**
238
238
 
239
239
  ```bash
240
- npx @lamalibre/create-lamaste@4.4.0 --uninstall
240
+ npx @lamalibre/create-lamaste@4.4.1 --uninstall
241
241
  ```
242
242
 
243
243
  ## Quick Reference
@@ -34,7 +34,7 @@ A tunnel's port is also a loopback port on the relay: its vhost proxies to `127.
34
34
  | 9292 | IP-based panel listener |
35
35
  | 9294 | Gatekeeper |
36
36
 
37
- A tunnel on one of them would publish that internal service on a public hostname. For the panel that is an administrator takeover, not a leak: the panel trusts the client-certificate headers nginx sets. `RESERVED_TUNNEL_PORTS` in `@lamalibre/lamaste@4.4.0` is enforced by the request schema, inside the tunnel workflow, and again when the Chisel grants are rendered. Tunnel ports must also be 1024–65535.
37
+ A tunnel on one of them would publish that internal service on a public hostname. For the panel that is an administrator takeover, not a leak: the panel trusts the client-certificate headers nginx sets. `RESERVED_TUNNEL_PORTS` in `@lamalibre/lamaste@4.4.1` is enforced by the request schema, inside the tunnel workflow, and again when the Chisel grants are rendered. Tunnel ports must also be 1024–65535.
38
38
 
39
39
  ## Systemd Units
40
40