@lamalibre/create-lamaste 4.4.0 → 4.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/scripts/lamaste-priv +200 -16
- package/src/index.js +1 -0
- package/src/lib/backend-cookie.js +65 -0
- package/src/lib/ip-panel-vhost.js +208 -0
- package/src/lib/service-config.js +5 -3
- package/src/tasks/nginx.js +18 -135
- package/src/tasks/redeploy.js +39 -1
- package/vendor/docs/00-introduction/how-it-works.md +2 -2
- package/vendor/docs/00-introduction/quickstart.md +5 -5
- package/vendor/docs/00-introduction/what-is-lamaste.md +5 -5
- package/vendor/docs/01-concepts/authentication.md +1 -1
- package/vendor/docs/01-concepts/authorization.md +66 -48
- package/vendor/docs/01-concepts/certificates.md +3 -3
- package/vendor/docs/01-concepts/mtls.md +2 -2
- package/vendor/docs/01-concepts/security-model.md +70 -33
- package/vendor/docs/01-concepts/tickets.md +2 -2
- package/vendor/docs/01-concepts/tunneling.md +4 -4
- package/vendor/docs/02-guides/agent-setup.md +5 -5
- package/vendor/docs/02-guides/certificate-management.md +2 -2
- package/vendor/docs/02-guides/cloud-provisioning.md +3 -3
- package/vendor/docs/02-guides/desktop-app-setup.md +10 -10
- package/vendor/docs/02-guides/installation.md +3 -3
- package/vendor/docs/02-guides/managing-users.md +113 -25
- package/vendor/docs/02-guides/onboarding.md +2 -2
- package/vendor/docs/02-guides/standalone-plugin-with-tickets.md +2 -2
- package/vendor/docs/03-architecture/installer-flow.md +2 -2
- package/vendor/docs/03-architecture/installer.md +4 -4
- package/vendor/docs/03-architecture/management-flow.md +3 -1
- package/vendor/docs/03-architecture/nginx-configuration.md +165 -18
- package/vendor/docs/03-architecture/overview.md +13 -17
- package/vendor/docs/03-architecture/panel-client.md +6 -6
- package/vendor/docs/03-architecture/panel-server.md +3 -2
- package/vendor/docs/03-architecture/state-management.md +4 -8
- package/vendor/docs/03-architecture/system-overview.md +1 -1
- package/vendor/docs/04-api-reference/agent-hosted-plugins.md +292 -23
- package/vendor/docs/04-api-reference/app-host.md +3 -3
- package/vendor/docs/04-api-reference/gatekeeper.md +60 -5
- package/vendor/docs/04-api-reference/lifecycle-hooks.md +15 -13
- package/vendor/docs/04-api-reference/onboarding.md +1 -1
- package/vendor/docs/04-api-reference/registry-and-oidc.md +6 -4
- package/vendor/docs/04-api-reference/tunnels.md +67 -29
- package/vendor/docs/04-api-reference/users.md +241 -45
- package/vendor/docs/05-operations/backup-and-restore.md +35 -26
- package/vendor/docs/05-operations/monitoring.md +39 -0
- package/vendor/docs/05-operations/uninstalling.md +2 -1
- package/vendor/docs/05-operations/upgrades.md +15 -6
- package/vendor/docs/06-reference/config-files.md +83 -189
- package/vendor/docs/06-reference/glossary.md +3 -3
- package/vendor/docs/06-reference/installer-flags.md +13 -13
- package/vendor/docs/06-reference/ports-and-services.md +25 -26
- package/vendor/docs/06-reference/troubleshooting.md +8 -8
- package/vendor/docs/gatekeeper-design.md +10 -8
- package/vendor/docs/identity-directory-design.md +48 -14
- package/vendor/docs/package.json +1 -1
- package/vendor/docs/todo/lib-cli-desktop-refactor.md +27 -27
- package/vendor/gatekeeper/dist/index.d.ts +2 -1
- package/vendor/gatekeeper/dist/index.d.ts.map +1 -1
- package/vendor/gatekeeper/dist/index.js +2 -0
- package/vendor/gatekeeper/dist/index.js.map +1 -1
- package/vendor/gatekeeper/dist/lib/grants.d.ts +6 -0
- package/vendor/gatekeeper/dist/lib/grants.d.ts.map +1 -1
- package/vendor/gatekeeper/dist/lib/grants.js +6 -1
- package/vendor/gatekeeper/dist/lib/grants.js.map +1 -1
- package/vendor/gatekeeper/dist/lib/groups.d.ts +5 -0
- package/vendor/gatekeeper/dist/lib/groups.d.ts.map +1 -1
- package/vendor/gatekeeper/dist/lib/groups.js +5 -1
- package/vendor/gatekeeper/dist/lib/groups.js.map +1 -1
- package/vendor/gatekeeper/dist/lib/principals.d.ts +29 -0
- package/vendor/gatekeeper/dist/lib/principals.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/lib/principals.js +67 -0
- package/vendor/gatekeeper/dist/lib/principals.js.map +1 -0
- package/vendor/gatekeeper/dist/lib/tunnels.d.ts +13 -0
- package/vendor/gatekeeper/dist/lib/tunnels.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/lib/tunnels.js +68 -0
- package/vendor/gatekeeper/dist/lib/tunnels.js.map +1 -0
- package/vendor/gatekeeper/dist/lib/types.d.ts +19 -0
- package/vendor/gatekeeper/dist/lib/types.d.ts.map +1 -1
- package/vendor/gatekeeper/dist/server/index.d.ts.map +1 -1
- package/vendor/gatekeeper/dist/server/index.js +22 -48
- package/vendor/gatekeeper/dist/server/index.js.map +1 -1
- package/vendor/gatekeeper/dist/server/routes/authz.d.ts +15 -1
- package/vendor/gatekeeper/dist/server/routes/authz.d.ts.map +1 -1
- package/vendor/gatekeeper/dist/server/routes/authz.js +250 -103
- package/vendor/gatekeeper/dist/server/routes/authz.js.map +1 -1
- package/vendor/gatekeeper/dist/server/routes/diagnostic.d.ts.map +1 -1
- package/vendor/gatekeeper/dist/server/routes/diagnostic.js +61 -4
- package/vendor/gatekeeper/dist/server/routes/diagnostic.js.map +1 -1
- package/vendor/gatekeeper/dist/server/routes/principals.d.ts +3 -0
- package/vendor/gatekeeper/dist/server/routes/principals.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/server/routes/principals.js +27 -0
- package/vendor/gatekeeper/dist/server/routes/principals.js.map +1 -0
- package/vendor/gatekeeper/package.json +2 -2
- package/vendor/server/bin/lamaste-server.js +15 -1
- package/vendor/server/package.json +2 -2
- package/vendor/server/src/commands/plugin-identity.js +335 -0
- package/vendor/server/src/commands/plugins.js +99 -11
- package/vendor/server/src/commands/registry-credential.js +2 -1
- package/vendor/server/src/commands/tunnels.js +102 -2
- package/vendor/server/src/commands/uninstall.js +3 -1
- package/vendor/server/src/commands/users.js +358 -0
- package/vendor/server/src/plugin-approval.js +107 -5
- package/vendor/server/src/plugin-identity.js +382 -0
- package/vendor/server-ui/dist/assets/index-D-fer5ef.css +1 -0
- package/vendor/server-ui/dist/assets/index-DSaDPJaw.js +84 -0
- package/vendor/server-ui/dist/index.html +2 -2
- package/vendor/serverd/package.json +2 -2
- package/vendor/serverd/src/index.js +25 -1
- package/vendor/serverd/src/lib/audience.js +576 -0
- package/vendor/serverd/src/lib/authelia.js +78 -7
- package/vendor/serverd/src/lib/config.js +68 -43
- package/vendor/serverd/src/lib/directory.js +1113 -0
- package/vendor/serverd/src/lib/entitlements.js +517 -0
- package/vendor/serverd/src/lib/gatekeeper-client.js +215 -10
- package/vendor/serverd/src/lib/hook-pipelines.js +13 -0
- package/vendor/serverd/src/lib/identity-sync.js +905 -0
- package/vendor/serverd/src/lib/migrations/state/0013_identity_directory.sql +158 -0
- package/vendor/serverd/src/lib/migrations/state/0014_identity_ledger.sql +20 -0
- package/vendor/serverd/src/lib/mtls.js +6 -2
- package/vendor/serverd/src/lib/nginx.js +279 -48
- package/vendor/serverd/src/lib/plugin-hooks.js +98 -45
- package/vendor/serverd/src/lib/plugin-hosting.js +33 -5
- package/vendor/serverd/src/lib/plugin-lifecycle.js +100 -9
- package/vendor/serverd/src/lib/plugin-packages.js +30 -22
- package/vendor/serverd/src/lib/plugins.js +94 -15
- package/vendor/serverd/src/lib/registry-credential.js +46 -6
- package/vendor/serverd/src/lib/request-logging.js +19 -0
- package/vendor/serverd/src/lib/retained-data.js +50 -4
- package/vendor/serverd/src/lib/state.js +18 -0
- package/vendor/serverd/src/lib/tunnel-hooks.js +4 -1
- package/vendor/serverd/src/lib/user-access-session.js +21 -11
- package/vendor/serverd/src/lib/user-caller.js +67 -0
- package/vendor/serverd/src/lib/user-surface.js +733 -0
- package/vendor/serverd/src/lib/vhost-reconcile.js +136 -34
- package/vendor/serverd/src/middleware/user-access-session.js +9 -0
- package/vendor/serverd/src/routes/agent-forwarder.js +4 -2
- package/vendor/serverd/src/routes/invite.js +53 -12
- package/vendor/serverd/src/routes/management/gatekeeper-proxy.js +107 -16
- package/vendor/serverd/src/routes/management/identity-directory.js +79 -0
- package/vendor/serverd/src/routes/management/invitations.js +78 -68
- package/vendor/serverd/src/routes/management/plugin-identity.js +221 -0
- package/vendor/serverd/src/routes/management/registry-credential.js +1 -1
- package/vendor/serverd/src/routes/management/tunnels.js +98 -2
- package/vendor/serverd/src/routes/management/users.js +290 -208
- package/vendor/serverd/src/routes/management.js +4 -0
- package/vendor/serverd/src/routes/onboarding/provision.js +14 -1
- package/vendor/serverd/src/routes/user-access.js +17 -4
- package/vendor/serverd/src/routes/user-surface.js +69 -0
- package/vendor/server-ui/dist/assets/index-25wo_8av.css +0 -1
- package/vendor/server-ui/dist/assets/index-CESyyK8V.js +0 -84
|
@@ -52,6 +52,13 @@
|
|
|
52
52
|
* configure <name> [--set key=value]... [--clear key]... [--secrets-stdin]
|
|
53
53
|
* Change them (kept by its agent half); in a terminal without
|
|
54
54
|
* flags, field by field — a secret never on the command line
|
|
55
|
+
* identity <name> The people of an agent-hosted plugin that declares identity:
|
|
56
|
+
* its roles and attributes, approval, readiness, the feed, erasures
|
|
57
|
+
* audience <name> Its members and the users who left, as its agent half is told
|
|
58
|
+
* entitlements <name> [--user <u> | --group <g> --roles a,b] [--revoke --user|--group]
|
|
59
|
+
* Who holds which of its roles; give or take them away
|
|
60
|
+
* identity-sync <name> [--reconcile]
|
|
61
|
+
* Sync its audience to its agent half now
|
|
55
62
|
*
|
|
56
63
|
* Every subcommand goes through the running panel (`/api/plugins/*`, admin
|
|
57
64
|
* certificate): the panel owns the plugin registry (SQLite `state.db`), runs
|
|
@@ -69,10 +76,13 @@ import { PANEL_SERVICE } from '../config.js';
|
|
|
69
76
|
import { panelRequest } from '../panel-api.js';
|
|
70
77
|
import { emit, emitStep, emitError, emitComplete, emitLog } from '../ndjson.js';
|
|
71
78
|
import { configureCmd, settingsCmd } from './plugin-settings.js';
|
|
79
|
+
import { audienceCmd, entitlementsCmd, identityCmd, identitySyncCmd } from './plugin-identity.js';
|
|
72
80
|
import { settingsDiffLines, terminalText } from '../plugin-settings.js';
|
|
73
81
|
import {
|
|
74
82
|
APPROVE_HOOKS_USAGE,
|
|
75
83
|
approveHooksCommand,
|
|
84
|
+
identityBlockLines,
|
|
85
|
+
identityDiffLines,
|
|
76
86
|
parseApproveArgs,
|
|
77
87
|
pendingUpdateReasons,
|
|
78
88
|
} from '../plugin-approval.js';
|
|
@@ -134,6 +144,14 @@ export async function runPlugins(args, { json }) {
|
|
|
134
144
|
return settingsCmd(args.slice(1), { json });
|
|
135
145
|
case 'configure':
|
|
136
146
|
return configureCmd(args.slice(1), { json });
|
|
147
|
+
case 'identity':
|
|
148
|
+
return identityCmd(args[1], { json });
|
|
149
|
+
case 'audience':
|
|
150
|
+
return audienceCmd(args[1], { json });
|
|
151
|
+
case 'entitlements':
|
|
152
|
+
return entitlementsCmd(args.slice(1), { json });
|
|
153
|
+
case 'identity-sync':
|
|
154
|
+
return identitySyncCmd(args.slice(1), { json });
|
|
137
155
|
default:
|
|
138
156
|
printPluginUsage();
|
|
139
157
|
process.exit(sub ? 1 : 0);
|
|
@@ -191,6 +209,16 @@ ${b('Subcommands:')}
|
|
|
191
209
|
--clear puts a field back to its default or clears a secret,
|
|
192
210
|
--secrets-stdin reads {"key": "value" | null} from stdin;
|
|
193
211
|
in a terminal without flags, asked field by field
|
|
212
|
+
${c('identity <name>')} People of a plugin that declares identity: its roles and attributes,
|
|
213
|
+
approval, whether users reach it (and why not), the feed, erasures
|
|
214
|
+
${c('audience <name>')} Its members and the users who left, as its agent half is told
|
|
215
|
+
${c('entitlements <name>')} Who holds which of its roles (Gatekeeper grants with roles)
|
|
216
|
+
${c('entitlements <name> --user <u> | --group <g> --roles a,b')}
|
|
217
|
+
Give a user or a Lamaste group exactly these roles
|
|
218
|
+
${c('entitlements <name> --revoke --user <u> | --group <g>')}
|
|
219
|
+
Take every role away
|
|
220
|
+
${c('identity-sync <name> [--reconcile]')}
|
|
221
|
+
Sync its audience to its agent half now; --reconcile replays all of it
|
|
194
222
|
`);
|
|
195
223
|
}
|
|
196
224
|
|
|
@@ -725,9 +753,11 @@ function printRelayHookSet(relay, title) {
|
|
|
725
753
|
` request: ${hook.stage} ${who} on ${hook.routes.join(', ')}${failOpen}` +
|
|
726
754
|
d(` (body ${hook.bodyMaxBytes ?? 0} B, ${hook.timeoutMs} ms, onError ${hook.onError})`),
|
|
727
755
|
);
|
|
728
|
-
if (hook.
|
|
756
|
+
if (hook.userSurfaceRoutes.length > 0) {
|
|
729
757
|
console.log(
|
|
730
|
-
d(
|
|
758
|
+
d(
|
|
759
|
+
` runs on user-surface requests (plugins that declare identity): ${hook.userSurfaceRoutes.join(', ')}`,
|
|
760
|
+
),
|
|
731
761
|
);
|
|
732
762
|
}
|
|
733
763
|
}
|
|
@@ -757,15 +787,25 @@ function printAgentHalf(agentHalf, title) {
|
|
|
757
787
|
);
|
|
758
788
|
}
|
|
759
789
|
|
|
790
|
+
/**
|
|
791
|
+
* Print an identity block — the roles a plugin's users hold and what it
|
|
792
|
+
* receives about each member.
|
|
793
|
+
*/
|
|
794
|
+
function printIdentityBlock(block, title) {
|
|
795
|
+
console.log(chalk.bold(` ${title}`));
|
|
796
|
+
for (const line of identityBlockLines(block)) console.log(` ${line}`);
|
|
797
|
+
}
|
|
798
|
+
|
|
760
799
|
/**
|
|
761
800
|
* Print what a version brings that needs approval — the approval digest
|
|
762
801
|
* (what `approve-hooks` takes; none for an update that brings neither
|
|
763
|
-
* relay hooks nor an agent half Lamaste installs
|
|
764
|
-
* the agent half Lamaste installs
|
|
802
|
+
* relay hooks, nor an agent half Lamaste installs, nor an identity block),
|
|
803
|
+
* its relay hook set, the agent half Lamaste installs and its identity
|
|
804
|
+
* block.
|
|
765
805
|
*/
|
|
766
|
-
function printApprovalContents({ digest, relay, agentHalf }) {
|
|
806
|
+
function printApprovalContents({ digest, relay, agentHalf, identity = null }) {
|
|
767
807
|
console.log(
|
|
768
|
-
` approval digest: ${digest ? chalk.cyan(digest) : chalk.dim('none — no relay hooks, no agent half Lamaste installs')}`,
|
|
808
|
+
` approval digest: ${digest ? chalk.cyan(digest) : chalk.dim('none — no relay hooks, no agent half Lamaste installs, no identity block')}`,
|
|
769
809
|
);
|
|
770
810
|
if (relay) {
|
|
771
811
|
console.log('');
|
|
@@ -775,12 +815,45 @@ function printApprovalContents({ digest, relay, agentHalf }) {
|
|
|
775
815
|
console.log('');
|
|
776
816
|
printAgentHalf(agentHalf, 'Agent half — Lamaste installs it on the hosting agent');
|
|
777
817
|
}
|
|
818
|
+
if (identity) {
|
|
819
|
+
console.log('');
|
|
820
|
+
printIdentityBlock(
|
|
821
|
+
identity,
|
|
822
|
+
'Identity — Lamaste sends its agent half the users entitled to these roles',
|
|
823
|
+
);
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
/**
|
|
828
|
+
* Print how an update changes the plugin's identity block (part of the
|
|
829
|
+
* digest): roles added or removed and attributes widened wait for an
|
|
830
|
+
* administrator whatever auto-approve says; the block it brings.
|
|
831
|
+
*/
|
|
832
|
+
function printIdentityChanges(diff) {
|
|
833
|
+
const shown = identityDiffLines(diff);
|
|
834
|
+
if (!shown) {
|
|
835
|
+
// Declared before and after, unchanged: still part of what is approved.
|
|
836
|
+
if (diff?.after) {
|
|
837
|
+
console.log('');
|
|
838
|
+
printIdentityBlock(diff.after, 'Identity — unchanged');
|
|
839
|
+
}
|
|
840
|
+
return;
|
|
841
|
+
}
|
|
842
|
+
console.log('');
|
|
843
|
+
console.log(chalk.bold(` Identity — this version ${shown.headline}`));
|
|
844
|
+
for (const line of shown.lines) {
|
|
845
|
+
console.log(` ${line.approval ? chalk.yellow(line.text) : line.text}`);
|
|
846
|
+
}
|
|
847
|
+
if (diff.after) {
|
|
848
|
+
for (const line of identityBlockLines(diff.after)) console.log(chalk.dim(` ${line}`));
|
|
849
|
+
}
|
|
778
850
|
}
|
|
779
851
|
|
|
780
852
|
/**
|
|
781
853
|
* Print an update that waits for approval under `title`: why it waits
|
|
782
|
-
* (`approvalRequired` — a secret turned plain in red
|
|
783
|
-
* (its digest, relay hook set and agent
|
|
854
|
+
* (`approvalRequired` — a secret turned plain in red, an identity change
|
|
855
|
+
* with its note), what it brings (its digest, relay hook set and agent
|
|
856
|
+
* half), how it changes the identity block, how it changes the settings
|
|
784
857
|
* schema (outside the digest) — fields added and removed with their types,
|
|
785
858
|
* retyped ones, newly required ones, and a warning for secrets that become
|
|
786
859
|
* plain values — what auto-approve could not cover beyond the reasons, and
|
|
@@ -791,10 +864,16 @@ function printPendingUpdate(name, update, title) {
|
|
|
791
864
|
console.log(chalk.bold(` ${title}`));
|
|
792
865
|
const { required, notCovered } = pendingUpdateReasons(update);
|
|
793
866
|
for (const line of required) {
|
|
794
|
-
|
|
867
|
+
const text = line.warning ? chalk.red(` - ${line.text}`) : ` - ${line.text}`;
|
|
868
|
+
console.log(line.note ? `${text}${chalk.dim(` (${line.note})`)}` : text);
|
|
795
869
|
}
|
|
796
870
|
console.log('');
|
|
797
|
-
printApprovalContents(
|
|
871
|
+
printApprovalContents({
|
|
872
|
+
digest: update.digest,
|
|
873
|
+
relay: update.relay,
|
|
874
|
+
agentHalf: update.agentHalf,
|
|
875
|
+
});
|
|
876
|
+
printIdentityChanges(update.identity);
|
|
798
877
|
const lines = settingsDiffLines(update.settings);
|
|
799
878
|
if (lines.length > 0) {
|
|
800
879
|
console.log('');
|
|
@@ -837,7 +916,7 @@ async function hooksCmd(name, { json }) {
|
|
|
837
916
|
console.log('');
|
|
838
917
|
if (!approval.required && !hooks.agent && !hooks.pendingUpdate) {
|
|
839
918
|
console.log(
|
|
840
|
-
` Plugin ${chalk.cyan(name)} declares no lifecycle hooks
|
|
919
|
+
` Plugin ${chalk.cyan(name)} declares no lifecycle hooks, no agent half for Lamaste to install and no identity.\n`,
|
|
841
920
|
);
|
|
842
921
|
return;
|
|
843
922
|
}
|
|
@@ -853,6 +932,7 @@ async function hooksCmd(name, { json }) {
|
|
|
853
932
|
digest: approval.digest,
|
|
854
933
|
relay: hooks.relay,
|
|
855
934
|
agentHalf: hooks.agentHalf,
|
|
935
|
+
identity: hooks.identity?.declared ? hooks.identity.block : null,
|
|
856
936
|
});
|
|
857
937
|
console.log(
|
|
858
938
|
`\n auto-approve for later versions: ${hooks.autoApprove ? chalk.yellow('on') : 'off'}`,
|
|
@@ -860,6 +940,13 @@ async function hooksCmd(name, { json }) {
|
|
|
860
940
|
if (approval.state !== 'approved') {
|
|
861
941
|
console.log(`\n Approve with: ${approveCommand(name, { digest: approval.digest })}`);
|
|
862
942
|
}
|
|
943
|
+
if (hooks.identity?.declared) {
|
|
944
|
+
console.log(
|
|
945
|
+
chalk.dim(
|
|
946
|
+
` Its people (entitlements, audience, feed): lamaste-server plugins identity ${terminalText(name)}`,
|
|
947
|
+
),
|
|
948
|
+
);
|
|
949
|
+
}
|
|
863
950
|
}
|
|
864
951
|
if (hooks.agent) {
|
|
865
952
|
console.log(chalk.bold(`\n Agent hooks (run by the agent half; no approval)`));
|
|
@@ -1174,6 +1261,7 @@ async function installPluginCmd(packageName, { json }) {
|
|
|
1174
1261
|
digest: approval.digest,
|
|
1175
1262
|
relay: result.hooks.relay,
|
|
1176
1263
|
agentHalf: result.hooks.agentHalf,
|
|
1264
|
+
identity: result.hooks.identity?.declared ? result.hooks.identity.block : null,
|
|
1177
1265
|
});
|
|
1178
1266
|
if (approval.state === 'pending') {
|
|
1179
1267
|
console.log(
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
* The panel authenticates as its own `lamaste-server` OIDC client on
|
|
9
9
|
* the relay's Authelia (client-credentials grant, Feria login). Nothing
|
|
10
10
|
* to paste; Feria's auth.json must grant the relay's lamaste-server client
|
|
11
|
-
* through a "workloads" rule on its groups issuer for the
|
|
11
|
+
* read and admin through a "workloads" rule on its groups issuer for the
|
|
12
|
+
* relay Authelia.
|
|
12
13
|
* registry-credential set --url <registry> [--token-stdin]
|
|
13
14
|
* A long-lived Feria token, read from LAMASTE_REGISTRY_TOKEN or from
|
|
14
15
|
* stdin with --token-stdin. Never accepted as a command-line argument.
|
|
@@ -9,16 +9,28 @@
|
|
|
9
9
|
* assign <id> --agent <label> Move a tunnel to another agent
|
|
10
10
|
* configure <id> [--access-mode <m>] [--max-body-mb <n>]
|
|
11
11
|
* Change access mode / body limit
|
|
12
|
+
* bind-user-surface <tunnel> <plugin> Serve an agent-hosted plugin's user
|
|
13
|
+
* surface (/api/<route>/app/*) on the
|
|
14
|
+
* tunnel's host
|
|
15
|
+
* unbind-user-surface <tunnel> <plugin> Stop serving it there
|
|
12
16
|
*
|
|
13
17
|
* List reads directly from the state file.
|
|
14
|
-
* Create/delete/toggle use the panel REST API
|
|
15
|
-
* nginx/certbot/chisel dependency stack)
|
|
18
|
+
* Create/delete/toggle and the user-surface binding use the panel REST API
|
|
19
|
+
* (which has the full nginx/certbot/chisel dependency stack); a tunnel is
|
|
20
|
+
* named by its id, subdomain or FQDN there, a plugin by its name or route.
|
|
16
21
|
*/
|
|
17
22
|
|
|
18
23
|
import chalk from 'chalk';
|
|
19
24
|
import { readTunnels } from '../state.js';
|
|
20
25
|
import { panelRequest } from '../panel-api.js';
|
|
21
26
|
import { emit, emitError, emitComplete } from '../ndjson.js';
|
|
27
|
+
import {
|
|
28
|
+
findHostTunnel,
|
|
29
|
+
findPluginByNameOrRoute,
|
|
30
|
+
parseUserSurfaceArgs,
|
|
31
|
+
userSurfaceStatusText,
|
|
32
|
+
} from '../plugin-identity.js';
|
|
33
|
+
import { terminalText } from '../plugin-settings.js';
|
|
22
34
|
|
|
23
35
|
/**
|
|
24
36
|
* @param {string[]} args
|
|
@@ -40,6 +52,9 @@ export async function runTunnels(args, { json }) {
|
|
|
40
52
|
return assignTunnel(args.slice(1), { json });
|
|
41
53
|
case 'configure':
|
|
42
54
|
return configureTunnel(args.slice(1), { json });
|
|
55
|
+
case 'bind-user-surface':
|
|
56
|
+
case 'unbind-user-surface':
|
|
57
|
+
return userSurfaceCmd(sub, args.slice(1), { json });
|
|
43
58
|
default:
|
|
44
59
|
printTunnelUsage();
|
|
45
60
|
process.exit(sub ? 1 : 0);
|
|
@@ -59,6 +74,13 @@ ${b('Subcommands:')}
|
|
|
59
74
|
${c('toggle')} <id> --enable|--disable Enable or disable a tunnel
|
|
60
75
|
${c('assign')} <id> --agent <label> Move a tunnel to another agent
|
|
61
76
|
${c('configure')} <id> [--access-mode <m>] [--max-body-mb <n>] Change access mode / body limit
|
|
77
|
+
${c('bind-user-surface')} <tunnel> <plugin> Serve an agent-hosted plugin's user surface
|
|
78
|
+
(/api/<route>/app/*) on the tunnel's host
|
|
79
|
+
${c('unbind-user-surface')} <tunnel> <plugin> Stop serving it there
|
|
80
|
+
|
|
81
|
+
A tunnel is named by its id, subdomain or FQDN; a plugin by its name or route. A user
|
|
82
|
+
surface needs an enabled tunnel that is authenticated or restricted, carried by the
|
|
83
|
+
agent that hosts the plugin — it is served only while that agent carries the tunnel.
|
|
62
84
|
|
|
63
85
|
${b('Create options:')}
|
|
64
86
|
--subdomain <name> Tunnel subdomain (required)
|
|
@@ -108,6 +130,10 @@ async function listTunnels({ json }) {
|
|
|
108
130
|
if (t.description) {
|
|
109
131
|
console.log(` ${d(t.description)}`);
|
|
110
132
|
}
|
|
133
|
+
const userSurface = Array.isArray(t.userSurface?.routes) ? t.userSurface.routes : [];
|
|
134
|
+
if (userSurface.length > 0) {
|
|
135
|
+
console.log(` ${d('user surface of:')} ${terminalText(userSurface.join(', '))}`);
|
|
136
|
+
}
|
|
111
137
|
console.log(` ${d(`id: ${t.id} fqdn: ${t.fqdn}`)}`);
|
|
112
138
|
}
|
|
113
139
|
console.log('');
|
|
@@ -363,3 +389,77 @@ function getArg(args, name) {
|
|
|
363
389
|
if (idx === -1 || idx + 1 >= args.length) return null;
|
|
364
390
|
return args[idx + 1];
|
|
365
391
|
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* `bind-user-surface|unbind-user-surface <tunnel> <plugin>`: the tunnel's
|
|
395
|
+
* host serves (or stops serving) the plugin's `/api/<route>/app/*` — from
|
|
396
|
+
* when its installed, approved version declares identity; until then its
|
|
397
|
+
* requests go down the tunnel. Prints how each route bound to the tunnel is
|
|
398
|
+
* served afterwards.
|
|
399
|
+
*
|
|
400
|
+
* @param {'bind-user-surface' | 'unbind-user-surface'} sub
|
|
401
|
+
* @param {string[]} args
|
|
402
|
+
* @param {{ json: boolean }} options
|
|
403
|
+
*/
|
|
404
|
+
async function userSurfaceCmd(sub, args, { json }) {
|
|
405
|
+
const failWith = (message, code) => {
|
|
406
|
+
if (json) emitError(message, false, code ? { code } : {});
|
|
407
|
+
else console.error(`\n ${chalk.red(terminalText(message))}\n`);
|
|
408
|
+
process.exit(1);
|
|
409
|
+
};
|
|
410
|
+
const parsed = parseUserSurfaceArgs(sub, args);
|
|
411
|
+
if (!parsed.ok) failWith(parsed.error);
|
|
412
|
+
|
|
413
|
+
const found = findHostTunnel(await readTunnels(), parsed.tunnel);
|
|
414
|
+
if (found.error) failWith(found.error);
|
|
415
|
+
const { tunnel } = found;
|
|
416
|
+
|
|
417
|
+
let plugins;
|
|
418
|
+
try {
|
|
419
|
+
({ plugins = [] } = await panelRequest('GET', '/api/plugins'));
|
|
420
|
+
} catch (err) {
|
|
421
|
+
failWith(err.message, err.code);
|
|
422
|
+
}
|
|
423
|
+
const named = findPluginByNameOrRoute(plugins, parsed.plugin);
|
|
424
|
+
if (named.error) failWith(named.error);
|
|
425
|
+
const { route } = named.plugin;
|
|
426
|
+
|
|
427
|
+
const bind = sub === 'bind-user-surface';
|
|
428
|
+
const surfacePath = `/api/tunnels/${encodeURIComponent(tunnel.id)}/user-surface`;
|
|
429
|
+
let result;
|
|
430
|
+
try {
|
|
431
|
+
result = bind
|
|
432
|
+
? await panelRequest('PUT', surfacePath, { route })
|
|
433
|
+
: await panelRequest('DELETE', `${surfacePath}/${encodeURIComponent(route)}`);
|
|
434
|
+
} catch (err) {
|
|
435
|
+
failWith(err.message, err.code);
|
|
436
|
+
}
|
|
437
|
+
if (json) {
|
|
438
|
+
emitComplete({ id: tunnel.id, route, ...result });
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
const host = terminalText(result.tunnel?.fqdn ?? tunnel.fqdn);
|
|
442
|
+
if (bind) {
|
|
443
|
+
console.log(
|
|
444
|
+
result.changed
|
|
445
|
+
? `\n ${chalk.green('✓')} ${chalk.cyan(host)} serves the user surface of ${chalk.cyan(route)}`
|
|
446
|
+
: `\n ${chalk.cyan(host)} already serves the user surface of ${chalk.cyan(route)}`,
|
|
447
|
+
);
|
|
448
|
+
} else {
|
|
449
|
+
console.log(
|
|
450
|
+
`\n ${chalk.green('✓')} ${chalk.cyan(host)} no longer serves the user surface of ${chalk.cyan(route)} — its requests go down the tunnel`,
|
|
451
|
+
);
|
|
452
|
+
}
|
|
453
|
+
const statuses = Array.isArray(result.tunnel?.userSurfaceStatus)
|
|
454
|
+
? result.tunnel.userSurfaceStatus
|
|
455
|
+
: [];
|
|
456
|
+
for (const status of statuses) {
|
|
457
|
+
const served = status.served === true;
|
|
458
|
+
const word = userSurfaceStatusText(status);
|
|
459
|
+
console.log(
|
|
460
|
+
` ${terminalText(status.route)}: ${served ? chalk.green(word) : chalk.yellow(word)}` +
|
|
461
|
+
(served ? chalk.dim(` https://${host}/api/${terminalText(status.route)}/app/`) : ''),
|
|
462
|
+
);
|
|
463
|
+
}
|
|
464
|
+
console.log('');
|
|
465
|
+
}
|
|
@@ -190,11 +190,13 @@ async function removeServices() {
|
|
|
190
190
|
* @param {Record<string, unknown> | null} _config
|
|
191
191
|
*/
|
|
192
192
|
async function removeNginxConfigs(_config) {
|
|
193
|
-
// Remove lamaste-specific sites
|
|
193
|
+
// Remove lamaste-specific sites, then the snippets and http-level maps
|
|
194
|
+
// they use (the sites go first: a site naming a removed map fails nginx)
|
|
194
195
|
const patterns = [
|
|
195
196
|
'/etc/nginx/sites-enabled/lamalibre-lamaste-*',
|
|
196
197
|
'/etc/nginx/sites-available/lamalibre-lamaste-*',
|
|
197
198
|
'/etc/nginx/snippets/lamalibre-lamaste-*',
|
|
199
|
+
'/etc/nginx/conf.d/lamalibre-lamaste-*',
|
|
198
200
|
];
|
|
199
201
|
|
|
200
202
|
for (const pattern of patterns) {
|