@lamalibre/create-lamaste 4.1.0 → 4.2.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 (49) 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 +1 -1
  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 +3 -3
  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/onboarding.md +1 -1
  26. package/vendor/docs/04-api-reference/registry-and-oidc.md +26 -1
  27. package/vendor/docs/05-operations/backup-and-restore.md +1 -1
  28. package/vendor/docs/05-operations/uninstalling.md +1 -1
  29. package/vendor/docs/05-operations/upgrades.md +5 -5
  30. package/vendor/docs/06-reference/config-files.md +1 -1
  31. package/vendor/docs/06-reference/glossary.md +3 -3
  32. package/vendor/docs/06-reference/installer-flags.md +13 -13
  33. package/vendor/docs/06-reference/ports-and-services.md +1 -1
  34. package/vendor/docs/06-reference/troubleshooting.md +8 -8
  35. package/vendor/docs/gatekeeper-design.md +8 -8
  36. package/vendor/docs/identity-directory-design.md +2 -2
  37. package/vendor/docs/package.json +1 -1
  38. package/vendor/docs/todo/lib-cli-desktop-refactor.md +27 -27
  39. package/vendor/gatekeeper/package.json +1 -1
  40. package/vendor/server/package.json +2 -2
  41. package/vendor/server/src/commands/plugins.js +123 -168
  42. package/vendor/server/src/panel-api.js +4 -2
  43. package/vendor/server-ui/dist/assets/{index-B0nWuUyL.js → index-B5Wp8lu-.js} +26 -26
  44. package/vendor/server-ui/dist/index.html +1 -1
  45. package/vendor/serverd/package.json +2 -2
  46. package/vendor/serverd/src/lib/migrations/state/0005_plugin_hosting.sql +3 -0
  47. package/vendor/serverd/src/lib/plugin-oidc.js +33 -16
  48. package/vendor/serverd/src/lib/plugins.js +11 -3
  49. package/vendor/serverd/src/routes/management/storage.js +10 -2
@@ -4,7 +4,7 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>Lamaste</title>
7
- <script type="module" crossorigin src="/assets/index-B0nWuUyL.js"></script>
7
+ <script type="module" crossorigin src="/assets/index-B5Wp8lu-.js"></script>
8
8
  <link rel="stylesheet" crossorigin href="/assets/index-BqWWFlTU.css">
9
9
  </head>
10
10
  <body style="background-color: #09090b">
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lamalibre/lamaste-serverd",
3
- "version": "4.1.0",
3
+ "version": "4.2.0",
4
4
  "description": "Lamaste server daemon — REST API and plugin host",
5
5
  "license": "SEE LICENSE IN LICENSE.md",
6
6
  "publishConfig": {
@@ -29,7 +29,7 @@
29
29
  "@fastify/rate-limit": "^10.3.0",
30
30
  "@fastify/static": "^9.1.1",
31
31
  "@fastify/websocket": "^11.0.0",
32
- "@lamalibre/lamaste": "^4.1.0",
32
+ "@lamalibre/lamaste": "^4.2.0",
33
33
  "bcryptjs": "^3.0.3",
34
34
  "execa": "^9.6.1",
35
35
  "fastify": "^5.8.4",
@@ -0,0 +1,3 @@
1
+ -- Where a plugin's code runs (manifest `hosting`, 4.2). Rows written before
2
+ -- the column existed are server plugins.
3
+ ALTER TABLE plugins ADD COLUMN hosting TEXT NOT NULL DEFAULT 'server';
@@ -3,9 +3,11 @@
3
3
  *
4
4
  * Two sources declare clients on the relay's Authelia:
5
5
  *
6
- * - every installed server plugin may declare them in its manifest
7
- * (`oidcClients`, see the core `ManifestSchema`); only the clients of
8
- * enabled plugins are served;
6
+ * - every installed plugin may declare them in its manifest (`oidcClients`,
7
+ * see the core `ManifestSchema`); only the clients of enabled plugins are
8
+ * served. Hosting does not matter: an agent-hosted plugin (`hosting:
9
+ * "agent"`, never imported by the panel) registers its clients exactly as
10
+ * a server plugin does — the manifest is all this reads;
9
11
  * - the panel itself declares the confidential `lamaste-server` client
10
12
  * when the registry credential uses `auth.kind: 'oidc'`.
11
13
  *
@@ -94,6 +96,30 @@ async function collectAllClients(overrides = {}) {
94
96
  return [...(await collectPluginOidcClients()), ...lamasteOwnClient(registryAuth)];
95
97
  }
96
98
 
99
+ /**
100
+ * What Authelia should serve and which secrets the store should hold, from
101
+ * the stored state: every installed plugin's clients (an installed plugin
102
+ * keeps its confidential-client secret while disabled), the clients of
103
+ * enabled plugins served, and the panel's own scopes when its client is.
104
+ * Pure with respect to Authelia — nothing is written.
105
+ *
106
+ * @param {{ registryAuth?: { registryUrl: string } | null }} [overrides] see collectAllClients
107
+ * @returns {Promise<{
108
+ * confidential: Array<{ id: string, plugin: string }>,
109
+ * served: Array<import('@lamalibre/lamaste/server').AutheliaOidcClient>,
110
+ * scopes: Record<string, { claims: readonly string[] }>,
111
+ * }>}
112
+ */
113
+ export async function planOidcClients(overrides = {}) {
114
+ const all = await collectAllClients(overrides);
115
+ const served = all.filter((c) => c.enabled).map(({ enabled: _enabled, ...client }) => client);
116
+ return {
117
+ confidential: all.filter((c) => c.confidential).map((c) => ({ id: c.id, plugin: c.plugin })),
118
+ served,
119
+ scopes: served.some((c) => c.plugin === 'lamaste') ? LAMASTE_REGISTRY_OIDC_SCOPES : {},
120
+ };
121
+ }
122
+
97
123
  /**
98
124
  * Sync Authelia's OIDC clients and the secret store with the plugins and
99
125
  * the registry credential. Returns what was done, or `null` when the panel
@@ -115,11 +141,8 @@ export async function syncOidcClients(logger, overrides = {}) {
115
141
  return null;
116
142
  }
117
143
 
118
- const all = await collectAllClients(overrides);
119
- const reconciled = await reconcileOidcClientSecrets(
120
- STATE_DIR,
121
- all.filter((c) => c.confidential).map((c) => ({ id: c.id, plugin: c.plugin })),
122
- );
144
+ const { confidential, served, scopes } = await planOidcClients(overrides);
145
+ const reconciled = await reconcileOidcClientSecrets(STATE_DIR, confidential);
123
146
  if (reconciled.created.length > 0 || reconciled.removed.length > 0) {
124
147
  logger.info(
125
148
  { created: reconciled.created.map((c) => c.id), removed: reconciled.removed },
@@ -127,8 +150,6 @@ export async function syncOidcClients(logger, overrides = {}) {
127
150
  );
128
151
  }
129
152
 
130
- const served = all.filter((c) => c.enabled).map(({ enabled: _enabled, ...client }) => client);
131
- const scopes = served.some((c) => c.plugin === 'lamaste') ? LAMASTE_REGISTRY_OIDC_SCOPES : {};
132
153
  const result = await updateOidcProvider(domain, served, {
133
154
  secretDigests: reconciled.digests,
134
155
  scopes,
@@ -199,12 +220,8 @@ export async function rotatePluginOidcClientSecret(pluginName, clientId, logger)
199
220
  const digests = await readOidcClientSecretDigests(STATE_DIR);
200
221
  if (client.enabled) {
201
222
  const domain = getConfig().domain;
202
- const all = await collectAllClients();
203
- const served = all.filter((c) => c.enabled).map(({ enabled: _enabled, ...c }) => c);
204
- await updateOidcProvider(domain, served, {
205
- secretDigests: digests,
206
- scopes: served.some((c) => c.plugin === 'lamaste') ? LAMASTE_REGISTRY_OIDC_SCOPES : {},
207
- });
223
+ const { served, scopes } = await planOidcClients();
224
+ await updateOidcProvider(domain, served, { secretDigests: digests, scopes });
208
225
  }
209
226
  logger.info({ plugin: pluginName, clientId }, 'OIDC client secret rotated');
210
227
  return {
@@ -14,6 +14,7 @@ import path from 'node:path';
14
14
  import { z } from 'zod';
15
15
  import { execa } from 'execa';
16
16
  import {
17
+ DEFAULT_PLUGIN_HOSTING,
17
18
  RESERVED_API_PREFIXES,
18
19
  RESERVED_NAV_LABELS,
19
20
  derivePluginRoute,
@@ -60,8 +61,8 @@ async function getStmts() {
60
61
  insert: db.prepare(`
61
62
  INSERT INTO plugins
62
63
  (name, display_name, package_name, version, description, capabilities,
63
- packages, panel, config, modes, status, installed_at, enabled_at)
64
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
64
+ packages, panel, config, modes, hosting, status, installed_at, enabled_at)
65
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
65
66
  `),
66
67
  deleteByName: db.prepare('DELETE FROM plugins WHERE name = ?'),
67
68
  deleteAll: db.prepare('DELETE FROM plugins'),
@@ -86,7 +87,10 @@ async function getStmts() {
86
87
  // JSON-encoded TEXT in storage. They round-trip through JSON.parse/stringify
87
88
  // so callers see the same shape the JSON-backed module returned — including
88
89
  // `undefined` for optional fields (mirroring the JSON object layout where
89
- // missing keys are absent rather than null).
90
+ // missing keys are absent rather than null). `hosting` is plain TEXT
91
+ // ('server' | 'agent'; rows from before 4.2 default to 'server'): an
92
+ // agent-hosted plugin is installed (npm, manifest) like any other, but the
93
+ // plugin host never imports it — see `pluginHostPlugin`.
90
94
 
91
95
  function rowToPlugin(row) {
92
96
  const entry = {
@@ -94,6 +98,7 @@ function rowToPlugin(row) {
94
98
  packageName: row.package_name,
95
99
  version: row.version,
96
100
  capabilities: row.capabilities ? JSON.parse(row.capabilities) : [],
101
+ hosting: row.hosting,
97
102
  status: row.status,
98
103
  installedAt: row.installed_at,
99
104
  };
@@ -155,6 +160,7 @@ export async function writePlugins(data) {
155
160
  nullableJson(p.panel),
156
161
  nullableJson(p.config),
157
162
  nullableJson(p.modes),
163
+ p.hosting ?? DEFAULT_PLUGIN_HOSTING,
158
164
  p.status,
159
165
  p.installedAt,
160
166
  nullableString(p.enabledAt),
@@ -359,6 +365,7 @@ export function installPlugin(packageName, logger) {
359
365
  panel: manifest.panel,
360
366
  config: manifest.config,
361
367
  modes: manifest.modes,
368
+ hosting: manifest.hosting,
362
369
  status: 'disabled',
363
370
  installedAt: new Date().toISOString(),
364
371
  };
@@ -376,6 +383,7 @@ export function installPlugin(packageName, logger) {
376
383
  nullableJson(entry.panel),
377
384
  nullableJson(entry.config),
378
385
  nullableJson(entry.modes),
386
+ entry.hosting,
379
387
  entry.status,
380
388
  entry.installedAt,
381
389
  null,
@@ -1,4 +1,5 @@
1
1
  import { z } from 'zod';
2
+ import { isAgentHostedPlugin } from '@lamalibre/lamaste';
2
3
  import {
3
4
  registerStorageServer,
4
5
  removeStorageServer,
@@ -118,12 +119,19 @@ export default async function storageRoutes(fastify, _opts) {
118
119
 
119
120
  // Validate plugin exists in registry
120
121
  const registry = await readPlugins();
121
- const pluginExists = registry.plugins.some((p) => p.name === body.pluginName);
122
- if (!pluginExists) {
122
+ const plugin = registry.plugins.find((p) => p.name === body.pluginName);
123
+ if (!plugin) {
123
124
  return reply.code(404).send({
124
125
  error: `Plugin "${body.pluginName}" not found in registry`,
125
126
  });
126
127
  }
128
+ // A binding is handed to the plugin's server code when the panel
129
+ // mounts it; an agent-hosted plugin has none to receive it.
130
+ if (isAgentHostedPlugin(plugin)) {
131
+ return reply.code(400).send({
132
+ error: `Plugin "${body.pluginName}" is agent-hosted — storage binds to server-hosted plugins only`,
133
+ });
134
+ }
127
135
 
128
136
  try {
129
137
  const binding = await bindPluginStorage(body.pluginName, body.storageServerId);