@camstack/core 0.1.37 → 0.1.38
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/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.d.ts +7 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.d.ts.map +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +46 -56
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js.map +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +47 -57
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs.map +1 -1
- package/dist/index.js +10 -138
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +10 -138
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -37
- package/dist/builtins/auth-orchestrator/auth-orchestrator.addon.d.ts +0 -8
- package/dist/builtins/auth-orchestrator/auth-orchestrator.addon.d.ts.map +0 -1
- package/dist/builtins/auth-orchestrator/auth-orchestrator.addon.js +0 -75
- package/dist/builtins/auth-orchestrator/auth-orchestrator.addon.js.map +0 -1
- package/dist/builtins/auth-orchestrator/auth-orchestrator.addon.mjs +0 -69
- package/dist/builtins/auth-orchestrator/auth-orchestrator.addon.mjs.map +0 -1
- package/dist/builtins/auth-orchestrator/index.d.ts +0 -2
- package/dist/builtins/auth-orchestrator/index.d.ts.map +0 -1
- package/dist/builtins/auth-orchestrator/index.js +0 -7
- package/dist/builtins/auth-orchestrator/index.mjs +0 -2
- package/dist/builtins/mesh-orchestrator/index.d.ts +0 -2
- package/dist/builtins/mesh-orchestrator/index.d.ts.map +0 -1
- package/dist/builtins/mesh-orchestrator/index.js +0 -7
- package/dist/builtins/mesh-orchestrator/index.mjs +0 -2
- package/dist/builtins/mesh-orchestrator/mesh-orchestrator.addon.d.ts +0 -9
- package/dist/builtins/mesh-orchestrator/mesh-orchestrator.addon.d.ts.map +0 -1
- package/dist/builtins/mesh-orchestrator/mesh-orchestrator.addon.js +0 -113
- package/dist/builtins/mesh-orchestrator/mesh-orchestrator.addon.js.map +0 -1
- package/dist/builtins/mesh-orchestrator/mesh-orchestrator.addon.mjs +0 -107
- package/dist/builtins/mesh-orchestrator/mesh-orchestrator.addon.mjs.map +0 -1
- package/dist/builtins/turn-orchestrator/index.d.ts +0 -2
- package/dist/builtins/turn-orchestrator/index.d.ts.map +0 -1
- package/dist/builtins/turn-orchestrator/index.js +0 -7
- package/dist/builtins/turn-orchestrator/index.mjs +0 -2
- package/dist/builtins/turn-orchestrator/turn-orchestrator.addon.d.ts +0 -34
- package/dist/builtins/turn-orchestrator/turn-orchestrator.addon.d.ts.map +0 -1
- package/dist/builtins/turn-orchestrator/turn-orchestrator.addon.js +0 -126
- package/dist/builtins/turn-orchestrator/turn-orchestrator.addon.js.map +0 -1
- package/dist/builtins/turn-orchestrator/turn-orchestrator.addon.mjs +0 -120
- package/dist/builtins/turn-orchestrator/turn-orchestrator.addon.mjs.map +0 -1
|
@@ -9,10 +9,16 @@ interface RemoteAccessOrchestratorConfig {
|
|
|
9
9
|
export declare class RemoteAccessOrchestratorAddon extends BaseAddon<RemoteAccessOrchestratorConfig> {
|
|
10
10
|
constructor();
|
|
11
11
|
protected onInitialize(): Promise<ProviderRegistration[]>;
|
|
12
|
+
/**
|
|
13
|
+
* Maintain the persisted `enabledProviders` set from a tunnel
|
|
14
|
+
* lifecycle event. `source.id` is `string | number`; `network-access`
|
|
15
|
+
* providers emit with `type: 'addon'` so it is always the addonId
|
|
16
|
+
* string. Non-string / non-addon sources are ignored defensively.
|
|
17
|
+
*/
|
|
18
|
+
private onTunnelLifecycle;
|
|
12
19
|
private autoStartEnabledProviders;
|
|
13
20
|
private markEnabled;
|
|
14
21
|
private resolveImpl;
|
|
15
|
-
private listProviders;
|
|
16
22
|
}
|
|
17
23
|
export default RemoteAccessOrchestratorAddon;
|
|
18
24
|
//# sourceMappingURL=remote-access-orchestrator.addon.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"remote-access-orchestrator.addon.d.ts","sourceRoot":"","sources":["../../../src/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"remote-access-orchestrator.addon.d.ts","sourceRoot":"","sources":["../../../src/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,EACL,SAAS,EAET,KAAK,oBAAoB,EAC1B,MAAM,iBAAiB,CAAA;AAWxB,UAAU,8BAA8B;IACtC;;;OAGG;IACH,QAAQ,CAAC,gBAAgB,EAAE,SAAS,MAAM,EAAE,CAAA;CAC7C;AAED,qBAAa,6BAA8B,SAAQ,SAAS,CAAC,8BAA8B,CAAC;;cAK1E,YAAY,IAAI,OAAO,CAAC,oBAAoB,EAAE,CAAC;IAsD/D;;;;;OAKG;YACW,iBAAiB;YAajB,yBAAyB;YA8CzB,WAAW;IAWzB,OAAO,CAAC,WAAW;CAOpB;AAED,eAAe,6BAA6B,CAAA"}
|
|
@@ -6,40 +6,39 @@ require("../../chunk-C13QxCFV.js");
|
|
|
6
6
|
let _camstack_types = require("@camstack/types");
|
|
7
7
|
//#region src/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.ts
|
|
8
8
|
/**
|
|
9
|
-
* Remote-access orchestrator —
|
|
10
|
-
* `network-access` collection (Cloudflare Tunnel, ngrok, Tailscale, …).
|
|
11
|
-
* Mirrors the auth-orchestrator and backup-orchestrator patterns.
|
|
9
|
+
* Remote-access orchestrator — backend-only boot-autostart service for
|
|
10
|
+
* the `network-access` collection (Cloudflare Tunnel, ngrok, Tailscale, …).
|
|
12
11
|
*
|
|
13
|
-
*
|
|
12
|
+
* Retired its `remote-access` facade cap (2026-05-15): the admin UI now
|
|
13
|
+
* talks to the `network-access` collection cap directly via generic
|
|
14
|
+
* per-`addonId` routing, so this addon registers NO capability.
|
|
15
|
+
*
|
|
16
|
+
* What it still owns — the load-bearing logic:
|
|
14
17
|
* The orchestrator owns the "operator wants this provider running"
|
|
15
|
-
* intent —
|
|
18
|
+
* intent — an `enabledProviders: string[]` slice in its addon-store
|
|
16
19
|
* blob (BaseAddon.config). On boot we iterate the list and call
|
|
17
|
-
* `provider.start()` for each enabled entry
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
+
* `provider.start()` for each enabled entry, so a tunnel set up once
|
|
21
|
+
* stays up across hub restarts.
|
|
22
|
+
*
|
|
23
|
+
* Since start/stop no longer flow through this addon, the enabled-set
|
|
24
|
+
* is maintained purely from the event bus: every `network-access`
|
|
25
|
+
* provider emits `NetworkTunnelStarted` / `NetworkTunnelStopped` when
|
|
26
|
+
* it starts/stops. We add the emitting addonId on Started, remove it
|
|
27
|
+
* on Stopped, and persist — keeping the operator intent in sync no
|
|
28
|
+
* matter who triggered the lifecycle change.
|
|
20
29
|
*/
|
|
21
30
|
var RemoteAccessOrchestratorAddon = class extends _camstack_types.BaseAddon {
|
|
22
31
|
constructor() {
|
|
23
32
|
super({ enabledProviders: [] });
|
|
24
33
|
}
|
|
25
34
|
async onInitialize() {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
return endpoint;
|
|
34
|
-
},
|
|
35
|
-
stopProvider: async ({ addonId }) => {
|
|
36
|
-
const impl = this.resolveImpl(addonId);
|
|
37
|
-
if (impl?.stop) await impl.stop();
|
|
38
|
-
await this.markEnabled(addonId, false);
|
|
39
|
-
return { success: true };
|
|
40
|
-
}
|
|
41
|
-
};
|
|
42
|
-
this.ctx.logger.info("Remote-access orchestrator initialized", { meta: { enabledCount: this.config.enabledProviders.length } });
|
|
35
|
+
this.ctx.logger.info("Remote-access orchestrator initialized (backend-only)", { meta: { enabledCount: this.config.enabledProviders.length } });
|
|
36
|
+
this.ctx.eventBus?.subscribe({ category: _camstack_types.EventCategory.NetworkTunnelStarted }, (event) => {
|
|
37
|
+
this.onTunnelLifecycle(event.source, true);
|
|
38
|
+
});
|
|
39
|
+
this.ctx.eventBus?.subscribe({ category: _camstack_types.EventCategory.NetworkTunnelStopped }, (event) => {
|
|
40
|
+
this.onTunnelLifecycle(event.source, false);
|
|
41
|
+
});
|
|
43
42
|
setImmediate(() => {
|
|
44
43
|
this.autoStartEnabledProviders();
|
|
45
44
|
});
|
|
@@ -49,10 +48,24 @@ var RemoteAccessOrchestratorAddon = class extends _camstack_types.BaseAddon {
|
|
|
49
48
|
this.watchCapability("mesh-network", { onReady: () => {
|
|
50
49
|
this.autoStartEnabledProviders();
|
|
51
50
|
} });
|
|
52
|
-
return [
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
51
|
+
return [];
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Maintain the persisted `enabledProviders` set from a tunnel
|
|
55
|
+
* lifecycle event. `source.id` is `string | number`; `network-access`
|
|
56
|
+
* providers emit with `type: 'addon'` so it is always the addonId
|
|
57
|
+
* string. Non-string / non-addon sources are ignored defensively.
|
|
58
|
+
*/
|
|
59
|
+
async onTunnelLifecycle(source, started) {
|
|
60
|
+
if (source.type !== "addon" || typeof source.id !== "string") {
|
|
61
|
+
this.ctx.logger.warn("tunnel lifecycle event with non-addon source — ignoring", { meta: {
|
|
62
|
+
sourceType: source.type,
|
|
63
|
+
sourceId: source.id,
|
|
64
|
+
started
|
|
65
|
+
} });
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
await this.markEnabled(source.id, started);
|
|
56
69
|
}
|
|
57
70
|
async autoStartEnabledProviders() {
|
|
58
71
|
const ids = this.config.enabledProviders;
|
|
@@ -96,38 +109,15 @@ var RemoteAccessOrchestratorAddon = class extends _camstack_types.BaseAddon {
|
|
|
96
109
|
if (enabled) current.add(addonId);
|
|
97
110
|
else current.delete(addonId);
|
|
98
111
|
if (wasEnabled === enabled) return;
|
|
112
|
+
this.ctx.logger.info("remote-access intent updated", { meta: {
|
|
113
|
+
addonId,
|
|
114
|
+
enabled
|
|
115
|
+
} });
|
|
99
116
|
await this.updateGlobalSettings({ enabledProviders: [...current] });
|
|
100
117
|
}
|
|
101
118
|
resolveImpl(addonId) {
|
|
102
119
|
return (this.capabilities?.getCollectionEntries("network-access") ?? []).find(([id]) => id === addonId)?.[1] ?? null;
|
|
103
120
|
}
|
|
104
|
-
async listProviders() {
|
|
105
|
-
const entries = this.capabilities?.getCollectionEntries("network-access") ?? [];
|
|
106
|
-
const enabled = new Set(this.config.enabledProviders);
|
|
107
|
-
const out = [];
|
|
108
|
-
for (const [addonId, impl] of entries) {
|
|
109
|
-
let connected = false;
|
|
110
|
-
let endpoint = null;
|
|
111
|
-
let error;
|
|
112
|
-
if (impl.getStatus) try {
|
|
113
|
-
const s = await impl.getStatus();
|
|
114
|
-
connected = s.connected;
|
|
115
|
-
endpoint = s.endpoint;
|
|
116
|
-
error = s.error;
|
|
117
|
-
} catch (err) {
|
|
118
|
-
error = err instanceof Error ? err.message : String(err);
|
|
119
|
-
}
|
|
120
|
-
out.push({
|
|
121
|
-
addonId,
|
|
122
|
-
displayName: impl.displayName ?? addonId,
|
|
123
|
-
enabled: enabled.has(addonId),
|
|
124
|
-
connected,
|
|
125
|
-
endpoint,
|
|
126
|
-
...error !== void 0 ? { error } : {}
|
|
127
|
-
});
|
|
128
|
-
}
|
|
129
|
-
return out;
|
|
130
|
-
}
|
|
131
121
|
};
|
|
132
122
|
//#endregion
|
|
133
123
|
exports.RemoteAccessOrchestratorAddon = RemoteAccessOrchestratorAddon;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"remote-access-orchestrator.addon.js","names":[],"sources":["../../../src/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.ts"],"sourcesContent":["/**\n * Remote-access orchestrator — singleton facade over the\n * `network-access` collection (Cloudflare Tunnel, ngrok, Tailscale, …).\n * Mirrors the auth-orchestrator and backup-orchestrator patterns.\n *\n * Persistence + autostart contract:\n * The orchestrator owns the \"operator wants this provider running\"\n * intent — a `enabledProviders: string[]` slice in its addon-store\n * blob (BaseAddon.config). On boot we iterate the list and call\n * `provider.start()` for each enabled entry. `startProvider` /\n * `stopProvider` mutate this list so a Start press persists across\n * restarts. Same shape as turn-orchestrator's setProviderEnabled.\n */\nimport {\n BaseAddon,\n remoteAccessCapability,\n type IRemoteAccessOrchestrator,\n type RemoteAccessProviderInfo,\n type ProviderRegistration,\n} from '@camstack/types'\n\ninterface NetworkAccessLike {\n start?: () => Promise<{ url: string; hostname: string; port: number; protocol: 'http' | 'https' }>\n stop?: () => Promise<void>\n getStatus?: () => Promise<{\n connected: boolean\n endpoint: { url: string; hostname: string; port: number; protocol: 'http' | 'https' } | null\n error?: string\n }>\n}\n\ninterface NetworkAccessRegistrationMeta {\n readonly displayName?: string\n}\n\ninterface RemoteAccessOrchestratorConfig {\n /**\n * addonIds the operator has explicitly Started. Auto-respawned on\n * boot so a tunnel set up once stays up across hub restarts.\n */\n readonly enabledProviders: readonly string[]\n}\n\nexport class RemoteAccessOrchestratorAddon extends BaseAddon<RemoteAccessOrchestratorConfig> {\n constructor() {\n super({ enabledProviders: [] })\n }\n\n protected async onInitialize(): Promise<ProviderRegistration[]> {\n const provider: IRemoteAccessOrchestrator = {\n listProviders: async () => this.listProviders(),\n startProvider: async ({ addonId }) => {\n const impl = this.resolveImpl(addonId)\n if (!impl?.start) throw new Error(`Remote-access provider \"${addonId}\" does not support start`)\n const endpoint = await impl.start()\n // Persist intent — next boot will auto-respawn this provider.\n await this.markEnabled(addonId, true)\n return endpoint\n },\n stopProvider: async ({ addonId }) => {\n const impl = this.resolveImpl(addonId)\n if (impl?.stop) await impl.stop()\n // Clear intent — boot must NOT respawn this on next start.\n await this.markEnabled(addonId, false)\n return { success: true as const }\n },\n } satisfies IRemoteAccessOrchestrator\n this.ctx.logger.info('Remote-access orchestrator initialized', {\n meta: { enabledCount: this.config.enabledProviders.length },\n })\n // Defer autostart to next tick so the orchestrator's own provider\n // registration completes first. `resolveImpl` reads from the\n // capabilities registry which only sees in-process / cluster-mirrored\n // providers once they've ALSO registered — small delay gives the\n // cluster bridge time to discover them on cold boot. Errors are\n // logged but never block init.\n setImmediate(() => { this.autoStartEnabledProviders() })\n\n // Lazy retry — forked providers (cloudflare-tunnel etc) typically\n // register 15-20 s after this orchestrator boots, well past the\n // `setImmediate` above. Hook BaseAddon's `system.ready-state`\n // subscription so we re-run autoStart every time the\n // `network-access` cap transitions to ready (whichever node holds\n // it). The inner logic is idempotent + skips already-connected\n // providers.\n this.watchCapability('network-access', {\n onReady: () => { void this.autoStartEnabledProviders() },\n })\n\n // Same watch for `mesh-network`. The tailscale-ingress provider\n // registers `network-access` synchronously at boot, but `start()`\n // throws when the tailnet isn't joined yet — so the boot-time\n // autoStart call fails for tailscale ingresses if the tailscale\n // daemon hadn't logged in by then. Watching `mesh-network` here\n // re-triggers autoStart the moment the client transitions to\n // joined (manual operator login or auto-rejoin), without needing\n // a server restart. Same idempotency rules: providers already\n // connected are skipped.\n this.watchCapability('mesh-network', {\n onReady: () => { void this.autoStartEnabledProviders() },\n })\n\n return [{ capability: remoteAccessCapability, provider }]\n }\n\n private async autoStartEnabledProviders(): Promise<void> {\n const ids = this.config.enabledProviders\n if (ids.length === 0) return\n this.ctx.logger.info('Auto-starting enabled remote-access providers', {\n meta: { addonIds: [...ids] },\n })\n for (const addonId of ids) {\n try {\n const impl = this.resolveImpl(addonId)\n if (!impl?.start) {\n // Provider isn't loaded yet (worker bridge still hydrating)\n // OR it doesn't implement start. Log at debug level — the\n // provider-registered subscription below will retry as soon\n // as the addon appears.\n this.ctx.logger.warn('autostart: provider not ready or unsupported', {\n meta: { addonId, hasImpl: !!impl, hasStart: !!impl?.start },\n })\n continue\n }\n // Idempotent: skip when the provider is already connected.\n // Avoids spamming start() on every provider-registered event\n // and prevents respawning a child process that's already alive.\n if (impl.getStatus) {\n const status = await impl.getStatus().catch(() => null)\n if (status?.connected) {\n this.ctx.logger.info('autostart: provider already connected — skipping', {\n meta: { addonId, url: status.endpoint?.url },\n })\n continue\n }\n }\n const endpoint = await impl.start()\n this.ctx.logger.info('autostart: provider started', {\n meta: { addonId, url: endpoint.url },\n })\n } catch (err) {\n this.ctx.logger.error('autostart: provider start failed', {\n meta: {\n addonId,\n error: err instanceof Error ? err.message : String(err),\n },\n })\n }\n }\n }\n\n private async markEnabled(addonId: string, enabled: boolean): Promise<void> {\n const current = new Set(this.config.enabledProviders)\n const wasEnabled = current.has(addonId)\n if (enabled) current.add(addonId); else current.delete(addonId)\n if (wasEnabled === enabled) return\n await this.updateGlobalSettings({ enabledProviders: [...current] })\n }\n\n private resolveImpl(addonId: string): (NetworkAccessLike & NetworkAccessRegistrationMeta) | null {\n const entries = this.capabilities?.getCollectionEntries<NetworkAccessLike & NetworkAccessRegistrationMeta>(\n 'network-access',\n ) ?? []\n const found = entries.find(([id]) => id === addonId)\n return found?.[1] ?? null\n }\n\n private async listProviders(): Promise<readonly RemoteAccessProviderInfo[]> {\n const entries = this.capabilities?.getCollectionEntries<NetworkAccessLike & NetworkAccessRegistrationMeta>(\n 'network-access',\n ) ?? []\n const enabled = new Set(this.config.enabledProviders)\n const out: RemoteAccessProviderInfo[] = []\n for (const [addonId, impl] of entries) {\n let connected = false\n let endpoint: RemoteAccessProviderInfo['endpoint'] = null\n let error: string | undefined\n if (impl.getStatus) {\n try {\n const s = await impl.getStatus()\n connected = s.connected\n endpoint = s.endpoint\n error = s.error\n } catch (err) {\n error = err instanceof Error ? err.message : String(err)\n }\n }\n out.push({\n addonId,\n displayName: impl.displayName ?? addonId,\n // `enabled` is now the operator's persisted intent — orthogonal\n // to `connected` (which reflects the live tunnel state). Boot\n // tries to bring enabled→connected automatically.\n enabled: enabled.has(addonId),\n connected,\n endpoint,\n ...(error !== undefined ? { error } : {}),\n })\n }\n return out\n }\n}\n\nexport default RemoteAccessOrchestratorAddon\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"remote-access-orchestrator.addon.js","names":[],"sources":["../../../src/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.ts"],"sourcesContent":["/**\n * Remote-access orchestrator — backend-only boot-autostart service for\n * the `network-access` collection (Cloudflare Tunnel, ngrok, Tailscale, …).\n *\n * Retired its `remote-access` facade cap (2026-05-15): the admin UI now\n * talks to the `network-access` collection cap directly via generic\n * per-`addonId` routing, so this addon registers NO capability.\n *\n * What it still owns — the load-bearing logic:\n * The orchestrator owns the \"operator wants this provider running\"\n * intent — an `enabledProviders: string[]` slice in its addon-store\n * blob (BaseAddon.config). On boot we iterate the list and call\n * `provider.start()` for each enabled entry, so a tunnel set up once\n * stays up across hub restarts.\n *\n * Since start/stop no longer flow through this addon, the enabled-set\n * is maintained purely from the event bus: every `network-access`\n * provider emits `NetworkTunnelStarted` / `NetworkTunnelStopped` when\n * it starts/stops. We add the emitting addonId on Started, remove it\n * on Stopped, and persist — keeping the operator intent in sync no\n * matter who triggered the lifecycle change.\n */\nimport {\n BaseAddon,\n EventCategory,\n type ProviderRegistration,\n} from '@camstack/types'\n\ninterface NetworkAccessLike {\n start?: () => Promise<{ url: string; hostname: string; port: number; protocol: 'http' | 'https' }>\n getStatus?: () => Promise<{\n connected: boolean\n endpoint: { url: string; hostname: string; port: number; protocol: 'http' | 'https' } | null\n error?: string\n }>\n}\n\ninterface RemoteAccessOrchestratorConfig {\n /**\n * addonIds the operator has explicitly Started. Auto-respawned on\n * boot so a tunnel set up once stays up across hub restarts.\n */\n readonly enabledProviders: readonly string[]\n}\n\nexport class RemoteAccessOrchestratorAddon extends BaseAddon<RemoteAccessOrchestratorConfig> {\n constructor() {\n super({ enabledProviders: [] })\n }\n\n protected async onInitialize(): Promise<ProviderRegistration[]> {\n this.ctx.logger.info('Remote-access orchestrator initialized (backend-only)', {\n meta: { enabledCount: this.config.enabledProviders.length },\n })\n\n // Track operator intent from the lifecycle events every\n // `network-access` provider emits. The emitting addonId is carried\n // on `event.source.id` (events are emitted with\n // `source: { type: 'addon', id: ctx.id }`).\n this.ctx.eventBus?.subscribe(\n { category: EventCategory.NetworkTunnelStarted },\n (event) => { void this.onTunnelLifecycle(event.source, true) },\n )\n this.ctx.eventBus?.subscribe(\n { category: EventCategory.NetworkTunnelStopped },\n (event) => { void this.onTunnelLifecycle(event.source, false) },\n )\n\n // Defer autostart to next tick so provider registrations from\n // co-located addons settle first. `resolveImpl` reads from the\n // capabilities registry which only sees in-process / cluster-mirrored\n // providers once they've ALSO registered — small delay gives the\n // cluster bridge time to discover them on cold boot. Errors are\n // logged but never block init.\n setImmediate(() => { void this.autoStartEnabledProviders() })\n\n // Lazy retry — forked providers (cloudflare-tunnel etc) typically\n // register 15-20 s after this orchestrator boots, well past the\n // `setImmediate` above. Hook BaseAddon's `system.ready-state`\n // subscription so we re-run autoStart every time the\n // `network-access` cap transitions to ready (whichever node holds\n // it). The inner logic is idempotent + skips already-connected\n // providers.\n this.watchCapability('network-access', {\n onReady: () => { void this.autoStartEnabledProviders() },\n })\n\n // Same watch for `mesh-network`. The tailscale-ingress provider\n // registers `network-access` synchronously at boot, but `start()`\n // throws when the tailnet isn't joined yet — so the boot-time\n // autoStart call fails for tailscale ingresses if the tailscale\n // daemon hadn't logged in by then. Watching `mesh-network` here\n // re-triggers autoStart the moment the client transitions to\n // joined (manual operator login or auto-rejoin), without needing\n // a server restart. Same idempotency rules: providers already\n // connected are skipped.\n this.watchCapability('mesh-network', {\n onReady: () => { void this.autoStartEnabledProviders() },\n })\n\n // Backend-only addon — registers no capability.\n return []\n }\n\n /**\n * Maintain the persisted `enabledProviders` set from a tunnel\n * lifecycle event. `source.id` is `string | number`; `network-access`\n * providers emit with `type: 'addon'` so it is always the addonId\n * string. Non-string / non-addon sources are ignored defensively.\n */\n private async onTunnelLifecycle(\n source: { readonly type: string; readonly id: string | number },\n started: boolean,\n ): Promise<void> {\n if (source.type !== 'addon' || typeof source.id !== 'string') {\n this.ctx.logger.warn('tunnel lifecycle event with non-addon source — ignoring', {\n meta: { sourceType: source.type, sourceId: source.id, started },\n })\n return\n }\n await this.markEnabled(source.id, started)\n }\n\n private async autoStartEnabledProviders(): Promise<void> {\n const ids = this.config.enabledProviders\n if (ids.length === 0) return\n this.ctx.logger.info('Auto-starting enabled remote-access providers', {\n meta: { addonIds: [...ids] },\n })\n for (const addonId of ids) {\n try {\n const impl = this.resolveImpl(addonId)\n if (!impl?.start) {\n // Provider isn't loaded yet (worker bridge still hydrating)\n // OR it doesn't implement start. Log at warn level — the\n // `watchCapability` subscriptions above retry as soon as the\n // provider appears.\n this.ctx.logger.warn('autostart: provider not ready or unsupported', {\n meta: { addonId, hasImpl: !!impl, hasStart: !!impl?.start },\n })\n continue\n }\n // Idempotent: skip when the provider is already connected.\n // Avoids spamming start() on every ready-state event and\n // prevents respawning a child process that's already alive.\n if (impl.getStatus) {\n const status = await impl.getStatus().catch(() => null)\n if (status?.connected) {\n this.ctx.logger.info('autostart: provider already connected — skipping', {\n meta: { addonId, url: status.endpoint?.url },\n })\n continue\n }\n }\n const endpoint = await impl.start()\n this.ctx.logger.info('autostart: provider started', {\n meta: { addonId, url: endpoint.url },\n })\n } catch (err) {\n this.ctx.logger.error('autostart: provider start failed', {\n meta: {\n addonId,\n error: err instanceof Error ? err.message : String(err),\n },\n })\n }\n }\n }\n\n private async markEnabled(addonId: string, enabled: boolean): Promise<void> {\n const current = new Set(this.config.enabledProviders)\n const wasEnabled = current.has(addonId)\n if (enabled) current.add(addonId); else current.delete(addonId)\n if (wasEnabled === enabled) return\n this.ctx.logger.info('remote-access intent updated', {\n meta: { addonId, enabled },\n })\n await this.updateGlobalSettings({ enabledProviders: [...current] })\n }\n\n private resolveImpl(addonId: string): NetworkAccessLike | null {\n const entries = this.capabilities?.getCollectionEntries<NetworkAccessLike>(\n 'network-access',\n ) ?? []\n const found = entries.find(([id]) => id === addonId)\n return found?.[1] ?? null\n }\n}\n\nexport default RemoteAccessOrchestratorAddon\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6CA,IAAa,gCAAb,cAAmD,gBAAA,UAA0C;CAC3F,cAAc;EACZ,MAAM,EAAE,kBAAkB,EAAE,EAAE,CAAC;;CAGjC,MAAgB,eAAgD;EAC9D,KAAK,IAAI,OAAO,KAAK,yDAAyD,EAC5E,MAAM,EAAE,cAAc,KAAK,OAAO,iBAAiB,QAAQ,EAC5D,CAAC;EAMF,KAAK,IAAI,UAAU,UACjB,EAAE,UAAU,gBAAA,cAAc,sBAAsB,GAC/C,UAAU;GAAE,KAAU,kBAAkB,MAAM,QAAQ,KAAK;IAC7D;EACD,KAAK,IAAI,UAAU,UACjB,EAAE,UAAU,gBAAA,cAAc,sBAAsB,GAC/C,UAAU;GAAE,KAAU,kBAAkB,MAAM,QAAQ,MAAM;IAC9D;EAQD,mBAAmB;GAAE,KAAU,2BAA2B;IAAG;EAS7D,KAAK,gBAAgB,kBAAkB,EACrC,eAAe;GAAE,KAAU,2BAA2B;KACvD,CAAC;EAWF,KAAK,gBAAgB,gBAAgB,EACnC,eAAe;GAAE,KAAU,2BAA2B;KACvD,CAAC;EAGF,OAAO,EAAE;;;;;;;;CASX,MAAc,kBACZ,QACA,SACe;EACf,IAAI,OAAO,SAAS,WAAW,OAAO,OAAO,OAAO,UAAU;GAC5D,KAAK,IAAI,OAAO,KAAK,2DAA2D,EAC9E,MAAM;IAAE,YAAY,OAAO;IAAM,UAAU,OAAO;IAAI;IAAS,EAChE,CAAC;GACF;;EAEF,MAAM,KAAK,YAAY,OAAO,IAAI,QAAQ;;CAG5C,MAAc,4BAA2C;EACvD,MAAM,MAAM,KAAK,OAAO;EACxB,IAAI,IAAI,WAAW,GAAG;EACtB,KAAK,IAAI,OAAO,KAAK,iDAAiD,EACpE,MAAM,EAAE,UAAU,CAAC,GAAG,IAAI,EAAE,EAC7B,CAAC;EACF,KAAK,MAAM,WAAW,KACpB,IAAI;GACF,MAAM,OAAO,KAAK,YAAY,QAAQ;GACtC,IAAI,CAAC,MAAM,OAAO;IAKhB,KAAK,IAAI,OAAO,KAAK,gDAAgD,EACnE,MAAM;KAAE;KAAS,SAAS,CAAC,CAAC;KAAM,UAAU,CAAC,CAAC,MAAM;KAAO,EAC5D,CAAC;IACF;;GAKF,IAAI,KAAK,WAAW;IAClB,MAAM,SAAS,MAAM,KAAK,WAAW,CAAC,YAAY,KAAK;IACvD,IAAI,QAAQ,WAAW;KACrB,KAAK,IAAI,OAAO,KAAK,oDAAoD,EACvE,MAAM;MAAE;MAAS,KAAK,OAAO,UAAU;MAAK,EAC7C,CAAC;KACF;;;GAGJ,MAAM,WAAW,MAAM,KAAK,OAAO;GACnC,KAAK,IAAI,OAAO,KAAK,+BAA+B,EAClD,MAAM;IAAE;IAAS,KAAK,SAAS;IAAK,EACrC,CAAC;WACK,KAAK;GACZ,KAAK,IAAI,OAAO,MAAM,oCAAoC,EACxD,MAAM;IACJ;IACA,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;IACxD,EACF,CAAC;;;CAKR,MAAc,YAAY,SAAiB,SAAiC;EAC1E,MAAM,UAAU,IAAI,IAAI,KAAK,OAAO,iBAAiB;EACrD,MAAM,aAAa,QAAQ,IAAI,QAAQ;EACvC,IAAI,SAAS,QAAQ,IAAI,QAAQ;OAAO,QAAQ,OAAO,QAAQ;EAC/D,IAAI,eAAe,SAAS;EAC5B,KAAK,IAAI,OAAO,KAAK,gCAAgC,EACnD,MAAM;GAAE;GAAS;GAAS,EAC3B,CAAC;EACF,MAAM,KAAK,qBAAqB,EAAE,kBAAkB,CAAC,GAAG,QAAQ,EAAE,CAAC;;CAGrE,YAAoB,SAA2C;EAK7D,QAJgB,KAAK,cAAc,qBACjC,iBACD,IAAI,EAAE,EACe,MAAM,CAAC,QAAQ,OAAO,QACrC,GAAQ,MAAM"}
|
|
@@ -1,40 +1,39 @@
|
|
|
1
|
-
import { BaseAddon,
|
|
1
|
+
import { BaseAddon, EventCategory } from "@camstack/types";
|
|
2
2
|
//#region src/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.ts
|
|
3
3
|
/**
|
|
4
|
-
* Remote-access orchestrator —
|
|
5
|
-
* `network-access` collection (Cloudflare Tunnel, ngrok, Tailscale, …).
|
|
6
|
-
* Mirrors the auth-orchestrator and backup-orchestrator patterns.
|
|
4
|
+
* Remote-access orchestrator — backend-only boot-autostart service for
|
|
5
|
+
* the `network-access` collection (Cloudflare Tunnel, ngrok, Tailscale, …).
|
|
7
6
|
*
|
|
8
|
-
*
|
|
7
|
+
* Retired its `remote-access` facade cap (2026-05-15): the admin UI now
|
|
8
|
+
* talks to the `network-access` collection cap directly via generic
|
|
9
|
+
* per-`addonId` routing, so this addon registers NO capability.
|
|
10
|
+
*
|
|
11
|
+
* What it still owns — the load-bearing logic:
|
|
9
12
|
* The orchestrator owns the "operator wants this provider running"
|
|
10
|
-
* intent —
|
|
13
|
+
* intent — an `enabledProviders: string[]` slice in its addon-store
|
|
11
14
|
* blob (BaseAddon.config). On boot we iterate the list and call
|
|
12
|
-
* `provider.start()` for each enabled entry
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
+
* `provider.start()` for each enabled entry, so a tunnel set up once
|
|
16
|
+
* stays up across hub restarts.
|
|
17
|
+
*
|
|
18
|
+
* Since start/stop no longer flow through this addon, the enabled-set
|
|
19
|
+
* is maintained purely from the event bus: every `network-access`
|
|
20
|
+
* provider emits `NetworkTunnelStarted` / `NetworkTunnelStopped` when
|
|
21
|
+
* it starts/stops. We add the emitting addonId on Started, remove it
|
|
22
|
+
* on Stopped, and persist — keeping the operator intent in sync no
|
|
23
|
+
* matter who triggered the lifecycle change.
|
|
15
24
|
*/
|
|
16
25
|
var RemoteAccessOrchestratorAddon = class extends BaseAddon {
|
|
17
26
|
constructor() {
|
|
18
27
|
super({ enabledProviders: [] });
|
|
19
28
|
}
|
|
20
29
|
async onInitialize() {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
return endpoint;
|
|
29
|
-
},
|
|
30
|
-
stopProvider: async ({ addonId }) => {
|
|
31
|
-
const impl = this.resolveImpl(addonId);
|
|
32
|
-
if (impl?.stop) await impl.stop();
|
|
33
|
-
await this.markEnabled(addonId, false);
|
|
34
|
-
return { success: true };
|
|
35
|
-
}
|
|
36
|
-
};
|
|
37
|
-
this.ctx.logger.info("Remote-access orchestrator initialized", { meta: { enabledCount: this.config.enabledProviders.length } });
|
|
30
|
+
this.ctx.logger.info("Remote-access orchestrator initialized (backend-only)", { meta: { enabledCount: this.config.enabledProviders.length } });
|
|
31
|
+
this.ctx.eventBus?.subscribe({ category: EventCategory.NetworkTunnelStarted }, (event) => {
|
|
32
|
+
this.onTunnelLifecycle(event.source, true);
|
|
33
|
+
});
|
|
34
|
+
this.ctx.eventBus?.subscribe({ category: EventCategory.NetworkTunnelStopped }, (event) => {
|
|
35
|
+
this.onTunnelLifecycle(event.source, false);
|
|
36
|
+
});
|
|
38
37
|
setImmediate(() => {
|
|
39
38
|
this.autoStartEnabledProviders();
|
|
40
39
|
});
|
|
@@ -44,10 +43,24 @@ var RemoteAccessOrchestratorAddon = class extends BaseAddon {
|
|
|
44
43
|
this.watchCapability("mesh-network", { onReady: () => {
|
|
45
44
|
this.autoStartEnabledProviders();
|
|
46
45
|
} });
|
|
47
|
-
return [
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
46
|
+
return [];
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Maintain the persisted `enabledProviders` set from a tunnel
|
|
50
|
+
* lifecycle event. `source.id` is `string | number`; `network-access`
|
|
51
|
+
* providers emit with `type: 'addon'` so it is always the addonId
|
|
52
|
+
* string. Non-string / non-addon sources are ignored defensively.
|
|
53
|
+
*/
|
|
54
|
+
async onTunnelLifecycle(source, started) {
|
|
55
|
+
if (source.type !== "addon" || typeof source.id !== "string") {
|
|
56
|
+
this.ctx.logger.warn("tunnel lifecycle event with non-addon source — ignoring", { meta: {
|
|
57
|
+
sourceType: source.type,
|
|
58
|
+
sourceId: source.id,
|
|
59
|
+
started
|
|
60
|
+
} });
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
await this.markEnabled(source.id, started);
|
|
51
64
|
}
|
|
52
65
|
async autoStartEnabledProviders() {
|
|
53
66
|
const ids = this.config.enabledProviders;
|
|
@@ -91,38 +104,15 @@ var RemoteAccessOrchestratorAddon = class extends BaseAddon {
|
|
|
91
104
|
if (enabled) current.add(addonId);
|
|
92
105
|
else current.delete(addonId);
|
|
93
106
|
if (wasEnabled === enabled) return;
|
|
107
|
+
this.ctx.logger.info("remote-access intent updated", { meta: {
|
|
108
|
+
addonId,
|
|
109
|
+
enabled
|
|
110
|
+
} });
|
|
94
111
|
await this.updateGlobalSettings({ enabledProviders: [...current] });
|
|
95
112
|
}
|
|
96
113
|
resolveImpl(addonId) {
|
|
97
114
|
return (this.capabilities?.getCollectionEntries("network-access") ?? []).find(([id]) => id === addonId)?.[1] ?? null;
|
|
98
115
|
}
|
|
99
|
-
async listProviders() {
|
|
100
|
-
const entries = this.capabilities?.getCollectionEntries("network-access") ?? [];
|
|
101
|
-
const enabled = new Set(this.config.enabledProviders);
|
|
102
|
-
const out = [];
|
|
103
|
-
for (const [addonId, impl] of entries) {
|
|
104
|
-
let connected = false;
|
|
105
|
-
let endpoint = null;
|
|
106
|
-
let error;
|
|
107
|
-
if (impl.getStatus) try {
|
|
108
|
-
const s = await impl.getStatus();
|
|
109
|
-
connected = s.connected;
|
|
110
|
-
endpoint = s.endpoint;
|
|
111
|
-
error = s.error;
|
|
112
|
-
} catch (err) {
|
|
113
|
-
error = err instanceof Error ? err.message : String(err);
|
|
114
|
-
}
|
|
115
|
-
out.push({
|
|
116
|
-
addonId,
|
|
117
|
-
displayName: impl.displayName ?? addonId,
|
|
118
|
-
enabled: enabled.has(addonId),
|
|
119
|
-
connected,
|
|
120
|
-
endpoint,
|
|
121
|
-
...error !== void 0 ? { error } : {}
|
|
122
|
-
});
|
|
123
|
-
}
|
|
124
|
-
return out;
|
|
125
|
-
}
|
|
126
116
|
};
|
|
127
117
|
//#endregion
|
|
128
118
|
export { RemoteAccessOrchestratorAddon, RemoteAccessOrchestratorAddon as default };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"remote-access-orchestrator.addon.mjs","names":[],"sources":["../../../src/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.ts"],"sourcesContent":["/**\n * Remote-access orchestrator — singleton facade over the\n * `network-access` collection (Cloudflare Tunnel, ngrok, Tailscale, …).\n * Mirrors the auth-orchestrator and backup-orchestrator patterns.\n *\n * Persistence + autostart contract:\n * The orchestrator owns the \"operator wants this provider running\"\n * intent — a `enabledProviders: string[]` slice in its addon-store\n * blob (BaseAddon.config). On boot we iterate the list and call\n * `provider.start()` for each enabled entry. `startProvider` /\n * `stopProvider` mutate this list so a Start press persists across\n * restarts. Same shape as turn-orchestrator's setProviderEnabled.\n */\nimport {\n BaseAddon,\n remoteAccessCapability,\n type IRemoteAccessOrchestrator,\n type RemoteAccessProviderInfo,\n type ProviderRegistration,\n} from '@camstack/types'\n\ninterface NetworkAccessLike {\n start?: () => Promise<{ url: string; hostname: string; port: number; protocol: 'http' | 'https' }>\n stop?: () => Promise<void>\n getStatus?: () => Promise<{\n connected: boolean\n endpoint: { url: string; hostname: string; port: number; protocol: 'http' | 'https' } | null\n error?: string\n }>\n}\n\ninterface NetworkAccessRegistrationMeta {\n readonly displayName?: string\n}\n\ninterface RemoteAccessOrchestratorConfig {\n /**\n * addonIds the operator has explicitly Started. Auto-respawned on\n * boot so a tunnel set up once stays up across hub restarts.\n */\n readonly enabledProviders: readonly string[]\n}\n\nexport class RemoteAccessOrchestratorAddon extends BaseAddon<RemoteAccessOrchestratorConfig> {\n constructor() {\n super({ enabledProviders: [] })\n }\n\n protected async onInitialize(): Promise<ProviderRegistration[]> {\n const provider: IRemoteAccessOrchestrator = {\n listProviders: async () => this.listProviders(),\n startProvider: async ({ addonId }) => {\n const impl = this.resolveImpl(addonId)\n if (!impl?.start) throw new Error(`Remote-access provider \"${addonId}\" does not support start`)\n const endpoint = await impl.start()\n // Persist intent — next boot will auto-respawn this provider.\n await this.markEnabled(addonId, true)\n return endpoint\n },\n stopProvider: async ({ addonId }) => {\n const impl = this.resolveImpl(addonId)\n if (impl?.stop) await impl.stop()\n // Clear intent — boot must NOT respawn this on next start.\n await this.markEnabled(addonId, false)\n return { success: true as const }\n },\n } satisfies IRemoteAccessOrchestrator\n this.ctx.logger.info('Remote-access orchestrator initialized', {\n meta: { enabledCount: this.config.enabledProviders.length },\n })\n // Defer autostart to next tick so the orchestrator's own provider\n // registration completes first. `resolveImpl` reads from the\n // capabilities registry which only sees in-process / cluster-mirrored\n // providers once they've ALSO registered — small delay gives the\n // cluster bridge time to discover them on cold boot. Errors are\n // logged but never block init.\n setImmediate(() => { this.autoStartEnabledProviders() })\n\n // Lazy retry — forked providers (cloudflare-tunnel etc) typically\n // register 15-20 s after this orchestrator boots, well past the\n // `setImmediate` above. Hook BaseAddon's `system.ready-state`\n // subscription so we re-run autoStart every time the\n // `network-access` cap transitions to ready (whichever node holds\n // it). The inner logic is idempotent + skips already-connected\n // providers.\n this.watchCapability('network-access', {\n onReady: () => { void this.autoStartEnabledProviders() },\n })\n\n // Same watch for `mesh-network`. The tailscale-ingress provider\n // registers `network-access` synchronously at boot, but `start()`\n // throws when the tailnet isn't joined yet — so the boot-time\n // autoStart call fails for tailscale ingresses if the tailscale\n // daemon hadn't logged in by then. Watching `mesh-network` here\n // re-triggers autoStart the moment the client transitions to\n // joined (manual operator login or auto-rejoin), without needing\n // a server restart. Same idempotency rules: providers already\n // connected are skipped.\n this.watchCapability('mesh-network', {\n onReady: () => { void this.autoStartEnabledProviders() },\n })\n\n return [{ capability: remoteAccessCapability, provider }]\n }\n\n private async autoStartEnabledProviders(): Promise<void> {\n const ids = this.config.enabledProviders\n if (ids.length === 0) return\n this.ctx.logger.info('Auto-starting enabled remote-access providers', {\n meta: { addonIds: [...ids] },\n })\n for (const addonId of ids) {\n try {\n const impl = this.resolveImpl(addonId)\n if (!impl?.start) {\n // Provider isn't loaded yet (worker bridge still hydrating)\n // OR it doesn't implement start. Log at debug level — the\n // provider-registered subscription below will retry as soon\n // as the addon appears.\n this.ctx.logger.warn('autostart: provider not ready or unsupported', {\n meta: { addonId, hasImpl: !!impl, hasStart: !!impl?.start },\n })\n continue\n }\n // Idempotent: skip when the provider is already connected.\n // Avoids spamming start() on every provider-registered event\n // and prevents respawning a child process that's already alive.\n if (impl.getStatus) {\n const status = await impl.getStatus().catch(() => null)\n if (status?.connected) {\n this.ctx.logger.info('autostart: provider already connected — skipping', {\n meta: { addonId, url: status.endpoint?.url },\n })\n continue\n }\n }\n const endpoint = await impl.start()\n this.ctx.logger.info('autostart: provider started', {\n meta: { addonId, url: endpoint.url },\n })\n } catch (err) {\n this.ctx.logger.error('autostart: provider start failed', {\n meta: {\n addonId,\n error: err instanceof Error ? err.message : String(err),\n },\n })\n }\n }\n }\n\n private async markEnabled(addonId: string, enabled: boolean): Promise<void> {\n const current = new Set(this.config.enabledProviders)\n const wasEnabled = current.has(addonId)\n if (enabled) current.add(addonId); else current.delete(addonId)\n if (wasEnabled === enabled) return\n await this.updateGlobalSettings({ enabledProviders: [...current] })\n }\n\n private resolveImpl(addonId: string): (NetworkAccessLike & NetworkAccessRegistrationMeta) | null {\n const entries = this.capabilities?.getCollectionEntries<NetworkAccessLike & NetworkAccessRegistrationMeta>(\n 'network-access',\n ) ?? []\n const found = entries.find(([id]) => id === addonId)\n return found?.[1] ?? null\n }\n\n private async listProviders(): Promise<readonly RemoteAccessProviderInfo[]> {\n const entries = this.capabilities?.getCollectionEntries<NetworkAccessLike & NetworkAccessRegistrationMeta>(\n 'network-access',\n ) ?? []\n const enabled = new Set(this.config.enabledProviders)\n const out: RemoteAccessProviderInfo[] = []\n for (const [addonId, impl] of entries) {\n let connected = false\n let endpoint: RemoteAccessProviderInfo['endpoint'] = null\n let error: string | undefined\n if (impl.getStatus) {\n try {\n const s = await impl.getStatus()\n connected = s.connected\n endpoint = s.endpoint\n error = s.error\n } catch (err) {\n error = err instanceof Error ? err.message : String(err)\n }\n }\n out.push({\n addonId,\n displayName: impl.displayName ?? addonId,\n // `enabled` is now the operator's persisted intent — orthogonal\n // to `connected` (which reflects the live tunnel state). Boot\n // tries to bring enabled→connected automatically.\n enabled: enabled.has(addonId),\n connected,\n endpoint,\n ...(error !== undefined ? { error } : {}),\n })\n }\n return out\n }\n}\n\nexport default RemoteAccessOrchestratorAddon\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"remote-access-orchestrator.addon.mjs","names":[],"sources":["../../../src/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.ts"],"sourcesContent":["/**\n * Remote-access orchestrator — backend-only boot-autostart service for\n * the `network-access` collection (Cloudflare Tunnel, ngrok, Tailscale, …).\n *\n * Retired its `remote-access` facade cap (2026-05-15): the admin UI now\n * talks to the `network-access` collection cap directly via generic\n * per-`addonId` routing, so this addon registers NO capability.\n *\n * What it still owns — the load-bearing logic:\n * The orchestrator owns the \"operator wants this provider running\"\n * intent — an `enabledProviders: string[]` slice in its addon-store\n * blob (BaseAddon.config). On boot we iterate the list and call\n * `provider.start()` for each enabled entry, so a tunnel set up once\n * stays up across hub restarts.\n *\n * Since start/stop no longer flow through this addon, the enabled-set\n * is maintained purely from the event bus: every `network-access`\n * provider emits `NetworkTunnelStarted` / `NetworkTunnelStopped` when\n * it starts/stops. We add the emitting addonId on Started, remove it\n * on Stopped, and persist — keeping the operator intent in sync no\n * matter who triggered the lifecycle change.\n */\nimport {\n BaseAddon,\n EventCategory,\n type ProviderRegistration,\n} from '@camstack/types'\n\ninterface NetworkAccessLike {\n start?: () => Promise<{ url: string; hostname: string; port: number; protocol: 'http' | 'https' }>\n getStatus?: () => Promise<{\n connected: boolean\n endpoint: { url: string; hostname: string; port: number; protocol: 'http' | 'https' } | null\n error?: string\n }>\n}\n\ninterface RemoteAccessOrchestratorConfig {\n /**\n * addonIds the operator has explicitly Started. Auto-respawned on\n * boot so a tunnel set up once stays up across hub restarts.\n */\n readonly enabledProviders: readonly string[]\n}\n\nexport class RemoteAccessOrchestratorAddon extends BaseAddon<RemoteAccessOrchestratorConfig> {\n constructor() {\n super({ enabledProviders: [] })\n }\n\n protected async onInitialize(): Promise<ProviderRegistration[]> {\n this.ctx.logger.info('Remote-access orchestrator initialized (backend-only)', {\n meta: { enabledCount: this.config.enabledProviders.length },\n })\n\n // Track operator intent from the lifecycle events every\n // `network-access` provider emits. The emitting addonId is carried\n // on `event.source.id` (events are emitted with\n // `source: { type: 'addon', id: ctx.id }`).\n this.ctx.eventBus?.subscribe(\n { category: EventCategory.NetworkTunnelStarted },\n (event) => { void this.onTunnelLifecycle(event.source, true) },\n )\n this.ctx.eventBus?.subscribe(\n { category: EventCategory.NetworkTunnelStopped },\n (event) => { void this.onTunnelLifecycle(event.source, false) },\n )\n\n // Defer autostart to next tick so provider registrations from\n // co-located addons settle first. `resolveImpl` reads from the\n // capabilities registry which only sees in-process / cluster-mirrored\n // providers once they've ALSO registered — small delay gives the\n // cluster bridge time to discover them on cold boot. Errors are\n // logged but never block init.\n setImmediate(() => { void this.autoStartEnabledProviders() })\n\n // Lazy retry — forked providers (cloudflare-tunnel etc) typically\n // register 15-20 s after this orchestrator boots, well past the\n // `setImmediate` above. Hook BaseAddon's `system.ready-state`\n // subscription so we re-run autoStart every time the\n // `network-access` cap transitions to ready (whichever node holds\n // it). The inner logic is idempotent + skips already-connected\n // providers.\n this.watchCapability('network-access', {\n onReady: () => { void this.autoStartEnabledProviders() },\n })\n\n // Same watch for `mesh-network`. The tailscale-ingress provider\n // registers `network-access` synchronously at boot, but `start()`\n // throws when the tailnet isn't joined yet — so the boot-time\n // autoStart call fails for tailscale ingresses if the tailscale\n // daemon hadn't logged in by then. Watching `mesh-network` here\n // re-triggers autoStart the moment the client transitions to\n // joined (manual operator login or auto-rejoin), without needing\n // a server restart. Same idempotency rules: providers already\n // connected are skipped.\n this.watchCapability('mesh-network', {\n onReady: () => { void this.autoStartEnabledProviders() },\n })\n\n // Backend-only addon — registers no capability.\n return []\n }\n\n /**\n * Maintain the persisted `enabledProviders` set from a tunnel\n * lifecycle event. `source.id` is `string | number`; `network-access`\n * providers emit with `type: 'addon'` so it is always the addonId\n * string. Non-string / non-addon sources are ignored defensively.\n */\n private async onTunnelLifecycle(\n source: { readonly type: string; readonly id: string | number },\n started: boolean,\n ): Promise<void> {\n if (source.type !== 'addon' || typeof source.id !== 'string') {\n this.ctx.logger.warn('tunnel lifecycle event with non-addon source — ignoring', {\n meta: { sourceType: source.type, sourceId: source.id, started },\n })\n return\n }\n await this.markEnabled(source.id, started)\n }\n\n private async autoStartEnabledProviders(): Promise<void> {\n const ids = this.config.enabledProviders\n if (ids.length === 0) return\n this.ctx.logger.info('Auto-starting enabled remote-access providers', {\n meta: { addonIds: [...ids] },\n })\n for (const addonId of ids) {\n try {\n const impl = this.resolveImpl(addonId)\n if (!impl?.start) {\n // Provider isn't loaded yet (worker bridge still hydrating)\n // OR it doesn't implement start. Log at warn level — the\n // `watchCapability` subscriptions above retry as soon as the\n // provider appears.\n this.ctx.logger.warn('autostart: provider not ready or unsupported', {\n meta: { addonId, hasImpl: !!impl, hasStart: !!impl?.start },\n })\n continue\n }\n // Idempotent: skip when the provider is already connected.\n // Avoids spamming start() on every ready-state event and\n // prevents respawning a child process that's already alive.\n if (impl.getStatus) {\n const status = await impl.getStatus().catch(() => null)\n if (status?.connected) {\n this.ctx.logger.info('autostart: provider already connected — skipping', {\n meta: { addonId, url: status.endpoint?.url },\n })\n continue\n }\n }\n const endpoint = await impl.start()\n this.ctx.logger.info('autostart: provider started', {\n meta: { addonId, url: endpoint.url },\n })\n } catch (err) {\n this.ctx.logger.error('autostart: provider start failed', {\n meta: {\n addonId,\n error: err instanceof Error ? err.message : String(err),\n },\n })\n }\n }\n }\n\n private async markEnabled(addonId: string, enabled: boolean): Promise<void> {\n const current = new Set(this.config.enabledProviders)\n const wasEnabled = current.has(addonId)\n if (enabled) current.add(addonId); else current.delete(addonId)\n if (wasEnabled === enabled) return\n this.ctx.logger.info('remote-access intent updated', {\n meta: { addonId, enabled },\n })\n await this.updateGlobalSettings({ enabledProviders: [...current] })\n }\n\n private resolveImpl(addonId: string): NetworkAccessLike | null {\n const entries = this.capabilities?.getCollectionEntries<NetworkAccessLike>(\n 'network-access',\n ) ?? []\n const found = entries.find(([id]) => id === addonId)\n return found?.[1] ?? null\n }\n}\n\nexport default RemoteAccessOrchestratorAddon\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AA6CA,IAAa,gCAAb,cAAmD,UAA0C;CAC3F,cAAc;EACZ,MAAM,EAAE,kBAAkB,EAAE,EAAE,CAAC;;CAGjC,MAAgB,eAAgD;EAC9D,KAAK,IAAI,OAAO,KAAK,yDAAyD,EAC5E,MAAM,EAAE,cAAc,KAAK,OAAO,iBAAiB,QAAQ,EAC5D,CAAC;EAMF,KAAK,IAAI,UAAU,UACjB,EAAE,UAAU,cAAc,sBAAsB,GAC/C,UAAU;GAAE,KAAU,kBAAkB,MAAM,QAAQ,KAAK;IAC7D;EACD,KAAK,IAAI,UAAU,UACjB,EAAE,UAAU,cAAc,sBAAsB,GAC/C,UAAU;GAAE,KAAU,kBAAkB,MAAM,QAAQ,MAAM;IAC9D;EAQD,mBAAmB;GAAE,KAAU,2BAA2B;IAAG;EAS7D,KAAK,gBAAgB,kBAAkB,EACrC,eAAe;GAAE,KAAU,2BAA2B;KACvD,CAAC;EAWF,KAAK,gBAAgB,gBAAgB,EACnC,eAAe;GAAE,KAAU,2BAA2B;KACvD,CAAC;EAGF,OAAO,EAAE;;;;;;;;CASX,MAAc,kBACZ,QACA,SACe;EACf,IAAI,OAAO,SAAS,WAAW,OAAO,OAAO,OAAO,UAAU;GAC5D,KAAK,IAAI,OAAO,KAAK,2DAA2D,EAC9E,MAAM;IAAE,YAAY,OAAO;IAAM,UAAU,OAAO;IAAI;IAAS,EAChE,CAAC;GACF;;EAEF,MAAM,KAAK,YAAY,OAAO,IAAI,QAAQ;;CAG5C,MAAc,4BAA2C;EACvD,MAAM,MAAM,KAAK,OAAO;EACxB,IAAI,IAAI,WAAW,GAAG;EACtB,KAAK,IAAI,OAAO,KAAK,iDAAiD,EACpE,MAAM,EAAE,UAAU,CAAC,GAAG,IAAI,EAAE,EAC7B,CAAC;EACF,KAAK,MAAM,WAAW,KACpB,IAAI;GACF,MAAM,OAAO,KAAK,YAAY,QAAQ;GACtC,IAAI,CAAC,MAAM,OAAO;IAKhB,KAAK,IAAI,OAAO,KAAK,gDAAgD,EACnE,MAAM;KAAE;KAAS,SAAS,CAAC,CAAC;KAAM,UAAU,CAAC,CAAC,MAAM;KAAO,EAC5D,CAAC;IACF;;GAKF,IAAI,KAAK,WAAW;IAClB,MAAM,SAAS,MAAM,KAAK,WAAW,CAAC,YAAY,KAAK;IACvD,IAAI,QAAQ,WAAW;KACrB,KAAK,IAAI,OAAO,KAAK,oDAAoD,EACvE,MAAM;MAAE;MAAS,KAAK,OAAO,UAAU;MAAK,EAC7C,CAAC;KACF;;;GAGJ,MAAM,WAAW,MAAM,KAAK,OAAO;GACnC,KAAK,IAAI,OAAO,KAAK,+BAA+B,EAClD,MAAM;IAAE;IAAS,KAAK,SAAS;IAAK,EACrC,CAAC;WACK,KAAK;GACZ,KAAK,IAAI,OAAO,MAAM,oCAAoC,EACxD,MAAM;IACJ;IACA,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;IACxD,EACF,CAAC;;;CAKR,MAAc,YAAY,SAAiB,SAAiC;EAC1E,MAAM,UAAU,IAAI,IAAI,KAAK,OAAO,iBAAiB;EACrD,MAAM,aAAa,QAAQ,IAAI,QAAQ;EACvC,IAAI,SAAS,QAAQ,IAAI,QAAQ;OAAO,QAAQ,OAAO,QAAQ;EAC/D,IAAI,eAAe,SAAS;EAC5B,KAAK,IAAI,OAAO,KAAK,gCAAgC,EACnD,MAAM;GAAE;GAAS;GAAS,EAC3B,CAAC;EACF,MAAM,KAAK,qBAAqB,EAAE,kBAAkB,CAAC,GAAG,QAAQ,EAAE,CAAC;;CAGrE,YAAoB,SAA2C;EAK7D,QAJgB,KAAK,cAAc,qBACjC,iBACD,IAAI,EAAE,EACe,MAAM,CAAC,QAAQ,OAAO,QACrC,GAAQ,MAAM"}
|
package/dist/index.js
CHANGED
|
@@ -37,7 +37,7 @@ let node_vm = require("node:vm");
|
|
|
37
37
|
node_vm = require_chunk.__toESM(node_vm);
|
|
38
38
|
let node_os = require("node:os");
|
|
39
39
|
node_os = require_chunk.__toESM(node_os);
|
|
40
|
-
//#region ../types/dist/index-
|
|
40
|
+
//#region ../types/dist/index-CWhQOnm9.mjs
|
|
41
41
|
var MODEL_FORMATS = [
|
|
42
42
|
"onnx",
|
|
43
43
|
"coreml",
|
|
@@ -3591,42 +3591,6 @@ method(zod.z.object({
|
|
|
3591
3591
|
username: zod.z.string(),
|
|
3592
3592
|
password: zod.z.string()
|
|
3593
3593
|
}), AuthResultSchema.nullable(), { kind: "mutation" }), method(zod.z.object({ state: zod.z.string() }), zod.z.string()), method(zod.z.record(zod.z.string(), zod.z.string()), AuthResultSchema, { kind: "mutation" }), method(zod.z.object({ token: zod.z.string() }), AuthResultSchema.nullable());
|
|
3594
|
-
var AuthProviderInfoSchema = zod.z.object({
|
|
3595
|
-
/** Stable id matching the addon id (used for `getLoginUrl({addonId,…})`). */
|
|
3596
|
-
addonId: zod.z.string(),
|
|
3597
|
-
/**
|
|
3598
|
-
* Per-instance id when one addon registers multiple "logical"
|
|
3599
|
-
* providers (e.g. OIDC with Google + Microsoft + custom). The login
|
|
3600
|
-
* URL becomes `/addon/${addonId}/${instanceId}/start` — handler reads
|
|
3601
|
-
* `:instanceId` from the route. Empty/unset means the addon is a
|
|
3602
|
-
* single-instance provider; the URL is `/addon/${addonId}/start`.
|
|
3603
|
-
*/
|
|
3604
|
-
instanceId: zod.z.string().optional(),
|
|
3605
|
-
/** Display label shown on the login button + admin row. */
|
|
3606
|
-
displayName: zod.z.string(),
|
|
3607
|
-
/** Optional iconography hint (lucide-react icon name OR emoji). */
|
|
3608
|
-
icon: zod.z.string().optional(),
|
|
3609
|
-
/** When true, the provider exposes a redirect-based login flow
|
|
3610
|
-
* (`getLoginUrl` returns a URL the browser navigates to). */
|
|
3611
|
-
hasRedirectFlow: zod.z.boolean(),
|
|
3612
|
-
/** When true, the provider exposes a credential-form login flow
|
|
3613
|
-
* (`validateCredentials` accepts username + password). */
|
|
3614
|
-
hasCredentialFlow: zod.z.boolean(),
|
|
3615
|
-
/** Provider kind, drives admin-UI hint dispatch (oidc / saml / totp / …). */
|
|
3616
|
-
kind: zod.z.string().optional(),
|
|
3617
|
-
/** Operator-facing status string (e.g. "Connected to https://login.acme.com"). */
|
|
3618
|
-
status: zod.z.string().optional(),
|
|
3619
|
-
/** When false, the provider is registered but disabled by config; the
|
|
3620
|
-
* UI surfaces it as inactive without enumerating it for login. */
|
|
3621
|
-
enabled: zod.z.boolean()
|
|
3622
|
-
});
|
|
3623
|
-
method(zod.z.void(), zod.z.array(AuthProviderInfoSchema).readonly()), method(zod.z.object({
|
|
3624
|
-
addonId: zod.z.string(),
|
|
3625
|
-
enabled: zod.z.boolean()
|
|
3626
|
-
}), zod.z.object({ success: zod.z.literal(true) }), {
|
|
3627
|
-
kind: "mutation",
|
|
3628
|
-
auth: "admin"
|
|
3629
|
-
});
|
|
3630
3594
|
var NetworkEndpointSchema = zod.z.object({
|
|
3631
3595
|
url: zod.z.string(),
|
|
3632
3596
|
hostname: zod.z.string(),
|
|
@@ -3652,33 +3616,6 @@ var NetworkEndpointEntrySchema = NetworkEndpointSchema.extend({
|
|
|
3652
3616
|
sourcePort: zod.z.number().optional()
|
|
3653
3617
|
});
|
|
3654
3618
|
method(zod.z.void(), NetworkEndpointSchema, { kind: "mutation" }), method(zod.z.void(), zod.z.void(), { kind: "mutation" }), method(zod.z.void(), NetworkEndpointSchema.nullable()), method(zod.z.void(), NetworkAccessStatusSchema), method(zod.z.void(), zod.z.array(NetworkEndpointEntrySchema).readonly());
|
|
3655
|
-
var RemoteAccessEndpointSchema = zod.z.object({
|
|
3656
|
-
url: zod.z.string(),
|
|
3657
|
-
hostname: zod.z.string(),
|
|
3658
|
-
port: zod.z.number(),
|
|
3659
|
-
protocol: zod.z.enum(["http", "https"])
|
|
3660
|
-
});
|
|
3661
|
-
var RemoteAccessProviderInfoSchema = zod.z.object({
|
|
3662
|
-
/** Stable id matching the addon id. */
|
|
3663
|
-
addonId: zod.z.string(),
|
|
3664
|
-
/** Display label shown on the admin row — sourced from the addon manifest. */
|
|
3665
|
-
displayName: zod.z.string(),
|
|
3666
|
-
/** When false, the provider is registered but disabled. */
|
|
3667
|
-
enabled: zod.z.boolean(),
|
|
3668
|
-
/** True when the underlying tunnel/connection is up. */
|
|
3669
|
-
connected: zod.z.boolean(),
|
|
3670
|
-
/** Public-facing endpoint, when connected. Null otherwise. */
|
|
3671
|
-
endpoint: RemoteAccessEndpointSchema.nullable(),
|
|
3672
|
-
/** Last error message (when connected=false), if available. */
|
|
3673
|
-
error: zod.z.string().optional()
|
|
3674
|
-
});
|
|
3675
|
-
method(zod.z.void(), zod.z.array(RemoteAccessProviderInfoSchema).readonly()), method(zod.z.object({ addonId: zod.z.string() }), RemoteAccessEndpointSchema, {
|
|
3676
|
-
kind: "mutation",
|
|
3677
|
-
auth: "admin"
|
|
3678
|
-
}), method(zod.z.object({ addonId: zod.z.string() }), zod.z.object({ success: zod.z.literal(true) }), {
|
|
3679
|
-
kind: "mutation",
|
|
3680
|
-
auth: "admin"
|
|
3681
|
-
});
|
|
3682
3619
|
var TurnServerSchema = zod.z.object({
|
|
3683
3620
|
/** Single URL or list of URLs (e.g. "turn:turn.example.com:3478?transport=udp"). */
|
|
3684
3621
|
urls: zod.z.union([zod.z.string(), zod.z.array(zod.z.string())]),
|
|
@@ -3686,33 +3623,6 @@ var TurnServerSchema = zod.z.object({
|
|
|
3686
3623
|
credential: zod.z.string().optional()
|
|
3687
3624
|
});
|
|
3688
3625
|
method(zod.z.void(), zod.z.array(TurnServerSchema).readonly());
|
|
3689
|
-
var TurnProviderInfoSchema = zod.z.object({
|
|
3690
|
-
/** Stable id matching the addon id. */
|
|
3691
|
-
addonId: zod.z.string(),
|
|
3692
|
-
/** Display label shown on the admin row — sourced from the addon manifest. */
|
|
3693
|
-
displayName: zod.z.string(),
|
|
3694
|
-
/** When false, the provider is registered but disabled. */
|
|
3695
|
-
enabled: zod.z.boolean(),
|
|
3696
|
-
/** Number of servers this provider is currently exposing. */
|
|
3697
|
-
serverCount: zod.z.number(),
|
|
3698
|
-
/**
|
|
3699
|
-
* Flat list of every TURN/STUN URL this provider currently exposes.
|
|
3700
|
-
* One row per URL (multi-URL ICE server entries are flattened). The
|
|
3701
|
-
* admin UI shows this in a compact per-provider list so operators
|
|
3702
|
-
* can verify what's actually being negotiated without having to dig
|
|
3703
|
-
* into the combined `getAllServers` output.
|
|
3704
|
-
*/
|
|
3705
|
-
urls: zod.z.array(zod.z.string()).readonly(),
|
|
3706
|
-
/** Last fetch error (when serverCount=0 due to API failure), if any. */
|
|
3707
|
-
error: zod.z.string().optional()
|
|
3708
|
-
});
|
|
3709
|
-
method(zod.z.void(), zod.z.array(TurnProviderInfoSchema).readonly()), method(zod.z.void(), zod.z.array(TurnServerSchema).readonly()), method(zod.z.object({
|
|
3710
|
-
addonId: zod.z.string(),
|
|
3711
|
-
enabled: zod.z.boolean()
|
|
3712
|
-
}), zod.z.object({ success: zod.z.literal(true) }), {
|
|
3713
|
-
kind: "mutation",
|
|
3714
|
-
auth: "admin"
|
|
3715
|
-
});
|
|
3716
3626
|
var SnapshotImageSchema = zod.z.object({
|
|
3717
3627
|
base64: zod.z.string(),
|
|
3718
3628
|
contentType: zod.z.string()
|
|
@@ -4809,7 +4719,7 @@ method(zod.z.void(), ListResultSchema), method(zod.z.void(), PreferredSchema), m
|
|
|
4809
4719
|
* tunnel always emits `https://` regardless. */
|
|
4810
4720
|
scheme: zod.z.enum(["http", "https"]).optional()
|
|
4811
4721
|
}), GetConnectionEndpointsResultSchema), method(zod.z.void(), AllowedAddressesSchema), method(AllowedAddressesSchema, zod.z.object({ success: zod.z.literal(true) }), { kind: "mutation" }), method(zod.z.void(), AllowedAddressesSchema, { kind: "mutation" });
|
|
4812
|
-
var MeshEndpointSchema
|
|
4722
|
+
var MeshEndpointSchema = zod.z.object({
|
|
4813
4723
|
/** Stable identifier within the provider (e.g. `mesh-ipv4`, `magicdns`, `funnel`). */
|
|
4814
4724
|
id: zod.z.string(),
|
|
4815
4725
|
/** Operator-facing label (e.g. "Mesh IPv4", "MagicDNS"). */
|
|
@@ -4886,7 +4796,7 @@ var MeshStatusSchema = zod.z.object({
|
|
|
4886
4796
|
/** Number of peers visible to this host (excluding self). */
|
|
4887
4797
|
peerCount: zod.z.number(),
|
|
4888
4798
|
/** Every endpoint this provider exposes for the current host. */
|
|
4889
|
-
endpoints: zod.z.array(MeshEndpointSchema
|
|
4799
|
+
endpoints: zod.z.array(MeshEndpointSchema).readonly(),
|
|
4890
4800
|
/** Last error from the daemon, when not joined. */
|
|
4891
4801
|
error: zod.z.string().optional(),
|
|
4892
4802
|
/**
|
|
@@ -4961,51 +4871,6 @@ authKey: zod.z.string().optional() }), zod.z.object({
|
|
|
4961
4871
|
/** Human-readable error when `ok: false`. */
|
|
4962
4872
|
error: zod.z.string().optional()
|
|
4963
4873
|
}), { kind: "mutation" });
|
|
4964
|
-
var MeshEndpointSchema = zod.z.object({
|
|
4965
|
-
id: zod.z.string(),
|
|
4966
|
-
label: zod.z.string(),
|
|
4967
|
-
scope: zod.z.enum(["mesh", "public"]),
|
|
4968
|
-
url: zod.z.string(),
|
|
4969
|
-
hostname: zod.z.string(),
|
|
4970
|
-
port: zod.z.number(),
|
|
4971
|
-
protocol: zod.z.enum(["http", "https"])
|
|
4972
|
-
});
|
|
4973
|
-
var MeshProviderInfoSchema = zod.z.object({
|
|
4974
|
-
/** Stable id matching the addon id. */
|
|
4975
|
-
addonId: zod.z.string(),
|
|
4976
|
-
/** Display label shown on the admin row — sourced from the addon manifest. */
|
|
4977
|
-
displayName: zod.z.string(),
|
|
4978
|
-
/** True when the host is joined to this provider's mesh. */
|
|
4979
|
-
joined: zod.z.boolean(),
|
|
4980
|
-
/** Local mesh IP (empty when not joined). */
|
|
4981
|
-
meshIp: zod.z.string(),
|
|
4982
|
-
/** MagicDNS / mesh hostname (empty when not configured). */
|
|
4983
|
-
magicDnsHostname: zod.z.string(),
|
|
4984
|
-
/** Peer count (excluding self). */
|
|
4985
|
-
peerCount: zod.z.number(),
|
|
4986
|
-
/** Active endpoints (mesh IP + MagicDNS + optional public Funnel). */
|
|
4987
|
-
endpoints: zod.z.array(MeshEndpointSchema).readonly(),
|
|
4988
|
-
/** Last error reported by the provider. */
|
|
4989
|
-
error: zod.z.string().optional(),
|
|
4990
|
-
/** Tenant / tailnet / network display name. Empty pre-join. */
|
|
4991
|
-
tenantName: zod.z.string(),
|
|
4992
|
-
/** Mesh DNS suffix (e.g. tailXXXX.ts.net). Empty when not configured. */
|
|
4993
|
-
magicDnsSuffix: zod.z.string(),
|
|
4994
|
-
/** Authenticated user / account login. Null for token-only providers. */
|
|
4995
|
-
userLogin: zod.z.string().nullable(),
|
|
4996
|
-
/** Provider control-plane URL. */
|
|
4997
|
-
controlPlaneUrl: zod.z.string(),
|
|
4998
|
-
/** Machine-key expiry (epoch ms). Null when keys don't rotate. */
|
|
4999
|
-
keyExpiry: zod.z.number().nullable()
|
|
5000
|
-
});
|
|
5001
|
-
method(zod.z.void(), zod.z.array(MeshProviderInfoSchema).readonly()), method(zod.z.object({
|
|
5002
|
-
addonId: zod.z.string(),
|
|
5003
|
-
authKey: zod.z.string().min(8),
|
|
5004
|
-
hostname: zod.z.string().optional()
|
|
5005
|
-
}), zod.z.object({ joined: zod.z.literal(true) }), { kind: "mutation" }), method(zod.z.object({ addonId: zod.z.string() }), zod.z.object({ success: zod.z.literal(true) }), { kind: "mutation" }), method(zod.z.object({
|
|
5006
|
-
addonId: zod.z.string(),
|
|
5007
|
-
hostname: zod.z.string().optional()
|
|
5008
|
-
}), zod.z.object({ loginUrl: zod.z.string() }), { kind: "mutation" }), method(zod.z.object({ addonId: zod.z.string() }), zod.z.object({ loggedOut: zod.z.literal(true) }), { kind: "mutation" }), method(zod.z.object({ addonId: zod.z.string() }), zod.z.object({ peers: zod.z.array(MeshPeerSchema).readonly() }));
|
|
5009
4874
|
var MethodAccessSchema = zod.z.enum([
|
|
5010
4875
|
"view",
|
|
5011
4876
|
"create",
|
|
@@ -5695,6 +5560,13 @@ method(zod.z.void(), zod.z.array(AddonListItemSchema).readonly()), method(zod.z.
|
|
|
5695
5560
|
mode: zod.z.enum(["singleton", "collection"]),
|
|
5696
5561
|
isActive: zod.z.boolean()
|
|
5697
5562
|
})).readonly()), method(zod.z.object({
|
|
5563
|
+
capName: zod.z.string().min(1),
|
|
5564
|
+
addonId: zod.z.string().min(1),
|
|
5565
|
+
enabled: zod.z.boolean()
|
|
5566
|
+
}), zod.z.object({ success: zod.z.literal(true) }), {
|
|
5567
|
+
kind: "mutation",
|
|
5568
|
+
auth: "admin"
|
|
5569
|
+
}), method(zod.z.object({
|
|
5698
5570
|
packageName: zod.z.string().min(1),
|
|
5699
5571
|
version: zod.z.string().optional()
|
|
5700
5572
|
}), UpdateFrameworkPackageResultSchema, {
|