@synoikia/core 0.1.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/LICENSE +21 -0
- package/dist/app.d.ts +79 -0
- package/dist/app.js +155 -0
- package/dist/app.js.map +1 -0
- package/dist/approvals/links.d.ts +23 -0
- package/dist/approvals/links.js +49 -0
- package/dist/approvals/links.js.map +1 -0
- package/dist/approvals/service.d.ts +110 -0
- package/dist/approvals/service.js +182 -0
- package/dist/approvals/service.js.map +1 -0
- package/dist/audit-query.d.ts +52 -0
- package/dist/audit-query.js +116 -0
- package/dist/audit-query.js.map +1 -0
- package/dist/audit.d.ts +5 -0
- package/dist/audit.js +8 -0
- package/dist/audit.js.map +1 -0
- package/dist/auth/mcp-auth.d.ts +50 -0
- package/dist/auth/mcp-auth.js +160 -0
- package/dist/auth/mcp-auth.js.map +1 -0
- package/dist/auth/mcp-tokens.d.ts +57 -0
- package/dist/auth/mcp-tokens.js +143 -0
- package/dist/auth/mcp-tokens.js.map +1 -0
- package/dist/auth/oauth.d.ts +188 -0
- package/dist/auth/oauth.js +470 -0
- package/dist/auth/oauth.js.map +1 -0
- package/dist/auth/oidc.d.ts +110 -0
- package/dist/auth/oidc.js +221 -0
- package/dist/auth/oidc.js.map +1 -0
- package/dist/auth/sessions.d.ts +34 -0
- package/dist/auth/sessions.js +83 -0
- package/dist/auth/sessions.js.map +1 -0
- package/dist/auth/throttle.d.ts +31 -0
- package/dist/auth/throttle.js +52 -0
- package/dist/auth/throttle.js.map +1 -0
- package/dist/auth/tokens.d.ts +7 -0
- package/dist/auth/tokens.js +40 -0
- package/dist/auth/tokens.js.map +1 -0
- package/dist/auth/totp.d.ts +14 -0
- package/dist/auth/totp.js +81 -0
- package/dist/auth/totp.js.map +1 -0
- package/dist/auth/users.d.ts +70 -0
- package/dist/auth/users.js +296 -0
- package/dist/auth/users.js.map +1 -0
- package/dist/catalog/groups.d.ts +102 -0
- package/dist/catalog/groups.js +363 -0
- package/dist/catalog/groups.js.map +1 -0
- package/dist/catalog/registry.d.ts +25 -0
- package/dist/catalog/registry.js +80 -0
- package/dist/catalog/registry.js.map +1 -0
- package/dist/catalog/rules.d.ts +128 -0
- package/dist/catalog/rules.js +178 -0
- package/dist/catalog/rules.js.map +1 -0
- package/dist/catalog/sync.d.ts +29 -0
- package/dist/catalog/sync.js +222 -0
- package/dist/catalog/sync.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +83 -0
- package/dist/cli.js.map +1 -0
- package/dist/config/env.d.ts +40 -0
- package/dist/config/env.js +71 -0
- package/dist/config/env.js.map +1 -0
- package/dist/crypto/index.d.ts +28 -0
- package/dist/crypto/index.js +88 -0
- package/dist/crypto/index.js.map +1 -0
- package/dist/crypto/rotate.d.ts +15 -0
- package/dist/crypto/rotate.js +61 -0
- package/dist/crypto/rotate.js.map +1 -0
- package/dist/db/index.d.ts +17 -0
- package/dist/db/index.js +29 -0
- package/dist/db/index.js.map +1 -0
- package/dist/db/schema.d.ts +4389 -0
- package/dist/db/schema.js +372 -0
- package/dist/db/schema.js.map +1 -0
- package/dist/endpoints/slug.d.ts +3 -0
- package/dist/endpoints/slug.js +7 -0
- package/dist/endpoints/slug.js.map +1 -0
- package/dist/errors.d.ts +16 -0
- package/dist/errors.js +29 -0
- package/dist/errors.js.map +1 -0
- package/dist/events.d.ts +43 -0
- package/dist/events.js +16 -0
- package/dist/events.js.map +1 -0
- package/dist/gate/access.d.ts +47 -0
- package/dist/gate/access.js +48 -0
- package/dist/gate/access.js.map +1 -0
- package/dist/gate/attestation.d.ts +23 -0
- package/dist/gate/attestation.js +33 -0
- package/dist/gate/attestation.js.map +1 -0
- package/dist/gate/canonical.d.ts +3 -0
- package/dist/gate/canonical.js +11 -0
- package/dist/gate/canonical.js.map +1 -0
- package/dist/gate/match.d.ts +67 -0
- package/dist/gate/match.js +133 -0
- package/dist/gate/match.js.map +1 -0
- package/dist/gate/pipeline.d.ts +61 -0
- package/dist/gate/pipeline.js +284 -0
- package/dist/gate/pipeline.js.map +1 -0
- package/dist/gate/preapproval.d.ts +24 -0
- package/dist/gate/preapproval.js +48 -0
- package/dist/gate/preapproval.js.map +1 -0
- package/dist/gate/rate-limit.d.ts +35 -0
- package/dist/gate/rate-limit.js +82 -0
- package/dist/gate/rate-limit.js.map +1 -0
- package/dist/gate/redact.d.ts +23 -0
- package/dist/gate/redact.js +87 -0
- package/dist/gate/redact.js.map +1 -0
- package/dist/http/admin/auth.d.ts +33 -0
- package/dist/http/admin/auth.js +338 -0
- package/dist/http/admin/auth.js.map +1 -0
- package/dist/http/admin/instances.d.ts +5 -0
- package/dist/http/admin/instances.js +193 -0
- package/dist/http/admin/instances.js.map +1 -0
- package/dist/http/admin/system.d.ts +5 -0
- package/dist/http/admin/system.js +225 -0
- package/dist/http/admin/system.js.map +1 -0
- package/dist/http/admin-app.d.ts +9 -0
- package/dist/http/admin-app.js +51 -0
- package/dist/http/admin-app.js.map +1 -0
- package/dist/http/common.d.ts +32 -0
- package/dist/http/common.js +80 -0
- package/dist/http/common.js.map +1 -0
- package/dist/http/mcp/approval-routes.d.ts +11 -0
- package/dist/http/mcp/approval-routes.js +117 -0
- package/dist/http/mcp/approval-routes.js.map +1 -0
- package/dist/http/mcp/endpoint.d.ts +32 -0
- package/dist/http/mcp/endpoint.js +293 -0
- package/dist/http/mcp/endpoint.js.map +1 -0
- package/dist/http/mcp/oauth-routes.d.ts +29 -0
- package/dist/http/mcp/oauth-routes.js +465 -0
- package/dist/http/mcp/oauth-routes.js.map +1 -0
- package/dist/http/mcp/pages.d.ts +63 -0
- package/dist/http/mcp/pages.js +200 -0
- package/dist/http/mcp/pages.js.map +1 -0
- package/dist/http/mcp-app.d.ts +7 -0
- package/dist/http/mcp-app.js +30 -0
- package/dist/http/mcp-app.js.map +1 -0
- package/dist/instances/connection.d.ts +37 -0
- package/dist/instances/connection.js +109 -0
- package/dist/instances/connection.js.map +1 -0
- package/dist/instances/manager.d.ts +313 -0
- package/dist/instances/manager.js +600 -0
- package/dist/instances/manager.js.map +1 -0
- package/dist/instances/settings.d.ts +42 -0
- package/dist/instances/settings.js +57 -0
- package/dist/instances/settings.js.map +1 -0
- package/dist/lenient.d.ts +16 -0
- package/dist/lenient.js +51 -0
- package/dist/lenient.js.map +1 -0
- package/dist/lock.d.ts +28 -0
- package/dist/lock.js +77 -0
- package/dist/lock.js.map +1 -0
- package/dist/main.d.ts +1 -0
- package/dist/main.js +38 -0
- package/dist/main.js.map +1 -0
- package/dist/maintenance.d.ts +10 -0
- package/dist/maintenance.js +48 -0
- package/dist/maintenance.js.map +1 -0
- package/dist/notify/service.d.ts +149 -0
- package/dist/notify/service.js +358 -0
- package/dist/notify/service.js.map +1 -0
- package/dist/plugins/discovery.d.ts +39 -0
- package/dist/plugins/discovery.js +157 -0
- package/dist/plugins/discovery.js.map +1 -0
- package/dist/plugins/minisign.d.ts +25 -0
- package/dist/plugins/minisign.js +49 -0
- package/dist/plugins/minisign.js.map +1 -0
- package/dist/plugins/process.d.ts +55 -0
- package/dist/plugins/process.js +162 -0
- package/dist/plugins/process.js.map +1 -0
- package/dist/plugins/repos.d.ts +198 -0
- package/dist/plugins/repos.js +612 -0
- package/dist/plugins/repos.js.map +1 -0
- package/dist/plugins/supervisor.d.ts +50 -0
- package/dist/plugins/supervisor.js +125 -0
- package/dist/plugins/supervisor.js.map +1 -0
- package/dist/runtime/index.d.ts +28 -0
- package/dist/runtime/index.js +248 -0
- package/dist/runtime/index.js.map +1 -0
- package/dist/sandbox/index.d.ts +53 -0
- package/dist/sandbox/index.js +197 -0
- package/dist/sandbox/index.js.map +1 -0
- package/dist/server.d.ts +16 -0
- package/dist/server.js +36 -0
- package/dist/server.js.map +1 -0
- package/dist/settings.d.ts +43 -0
- package/dist/settings.js +99 -0
- package/dist/settings.js.map +1 -0
- package/dist/testing/index.d.ts +73 -0
- package/dist/testing/index.js +174 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.js +4 -0
- package/dist/version.js.map +1 -0
- package/drizzle/0000_init.sql +334 -0
- package/drizzle/0001_totp_last_step.sql +1 -0
- package/drizzle/0002_access_levels.sql +16 -0
- package/drizzle/0003_drop_exclusion_flags.sql +2 -0
- package/drizzle/0004_grant_instance_ids.sql +1 -0
- package/drizzle/0005_rule_strict_miss.sql +1 -0
- package/drizzle/0006_attestation_waived.sql +1 -0
- package/drizzle/meta/0000_snapshot.json +2318 -0
- package/drizzle/meta/0001_snapshot.json +2325 -0
- package/drizzle/meta/0002_snapshot.json +2348 -0
- package/drizzle/meta/0003_snapshot.json +2332 -0
- package/drizzle/meta/0004_snapshot.json +2339 -0
- package/drizzle/meta/0005_snapshot.json +2346 -0
- package/drizzle/meta/0006_snapshot.json +2354 -0
- package/drizzle/meta/_journal.json +55 -0
- package/package.json +58 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { createHash, createPublicKey, verify } from 'node:crypto';
|
|
2
|
+
export class MinisignError extends Error {
|
|
3
|
+
}
|
|
4
|
+
/** Minisign prints key ids as a little-endian u64. */
|
|
5
|
+
const keyIdHex = (bytes) => Buffer.from(bytes).reverse().toString('hex').toUpperCase();
|
|
6
|
+
/** Accepts the bare base64 key line or a whole `.pub` file (with its untrusted comment). */
|
|
7
|
+
export function parsePublicKey(text) {
|
|
8
|
+
const line = text
|
|
9
|
+
.split(/\r?\n/)
|
|
10
|
+
.map((l) => l.trim())
|
|
11
|
+
.filter((l) => l && !l.startsWith('untrusted comment:'))[0];
|
|
12
|
+
const raw = line && /^[A-Za-z0-9+/]+={0,2}$/.test(line) ? Buffer.from(line, 'base64') : Buffer.alloc(0);
|
|
13
|
+
if (raw.length !== 42 || raw.subarray(0, 2).toString('latin1') !== 'Ed') {
|
|
14
|
+
throw new MinisignError('Not a minisign Ed25519 public key');
|
|
15
|
+
}
|
|
16
|
+
const key = createPublicKey({
|
|
17
|
+
key: { kty: 'OKP', crv: 'Ed25519', x: raw.subarray(10).toString('base64url') },
|
|
18
|
+
format: 'jwk',
|
|
19
|
+
});
|
|
20
|
+
return { keyId: keyIdHex(raw.subarray(2, 10)), key, base64: raw.toString('base64') };
|
|
21
|
+
}
|
|
22
|
+
/** Throws MinisignError unless `signature` is a valid minisign signature of `data` by `publicKey`. */
|
|
23
|
+
export function verifySignature(data, signature, publicKey) {
|
|
24
|
+
const lines = signature.split(/\r?\n/).map((l) => l.trim());
|
|
25
|
+
if (lines.length < 4 || !lines[0].startsWith('untrusted comment:') || !lines[2].startsWith('trusted comment: ')) {
|
|
26
|
+
throw new MinisignError('Malformed minisign signature');
|
|
27
|
+
}
|
|
28
|
+
const sig = Buffer.from(lines[1], 'base64');
|
|
29
|
+
const globalSig = Buffer.from(lines[3], 'base64');
|
|
30
|
+
if (sig.length !== 74 || globalSig.length !== 64)
|
|
31
|
+
throw new MinisignError('Malformed minisign signature');
|
|
32
|
+
const alg = sig.subarray(0, 2).toString('latin1');
|
|
33
|
+
if (alg !== 'Ed' && alg !== 'ED')
|
|
34
|
+
throw new MinisignError(`Unsupported signature algorithm ${alg}`);
|
|
35
|
+
const keyId = keyIdHex(sig.subarray(2, 10));
|
|
36
|
+
if (keyId !== publicKey.keyId) {
|
|
37
|
+
throw new MinisignError(`Signed by key ${keyId}, expected ${publicKey.keyId}`);
|
|
38
|
+
}
|
|
39
|
+
const signed = alg === 'ED' ? createHash('blake2b512').update(data).digest() : data;
|
|
40
|
+
const bare = sig.subarray(10);
|
|
41
|
+
if (!verify(null, signed, publicKey.key, bare))
|
|
42
|
+
throw new MinisignError('Signature does not match the file');
|
|
43
|
+
const trustedComment = lines[2].slice('trusted comment: '.length);
|
|
44
|
+
if (!verify(null, Buffer.concat([bare, Buffer.from(trustedComment, 'utf8')]), publicKey.key, globalSig)) {
|
|
45
|
+
throw new MinisignError('Trusted comment signature is invalid');
|
|
46
|
+
}
|
|
47
|
+
return { keyId, prehashed: alg === 'ED', trustedComment };
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=minisign.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"minisign.js","sourceRoot":"","sources":["../../src/plugins/minisign.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { EventEmitter } from 'node:events';
|
|
2
|
+
import type { ErrorCode, PluginHandlers, RpcMethod } from '@synoikia/plugin-sdk';
|
|
3
|
+
/**
|
|
4
|
+
* One plugin child process and the core side of its JSON-RPC-over-IPC channel (design §3.3, §4.4).
|
|
5
|
+
* Every result is validated before it is returned: plugin output is untrusted.
|
|
6
|
+
*/
|
|
7
|
+
export declare class PluginRpcError extends Error {
|
|
8
|
+
readonly code: ErrorCode;
|
|
9
|
+
readonly data?: unknown | undefined;
|
|
10
|
+
constructor(code: ErrorCode, message: string, data?: unknown | undefined);
|
|
11
|
+
}
|
|
12
|
+
/** The child is not running (never started, crashed, or stopped). Callers surface `PluginUnavailable`. */
|
|
13
|
+
export declare class PluginUnavailableError extends Error {
|
|
14
|
+
constructor(message?: string);
|
|
15
|
+
}
|
|
16
|
+
export declare class PluginTimeoutError extends Error {
|
|
17
|
+
constructor(method: string, ms: number);
|
|
18
|
+
}
|
|
19
|
+
/** The plugin answered with something that violates the contract. */
|
|
20
|
+
export declare class PluginProtocolError extends Error {
|
|
21
|
+
constructor(method: string, detail: string);
|
|
22
|
+
}
|
|
23
|
+
type Params<M extends RpcMethod> = Parameters<NonNullable<PluginHandlers[M]>>[0];
|
|
24
|
+
type Result<M extends RpcMethod> = Awaited<ReturnType<NonNullable<PluginHandlers[M]>>>;
|
|
25
|
+
export interface SpawnOptions {
|
|
26
|
+
/** Plugin package directory; the only path the child may read. */
|
|
27
|
+
dir: string;
|
|
28
|
+
entry: string;
|
|
29
|
+
instanceId: string;
|
|
30
|
+
memoryMb?: number;
|
|
31
|
+
defaultTimeoutMs?: number;
|
|
32
|
+
}
|
|
33
|
+
export interface PluginProcessEvents {
|
|
34
|
+
exit: [code: number | null, signal: NodeJS.Signals | null];
|
|
35
|
+
log: [level: string, message: string];
|
|
36
|
+
catalogChanged: [reason?: string];
|
|
37
|
+
}
|
|
38
|
+
export declare class PluginProcess extends EventEmitter<PluginProcessEvents> {
|
|
39
|
+
private readonly opts;
|
|
40
|
+
private child?;
|
|
41
|
+
private nextId;
|
|
42
|
+
private readonly pending;
|
|
43
|
+
private exited;
|
|
44
|
+
constructor(opts: SpawnOptions);
|
|
45
|
+
get running(): boolean;
|
|
46
|
+
get pid(): number | undefined;
|
|
47
|
+
/** Forks the entry under the Node permission model: read-only access to its own dir, nothing else. */
|
|
48
|
+
start(): void;
|
|
49
|
+
call<M extends RpcMethod>(method: M, params?: Params<M>, timeoutMs?: number): Promise<Result<M>>;
|
|
50
|
+
/** Graceful stop: `shutdown` RPC, then SIGKILL if the child hasn't exited within `graceMs`. */
|
|
51
|
+
stop(graceMs?: number): Promise<void>;
|
|
52
|
+
kill(): void;
|
|
53
|
+
private onMessage;
|
|
54
|
+
}
|
|
55
|
+
export {};
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { fork } from 'node:child_process';
|
|
2
|
+
import { EventEmitter } from 'node:events';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { GuideSchema, OptionSchema, PrepareWriteResultSchema, RegistryEntrySchema, ResolvedTargetSchema, ResolveOperationResultSchema, SummarizeResultSchema, SyncCatalogResultSchema, TestConnectionResultSchema, } from '@synoikia/plugin-sdk';
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
/**
|
|
7
|
+
* One plugin child process and the core side of its JSON-RPC-over-IPC channel (design §3.3, §4.4).
|
|
8
|
+
* Every result is validated before it is returned: plugin output is untrusted.
|
|
9
|
+
*/
|
|
10
|
+
export class PluginRpcError extends Error {
|
|
11
|
+
code;
|
|
12
|
+
data;
|
|
13
|
+
constructor(code, message, data) {
|
|
14
|
+
super(message);
|
|
15
|
+
this.code = code;
|
|
16
|
+
this.data = data;
|
|
17
|
+
this.name = 'PluginRpcError';
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/** The child is not running (never started, crashed, or stopped). Callers surface `PluginUnavailable`. */
|
|
21
|
+
export class PluginUnavailableError extends Error {
|
|
22
|
+
constructor(message = 'Plugin process is not available') {
|
|
23
|
+
super(message);
|
|
24
|
+
this.name = 'PluginUnavailableError';
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export class PluginTimeoutError extends Error {
|
|
28
|
+
constructor(method, ms) {
|
|
29
|
+
super(`Plugin did not answer ${method} within ${ms} ms`);
|
|
30
|
+
this.name = 'PluginTimeoutError';
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/** The plugin answered with something that violates the contract. */
|
|
34
|
+
export class PluginProtocolError extends Error {
|
|
35
|
+
constructor(method, detail) {
|
|
36
|
+
super(`Invalid ${method} result from plugin: ${detail}`);
|
|
37
|
+
this.name = 'PluginProtocolError';
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const nullish = z.unknown().transform(() => null);
|
|
41
|
+
const RESULT_SCHEMAS = {
|
|
42
|
+
init: nullish,
|
|
43
|
+
testConnection: TestConnectionResultSchema,
|
|
44
|
+
getUpstreamVersion: z.string().min(1),
|
|
45
|
+
syncCatalog: SyncCatalogResultSchema,
|
|
46
|
+
syncRegistry: z.array(RegistryEntrySchema),
|
|
47
|
+
resolveOperation: ResolveOperationResultSchema,
|
|
48
|
+
resolveTargets: z.array(ResolvedTargetSchema),
|
|
49
|
+
summarize: SummarizeResultSchema,
|
|
50
|
+
prepareWrite: PrepareWriteResultSchema,
|
|
51
|
+
invoke: z.unknown(),
|
|
52
|
+
optionsFor: z.array(OptionSchema),
|
|
53
|
+
getGuide: GuideSchema,
|
|
54
|
+
shutdown: nullish,
|
|
55
|
+
};
|
|
56
|
+
export class PluginProcess extends EventEmitter {
|
|
57
|
+
opts;
|
|
58
|
+
child;
|
|
59
|
+
nextId = 1;
|
|
60
|
+
pending = new Map();
|
|
61
|
+
exited = false;
|
|
62
|
+
constructor(opts) {
|
|
63
|
+
super();
|
|
64
|
+
this.opts = opts;
|
|
65
|
+
}
|
|
66
|
+
get running() {
|
|
67
|
+
return !!this.child && !this.exited;
|
|
68
|
+
}
|
|
69
|
+
get pid() {
|
|
70
|
+
return this.child?.pid;
|
|
71
|
+
}
|
|
72
|
+
/** Forks the entry under the Node permission model: read-only access to its own dir, nothing else. */
|
|
73
|
+
start() {
|
|
74
|
+
if (this.child)
|
|
75
|
+
throw new Error('Plugin process already started');
|
|
76
|
+
const dir = path.resolve(this.opts.dir);
|
|
77
|
+
this.child = fork(path.resolve(dir, this.opts.entry), [], {
|
|
78
|
+
cwd: dir,
|
|
79
|
+
execArgv: ['--permission', `--allow-fs-read=${dir}`, `--max-old-space-size=${this.opts.memoryMb ?? 256}`],
|
|
80
|
+
// Scrubbed environment: no MASTER_KEY, DB paths or admin config ever reach the plugin.
|
|
81
|
+
env: { NODE_ENV: process.env.NODE_ENV ?? 'production', PLUGIN_INSTANCE_ID: this.opts.instanceId },
|
|
82
|
+
stdio: ['ignore', 'pipe', 'pipe', 'ipc'],
|
|
83
|
+
serialization: 'json',
|
|
84
|
+
});
|
|
85
|
+
this.child.stdout?.on('data', (d) => this.emit('log', 'info', d.toString().trimEnd()));
|
|
86
|
+
this.child.stderr?.on('data', (d) => this.emit('log', 'error', d.toString().trimEnd()));
|
|
87
|
+
this.child.on('message', (msg) => this.onMessage(msg));
|
|
88
|
+
this.child.on('error', (err) => this.emit('log', 'error', `plugin process error: ${err.message}`));
|
|
89
|
+
this.child.on('exit', (code, signal) => {
|
|
90
|
+
this.exited = true;
|
|
91
|
+
for (const p of this.pending.values()) {
|
|
92
|
+
clearTimeout(p.timer);
|
|
93
|
+
p.reject(new PluginUnavailableError(`Plugin exited (${signal ?? code}) during ${p.method}`));
|
|
94
|
+
}
|
|
95
|
+
this.pending.clear();
|
|
96
|
+
this.emit('exit', code, signal);
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
async call(method, params, timeoutMs) {
|
|
100
|
+
const child = this.child;
|
|
101
|
+
if (!child || this.exited || !child.connected)
|
|
102
|
+
throw new PluginUnavailableError();
|
|
103
|
+
const id = this.nextId++;
|
|
104
|
+
const ms = timeoutMs ?? this.opts.defaultTimeoutMs ?? 30_000;
|
|
105
|
+
const raw = await new Promise((resolve, reject) => {
|
|
106
|
+
const timer = setTimeout(() => {
|
|
107
|
+
this.pending.delete(id);
|
|
108
|
+
reject(new PluginTimeoutError(method, ms));
|
|
109
|
+
}, ms);
|
|
110
|
+
this.pending.set(id, { method, resolve, reject, timer });
|
|
111
|
+
child.send({ jsonrpc: '2.0', id, method, params }, (err) => {
|
|
112
|
+
if (!err)
|
|
113
|
+
return;
|
|
114
|
+
clearTimeout(timer);
|
|
115
|
+
this.pending.delete(id);
|
|
116
|
+
reject(new PluginUnavailableError(`Could not send ${method}: ${err.message}`));
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
const parsed = RESULT_SCHEMAS[method].safeParse(raw);
|
|
120
|
+
if (!parsed.success) {
|
|
121
|
+
throw new PluginProtocolError(method, parsed.error.issues.map((i) => `${i.path.join('.') || '(root)'}: ${i.message}`).join('; '));
|
|
122
|
+
}
|
|
123
|
+
return parsed.data;
|
|
124
|
+
}
|
|
125
|
+
/** Graceful stop: `shutdown` RPC, then SIGKILL if the child hasn't exited within `graceMs`. */
|
|
126
|
+
async stop(graceMs = 2000) {
|
|
127
|
+
const child = this.child;
|
|
128
|
+
if (!child || this.exited)
|
|
129
|
+
return;
|
|
130
|
+
const exited = new Promise((resolve) => child.once('exit', () => resolve()));
|
|
131
|
+
this.call('shutdown', undefined, graceMs).catch(() => undefined);
|
|
132
|
+
const killTimer = setTimeout(() => child.kill('SIGKILL'), graceMs);
|
|
133
|
+
await exited;
|
|
134
|
+
clearTimeout(killTimer);
|
|
135
|
+
}
|
|
136
|
+
kill() {
|
|
137
|
+
this.child?.kill('SIGKILL');
|
|
138
|
+
}
|
|
139
|
+
onMessage(msg) {
|
|
140
|
+
if (typeof msg !== 'object' || msg === null || msg.jsonrpc !== '2.0')
|
|
141
|
+
return;
|
|
142
|
+
if ('id' in msg && typeof msg.id === 'number') {
|
|
143
|
+
const pending = this.pending.get(msg.id);
|
|
144
|
+
if (!pending)
|
|
145
|
+
return;
|
|
146
|
+
this.pending.delete(msg.id);
|
|
147
|
+
clearTimeout(pending.timer);
|
|
148
|
+
const res = msg;
|
|
149
|
+
if ('error' in res)
|
|
150
|
+
pending.reject(new PluginRpcError(res.error.code, res.error.message, res.error.data));
|
|
151
|
+
else
|
|
152
|
+
pending.resolve(res.result);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
const note = msg;
|
|
156
|
+
if (note.method === 'log')
|
|
157
|
+
this.emit('log', note.params.level, note.params.message);
|
|
158
|
+
else if (note.method === 'catalogChanged')
|
|
159
|
+
this.emit('catalogChanged', note.params?.reason);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
//# sourceMappingURL=process.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"process.js","sourceRoot":"","sources":["../../src/plugins/process.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { Db } from '../db/index.js';
|
|
3
|
+
export declare const IndexSchema: z.ZodObject<{
|
|
4
|
+
schema: z.ZodLiteral<1>;
|
|
5
|
+
name: z.ZodString;
|
|
6
|
+
homepage: z.ZodOptional<z.ZodString>;
|
|
7
|
+
publicKey: z.ZodOptional<z.ZodString>;
|
|
8
|
+
plugins: z.ZodArray<z.ZodObject<{
|
|
9
|
+
id: z.ZodString;
|
|
10
|
+
name: z.ZodString;
|
|
11
|
+
description: z.ZodOptional<z.ZodString>;
|
|
12
|
+
versions: z.ZodArray<z.ZodObject<{
|
|
13
|
+
version: z.ZodString;
|
|
14
|
+
sdk: z.ZodString;
|
|
15
|
+
minCoreVersion: z.ZodOptional<z.ZodString>;
|
|
16
|
+
url: z.ZodString;
|
|
17
|
+
sha256: z.ZodString;
|
|
18
|
+
signature: z.ZodOptional<z.ZodString>;
|
|
19
|
+
}, z.core.$strip>>;
|
|
20
|
+
}, z.core.$strip>>;
|
|
21
|
+
}, z.core.$strip>;
|
|
22
|
+
export type RepoIndex = z.infer<typeof IndexSchema>;
|
|
23
|
+
export declare const AddRepoSchema: z.ZodObject<{
|
|
24
|
+
url: z.ZodURL;
|
|
25
|
+
signingMode: z.ZodEnum<{
|
|
26
|
+
signed: "signed";
|
|
27
|
+
unsigned: "unsigned";
|
|
28
|
+
}>;
|
|
29
|
+
confirmPublicKey: z.ZodOptional<z.ZodString>;
|
|
30
|
+
}, z.core.$strip>;
|
|
31
|
+
export declare const InstallSchema: z.ZodObject<{
|
|
32
|
+
repoId: z.ZodString;
|
|
33
|
+
pluginId: z.ZodString;
|
|
34
|
+
version: z.ZodString;
|
|
35
|
+
confirm: z.ZodOptional<z.ZodString>;
|
|
36
|
+
}, z.core.$strip>;
|
|
37
|
+
export type FetchBytes = (url: string, init?: {
|
|
38
|
+
signal?: AbortSignal;
|
|
39
|
+
redirect?: 'manual';
|
|
40
|
+
}) => Promise<Response>;
|
|
41
|
+
type Actor = {
|
|
42
|
+
userId?: string;
|
|
43
|
+
};
|
|
44
|
+
export interface RepoServiceOptions {
|
|
45
|
+
db: Db;
|
|
46
|
+
dataDir: string;
|
|
47
|
+
fetch?: FetchBytes;
|
|
48
|
+
/** Only for tests and lab mirrors: allow http:// index and tarball URLs. */
|
|
49
|
+
allowHttp?: boolean;
|
|
50
|
+
now?: () => Date;
|
|
51
|
+
/** Re-scan plugin directories into the registry. */
|
|
52
|
+
discover: () => unknown;
|
|
53
|
+
/** Stop every instance of a plugin row before its files change. */
|
|
54
|
+
stopPlugin: (pluginRowId: string) => Promise<void>;
|
|
55
|
+
/** Start (and resync) every servable instance of a plugin row. */
|
|
56
|
+
startPlugin: (pluginRowId: string) => Promise<void>;
|
|
57
|
+
}
|
|
58
|
+
/** The parts of a manifest that change what a plugin may do; an update touching them needs review. */
|
|
59
|
+
export declare function permissionChanges(before: unknown, after: unknown): string[];
|
|
60
|
+
/**
|
|
61
|
+
* Replaces `target` with `next`, keeping the previous copy at `backup` until the swap succeeded. If
|
|
62
|
+
* moving `next` in fails, the previous copy is moved back, so a failed update never leaves no plugin.
|
|
63
|
+
*/
|
|
64
|
+
export declare function swapDirectory(next: string, target: string, backup: string, rename?: (from: string, to: string) => void): void;
|
|
65
|
+
export declare class PluginRepoService {
|
|
66
|
+
private readonly opts;
|
|
67
|
+
private readonly installing;
|
|
68
|
+
constructor(opts: RepoServiceOptions);
|
|
69
|
+
private get db();
|
|
70
|
+
private now;
|
|
71
|
+
private checkUrl;
|
|
72
|
+
private download;
|
|
73
|
+
private fetchIndex;
|
|
74
|
+
private keyOf;
|
|
75
|
+
private repoRow;
|
|
76
|
+
private publicRepo;
|
|
77
|
+
list(): {
|
|
78
|
+
id: string;
|
|
79
|
+
url: string;
|
|
80
|
+
name: string | null;
|
|
81
|
+
signingMode: "signed" | "unsigned";
|
|
82
|
+
publicKey: string | null;
|
|
83
|
+
keyId: string | null;
|
|
84
|
+
keyStatus: "ok" | "key_changed";
|
|
85
|
+
/** When the key changed: the new key awaiting confirmation. */
|
|
86
|
+
offeredKey: {
|
|
87
|
+
publicKey: string;
|
|
88
|
+
keyId: string;
|
|
89
|
+
} | null;
|
|
90
|
+
pluginCount: number;
|
|
91
|
+
lastFetchedAt: Date | null;
|
|
92
|
+
lastFetchError: string | null;
|
|
93
|
+
}[];
|
|
94
|
+
/**
|
|
95
|
+
* Adds a repository. Signed repos take two calls: the first answers 409 `confirm_key` with the key
|
|
96
|
+
* id, the second repeats the request with `confirmKeyId` once the admin has checked it out of band.
|
|
97
|
+
*/
|
|
98
|
+
add(raw: unknown, actor?: Actor): Promise<{
|
|
99
|
+
id: string;
|
|
100
|
+
url: string;
|
|
101
|
+
name: string | null;
|
|
102
|
+
signingMode: "signed" | "unsigned";
|
|
103
|
+
publicKey: string | null;
|
|
104
|
+
keyId: string | null;
|
|
105
|
+
keyStatus: "ok" | "key_changed";
|
|
106
|
+
/** When the key changed: the new key awaiting confirmation. */
|
|
107
|
+
offeredKey: {
|
|
108
|
+
publicKey: string;
|
|
109
|
+
keyId: string;
|
|
110
|
+
} | null;
|
|
111
|
+
pluginCount: number;
|
|
112
|
+
lastFetchedAt: Date | null;
|
|
113
|
+
lastFetchError: string | null;
|
|
114
|
+
}>;
|
|
115
|
+
/** Re-reads the index. A changed signing key blocks installs until re-confirmed. */
|
|
116
|
+
refresh(id: string): Promise<{
|
|
117
|
+
id: string;
|
|
118
|
+
url: string;
|
|
119
|
+
name: string | null;
|
|
120
|
+
signingMode: "signed" | "unsigned";
|
|
121
|
+
publicKey: string | null;
|
|
122
|
+
keyId: string | null;
|
|
123
|
+
keyStatus: "ok" | "key_changed";
|
|
124
|
+
/** When the key changed: the new key awaiting confirmation. */
|
|
125
|
+
offeredKey: {
|
|
126
|
+
publicKey: string;
|
|
127
|
+
keyId: string;
|
|
128
|
+
} | null;
|
|
129
|
+
pluginCount: number;
|
|
130
|
+
lastFetchedAt: Date | null;
|
|
131
|
+
lastFetchError: string | null;
|
|
132
|
+
}>;
|
|
133
|
+
/** Daily refresh (design §10). Failures are recorded on the row, never thrown. */
|
|
134
|
+
refreshStale(maxAgeMs?: number): Promise<void>;
|
|
135
|
+
/** Pins the key a refreshed index now offers, after the admin checked it against the publisher's. */
|
|
136
|
+
confirmKey(id: string, publicKey: string, actor?: Actor): {
|
|
137
|
+
id: string;
|
|
138
|
+
url: string;
|
|
139
|
+
name: string | null;
|
|
140
|
+
signingMode: "signed" | "unsigned";
|
|
141
|
+
publicKey: string | null;
|
|
142
|
+
keyId: string | null;
|
|
143
|
+
keyStatus: "ok" | "key_changed";
|
|
144
|
+
/** When the key changed: the new key awaiting confirmation. */
|
|
145
|
+
offeredKey: {
|
|
146
|
+
publicKey: string;
|
|
147
|
+
keyId: string;
|
|
148
|
+
} | null;
|
|
149
|
+
pluginCount: number;
|
|
150
|
+
lastFetchedAt: Date | null;
|
|
151
|
+
lastFetchError: string | null;
|
|
152
|
+
};
|
|
153
|
+
remove(id: string, actor?: Actor): void;
|
|
154
|
+
/** Every plugin offered by every repo, with install/update state. */
|
|
155
|
+
available(): {
|
|
156
|
+
repoId: string;
|
|
157
|
+
repoName: string | null;
|
|
158
|
+
signingMode: "signed" | "unsigned";
|
|
159
|
+
pluginId: string;
|
|
160
|
+
name: string;
|
|
161
|
+
description: string | null;
|
|
162
|
+
versions: {
|
|
163
|
+
version: string;
|
|
164
|
+
compatible: boolean;
|
|
165
|
+
}[];
|
|
166
|
+
latest: string | null;
|
|
167
|
+
installed: {
|
|
168
|
+
version: string;
|
|
169
|
+
fromThisRepo: boolean;
|
|
170
|
+
source: "core" | "repo";
|
|
171
|
+
} | null;
|
|
172
|
+
updateAvailable: boolean;
|
|
173
|
+
blocked: string | null;
|
|
174
|
+
}[];
|
|
175
|
+
private get pluginsDir();
|
|
176
|
+
private get stagingDir();
|
|
177
|
+
/** Extracts regular files and directories only; links, devices and escaping paths fail the install. */
|
|
178
|
+
private extract;
|
|
179
|
+
install(raw: unknown, actor?: Actor): Promise<{
|
|
180
|
+
id: string;
|
|
181
|
+
pluginId: string;
|
|
182
|
+
version: string;
|
|
183
|
+
source: "core" | "repo";
|
|
184
|
+
repoId: string | null;
|
|
185
|
+
path: string;
|
|
186
|
+
sha256: string | null;
|
|
187
|
+
signatureVerified: boolean;
|
|
188
|
+
manifest: unknown;
|
|
189
|
+
status: "ok" | "invalid" | "incompatible";
|
|
190
|
+
statusError: string | null;
|
|
191
|
+
enabled: boolean;
|
|
192
|
+
installedAt: Date;
|
|
193
|
+
}>;
|
|
194
|
+
private installLocked;
|
|
195
|
+
/** Removes an installed (repo) plugin. Blocked while any instance uses it. */
|
|
196
|
+
uninstall(pluginRowOrId: string, actor?: Actor): void;
|
|
197
|
+
}
|
|
198
|
+
export {};
|