@aopslabs/aops 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +19 -0
- package/NOTICE +7 -0
- package/README.md +203 -0
- package/assets/agent-assets/core/SKILL.md +49 -0
- package/assets/agent-assets/core/references/agentspace/SKILL.md +78 -0
- package/assets/agent-assets/core/references/aops-cli-core/SKILL.md +108 -0
- package/assets/agent-assets/core/references/chatv3/SKILL.md +48 -0
- package/assets/agent-assets/core/references/collaborative-work/SKILL.md +85 -0
- package/assets/agent-assets/core/references/discuss/SKILL.md +66 -0
- package/assets/agent-assets/core/references/docman/SKILL.md +69 -0
- package/assets/agent-assets/core/references/hosted-chat/SKILL.md +64 -0
- package/assets/agent-assets/core/references/mission/SKILL.md +54 -0
- package/assets/agent-assets/core/references/projectman/SKILL.md +86 -0
- package/assets/agent-assets/core/references/sys/SKILL.md +36 -0
- package/assets/agent-assets/core/references/view/SKILL.md +58 -0
- package/assets/agent-assets/core/references/working-disciplines/SKILL.md +69 -0
- package/assets/agent-assets/core/user-guides/agent-assets.md +81 -0
- package/assets/agent-assets/core/user-guides/agentspace.md +1098 -0
- package/assets/agent-assets/core/user-guides/aops-cli.md +1107 -0
- package/assets/agent-assets/core/user-guides/aops-system.md +63 -0
- package/assets/agent-assets/core/user-guides/chatv3.md +113 -0
- package/assets/agent-assets/core/user-guides/docman.md +162 -0
- package/assets/agent-assets/core/user-guides/projectman.md +212 -0
- package/assets/agent-assets/core/user-guides/sys.md +94 -0
- package/assets/agent-assets/core/user-guides/working-disciplines.md +588 -0
- package/assets/agent-assets/gateway/aops/SKILL.md +14 -0
- package/assets/r6-remote-session-baseline.json +19 -0
- package/assets/skills/aops-install/SKILL.md +231 -0
- package/assets/skills/aops-install/agents/openai.yaml +4 -0
- package/dist/commands/activity.js +352 -0
- package/dist/commands/agent-profile.js +653 -0
- package/dist/commands/agent.js +710 -0
- package/dist/commands/agents-md.js +125 -0
- package/dist/commands/api.js +84 -0
- package/dist/commands/archive.js +873 -0
- package/dist/commands/artifact.js +535 -0
- package/dist/commands/assets.js +1414 -0
- package/dist/commands/auth/device-login.js +1 -0
- package/dist/commands/auth/legacy-export.js +177 -0
- package/dist/commands/auth/login.js +426 -0
- package/dist/commands/chat.js +1363 -0
- package/dist/commands/chatv3.js +1684 -0
- package/dist/commands/checkpoint.js +549 -0
- package/dist/commands/commercial-artifact.js +475 -0
- package/dist/commands/commercial-license.js +594 -0
- package/dist/commands/community-auth.js +62 -0
- package/dist/commands/community-cockpit.js +185 -0
- package/dist/commands/community-console.js +109 -0
- package/dist/commands/community-doctor.js +335 -0
- package/dist/commands/community-server.js +2062 -0
- package/dist/commands/community-setup.js +284 -0
- package/dist/commands/discuss.js +1448 -0
- package/dist/commands/doc.js +5219 -0
- package/dist/commands/experience.js +922 -0
- package/dist/commands/host.js +1127 -0
- package/dist/commands/init.js +118 -0
- package/dist/commands/memory.js +3129 -0
- package/dist/commands/mission.js +562 -0
- package/dist/commands/official-catalog.js +134 -0
- package/dist/commands/plan.js +84 -0
- package/dist/commands/playbook.js +415 -0
- package/dist/commands/pm/context.js +6 -0
- package/dist/commands/pm/index.js +701 -0
- package/dist/commands/pm/projectman.js +6266 -0
- package/dist/commands/project.js +1080 -0
- package/dist/commands/prompt.js +1024 -0
- package/dist/commands/repo-sync.js +1907 -0
- package/dist/commands/resource.js +527 -0
- package/dist/commands/skill.js +1211 -0
- package/dist/commands/start-disciplines.js +510 -0
- package/dist/commands/start.js +1343 -0
- package/dist/commands/target.js +309 -0
- package/dist/commands/version.js +42 -0
- package/dist/commands/view.js +1852 -0
- package/dist/launchers/cockpit.js +28 -0
- package/dist/launchers/options.js +74 -0
- package/dist/launchers/server.js +41 -0
- package/dist/lib/agent-assets/envelope.js +83 -0
- package/dist/lib/agent-assets/gateway.js +15 -0
- package/dist/lib/agent-assets/guards.js +23 -0
- package/dist/lib/agent-assets/hosted-discovery.js +148 -0
- package/dist/lib/agent-assets/hosted-package-input.js +154 -0
- package/dist/lib/agent-assets/legacy-pointer-migration.js +677 -0
- package/dist/lib/agent-assets/native-fs.js +589 -0
- package/dist/lib/agent-assets/package-manifest.js +324 -0
- package/dist/lib/agent-assets/portable-path.js +67 -0
- package/dist/lib/agent-assets/release-input.js +177 -0
- package/dist/lib/agent-assets/roots.js +45 -0
- package/dist/lib/agent-assets/runtime-binding-reader.js +545 -0
- package/dist/lib/agent-assets/runtime-targets.js +50 -0
- package/dist/lib/agent-assets/store-reader.js +1212 -0
- package/dist/lib/agent-assets/store-types.js +1 -0
- package/dist/lib/agent-assets/store-writer.js +1484 -0
- package/dist/lib/agent-assets/types.js +1 -0
- package/dist/lib/agent-assets/unicode-case-folding-v15-1.generated.js +1548 -0
- package/dist/lib/commercial-final-readiness.js +504 -0
- package/dist/lib/commercial-lifecycle-smoke-isolation.js +236 -0
- package/dist/lib/commercial-managed-postgres-docker-adapter.js +274 -0
- package/dist/lib/commercial-setup-readiness-adapter.js +179 -0
- package/dist/lib/community-client-contract.js +164 -0
- package/dist/lib/community-cockpit-lifecycle.js +601 -0
- package/dist/lib/community-command-abort.js +59 -0
- package/dist/lib/community-diagnostic.js +237 -0
- package/dist/lib/community-docker-adapter.js +498 -0
- package/dist/lib/community-home.js +31 -0
- package/dist/lib/community-instance-contract.js +148 -0
- package/dist/lib/community-instance-layout.js +97 -0
- package/dist/lib/community-lifecycle.js +1631 -0
- package/dist/lib/community-migration-snapshot.js +770 -0
- package/dist/lib/community-native-application-recovery.js +425 -0
- package/dist/lib/community-native-child.js +240 -0
- package/dist/lib/community-native-database-recovery.js +456 -0
- package/dist/lib/community-native-lifecycle.js +2458 -0
- package/dist/lib/community-native-migration.js +339 -0
- package/dist/lib/community-native-postgres.js +596 -0
- package/dist/lib/community-operation-journal.js +773 -0
- package/dist/lib/community-operation-lock.js +596 -0
- package/dist/lib/community-release-resolver.js +291 -0
- package/dist/lib/community-release-verifier.js +475 -0
- package/dist/lib/community-repo-discovery.js +252 -0
- package/dist/lib/community-setup-server-env.js +203 -0
- package/dist/lib/external-url.js +110 -0
- package/dist/lib/official-catalog-gateway.js +108 -0
- package/dist/lib/official-catalog.js +364 -0
- package/dist/lib/server-bootstrap.js +240 -0
- package/dist/lib/setup-agent-assets-bridge.js +176 -0
- package/dist/lib/setup-agent-assets-release.js +52 -0
- package/dist/lib/setup-external-postgres.js +108 -0
- package/dist/lib/setup-init-orchestrator.js +990 -0
- package/dist/lib/setup-install-guide.js +52 -0
- package/dist/lib/setup-installer-release.js +47 -0
- package/dist/lib/setup-local-postgres.js +329 -0
- package/dist/lib/setup-official-catalog-bridge.js +148 -0
- package/dist/lib/setup-readiness.js +888 -0
- package/dist/lib/tui-launcher.js +59 -0
- package/dist/main.js +150 -0
- package/dist/seeds/agents-md.js +54 -0
- package/dist/utils/agent-gateway.js +275 -0
- package/dist/utils/agents-md.js +281 -0
- package/dist/utils/api.js +255 -0
- package/dist/utils/chatv3-session-store.js +185 -0
- package/dist/utils/command.js +67 -0
- package/dist/utils/community-host-runtime.js +376 -0
- package/dist/utils/config.js +1015 -0
- package/dist/utils/discussion-workspace.js +552 -0
- package/dist/utils/experience-workspace.js +326 -0
- package/dist/utils/guide-paths.js +67 -0
- package/dist/utils/hosted-sugar.js +56 -0
- package/dist/utils/hosted-workspace.js +229 -0
- package/dist/utils/memory-workspace.js +537 -0
- package/dist/utils/operator-runtime.js +199 -0
- package/dist/utils/password-crypto.js +39 -0
- package/dist/utils/playbook-workspace.js +479 -0
- package/dist/utils/project-context.js +255 -0
- package/dist/utils/prompts.js +70 -0
- package/dist/utils/refresh-race.js +28 -0
- package/dist/utils/repo-config.js +109 -0
- package/dist/utils/repo-first-projectman.js +194 -0
- package/dist/utils/repo-first-storage.js +13 -0
- package/dist/utils/repo-first-view.js +305 -0
- package/dist/utils/session-guidance.js +278 -0
- package/dist/utils/session-state.js +137 -0
- package/dist/utils/table.js +39 -0
- package/dist/utils/transient-fs.js +49 -0
- package/dist/utils/workspace-root.js +1 -0
- package/launchers/aops-cockpit.ps1 +15 -0
- package/launchers/aops-cockpit.sh +16 -0
- package/launchers/aops-server.ps1 +15 -0
- package/launchers/aops-server.sh +16 -0
- package/native/tui/win32-x64/aops-tui.exe +0 -0
- package/package.json +81 -0
|
@@ -0,0 +1,498 @@
|
|
|
1
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
2
|
+
import { closeSync, constants, existsSync, fstatSync, fsyncSync, lstatSync, mkdirSync, openSync, readSync, realpathSync, writeSync, } from 'node:fs';
|
|
3
|
+
import { spawn } from 'node:child_process';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { buildCommunityComposeBaseInvocation, buildCommunityComposeInvocation, assertCommunityPostgresVolumeName, readCommunityRuntimePort, } from './community-lifecycle.js';
|
|
6
|
+
export class CommunityProcessAbortedError extends Error {
|
|
7
|
+
code = 'COMMUNITY_OPERATION_ABORTED';
|
|
8
|
+
constructor() {
|
|
9
|
+
super('community_operation_aborted');
|
|
10
|
+
this.name = 'CommunityProcessAbortedError';
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
function throwIfAborted(signal) {
|
|
14
|
+
if (signal?.aborted)
|
|
15
|
+
throw new CommunityProcessAbortedError();
|
|
16
|
+
}
|
|
17
|
+
function appendBounded(current, chunk, maxBytes = 1_048_576) {
|
|
18
|
+
const next = current + chunk.toString('utf8');
|
|
19
|
+
return next.length > maxBytes ? next.slice(-maxBytes) : next;
|
|
20
|
+
}
|
|
21
|
+
export const communityProcessRuntime = {
|
|
22
|
+
run(invocation) {
|
|
23
|
+
return new Promise((resolve, reject) => {
|
|
24
|
+
let preparedInputFd;
|
|
25
|
+
let preparedOutputFd;
|
|
26
|
+
let ownsInputFd = false;
|
|
27
|
+
let ownsOutputFd = false;
|
|
28
|
+
try {
|
|
29
|
+
if (invocation.inputPath && invocation.inputFd !== undefined)
|
|
30
|
+
throw new Error('community_process_multiple_inputs_refused');
|
|
31
|
+
if (invocation.outputPath && invocation.outputFd !== undefined)
|
|
32
|
+
throw new Error('community_process_multiple_outputs_refused');
|
|
33
|
+
throwIfAborted(invocation.signal);
|
|
34
|
+
if (invocation.inputFd !== undefined) {
|
|
35
|
+
if (!Number.isInteger(invocation.inputFd) || invocation.inputFd < 0)
|
|
36
|
+
throw new Error('community_process_input_fd_invalid');
|
|
37
|
+
const stat = fstatSync(invocation.inputFd, { bigint: true });
|
|
38
|
+
if (!stat.isFile() || stat.nlink !== 1n)
|
|
39
|
+
throw new Error('community_process_input_fd_unsafe');
|
|
40
|
+
preparedInputFd = invocation.inputFd;
|
|
41
|
+
}
|
|
42
|
+
else if (invocation.inputPath) {
|
|
43
|
+
const noFollow = typeof constants.O_NOFOLLOW === 'number' ? constants.O_NOFOLLOW : 0;
|
|
44
|
+
preparedInputFd = openSync(invocation.inputPath, constants.O_RDONLY | noFollow);
|
|
45
|
+
ownsInputFd = true;
|
|
46
|
+
const stat = fstatSync(preparedInputFd, { bigint: true });
|
|
47
|
+
if (!stat.isFile() || stat.nlink !== 1n)
|
|
48
|
+
throw new Error('community_process_input_path_unsafe');
|
|
49
|
+
}
|
|
50
|
+
if (invocation.outputFd !== undefined) {
|
|
51
|
+
if (!Number.isInteger(invocation.outputFd) || invocation.outputFd < 0)
|
|
52
|
+
throw new Error('community_process_output_fd_invalid');
|
|
53
|
+
const stat = fstatSync(invocation.outputFd, { bigint: true });
|
|
54
|
+
if (!stat.isFile() || stat.nlink !== 1n || stat.size !== 0n)
|
|
55
|
+
throw new Error('community_process_output_fd_unsafe');
|
|
56
|
+
preparedOutputFd = invocation.outputFd;
|
|
57
|
+
}
|
|
58
|
+
else if (invocation.outputPath) {
|
|
59
|
+
if (existsSync(invocation.outputPath))
|
|
60
|
+
throw new Error('community_process_output_already_exists');
|
|
61
|
+
mkdirSync(path.dirname(invocation.outputPath), { recursive: true });
|
|
62
|
+
const noFollow = typeof constants.O_NOFOLLOW === 'number' ? constants.O_NOFOLLOW : 0;
|
|
63
|
+
preparedOutputFd = openSync(invocation.outputPath, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY | noFollow, 0o600);
|
|
64
|
+
ownsOutputFd = true;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
if (ownsInputFd && preparedInputFd !== undefined)
|
|
69
|
+
closeSync(preparedInputFd);
|
|
70
|
+
if (ownsOutputFd && preparedOutputFd !== undefined)
|
|
71
|
+
closeSync(preparedOutputFd);
|
|
72
|
+
reject(error);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
let child;
|
|
76
|
+
try {
|
|
77
|
+
child = spawn(invocation.command, invocation.args, {
|
|
78
|
+
env: { ...process.env, ...invocation.env },
|
|
79
|
+
shell: false,
|
|
80
|
+
windowsHide: true,
|
|
81
|
+
stdio: [preparedInputFd ?? 'pipe', 'pipe', 'pipe'],
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
if (ownsInputFd && preparedInputFd !== undefined)
|
|
86
|
+
closeSync(preparedInputFd);
|
|
87
|
+
if (ownsOutputFd && preparedOutputFd !== undefined)
|
|
88
|
+
closeSync(preparedOutputFd);
|
|
89
|
+
reject(error);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (ownsInputFd && preparedInputFd !== undefined) {
|
|
93
|
+
closeSync(preparedInputFd);
|
|
94
|
+
ownsInputFd = false;
|
|
95
|
+
}
|
|
96
|
+
let stdout = '';
|
|
97
|
+
let stderr = '';
|
|
98
|
+
let outputOffset = 0;
|
|
99
|
+
let settled = false;
|
|
100
|
+
let childClosed = false;
|
|
101
|
+
let terminalError;
|
|
102
|
+
let hardKillTimer;
|
|
103
|
+
let finalKillTimer;
|
|
104
|
+
let exitCode = null;
|
|
105
|
+
let exitSignal = null;
|
|
106
|
+
const cleanup = () => {
|
|
107
|
+
invocation.signal?.removeEventListener('abort', onAbort);
|
|
108
|
+
if (hardKillTimer)
|
|
109
|
+
clearTimeout(hardKillTimer);
|
|
110
|
+
if (finalKillTimer)
|
|
111
|
+
clearTimeout(finalKillTimer);
|
|
112
|
+
if (ownsOutputFd && preparedOutputFd !== undefined) {
|
|
113
|
+
closeSync(preparedOutputFd);
|
|
114
|
+
ownsOutputFd = false;
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
const finish = () => {
|
|
118
|
+
if (settled || !childClosed)
|
|
119
|
+
return;
|
|
120
|
+
settled = true;
|
|
121
|
+
cleanup();
|
|
122
|
+
if (terminalError)
|
|
123
|
+
reject(terminalError);
|
|
124
|
+
else
|
|
125
|
+
resolve({ exitCode, signal: exitSignal, stdout, stderr });
|
|
126
|
+
};
|
|
127
|
+
const safeKill = (signal) => {
|
|
128
|
+
try {
|
|
129
|
+
return child.kill(signal);
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
const terminate = (error) => {
|
|
136
|
+
if (settled)
|
|
137
|
+
return;
|
|
138
|
+
terminalError ??= error;
|
|
139
|
+
child.stdin?.destroy();
|
|
140
|
+
child.stdout?.destroy();
|
|
141
|
+
child.stderr?.destroy();
|
|
142
|
+
if (!childClosed) {
|
|
143
|
+
safeKill('SIGTERM');
|
|
144
|
+
hardKillTimer ??= setTimeout(() => {
|
|
145
|
+
if (!childClosed)
|
|
146
|
+
safeKill('SIGKILL');
|
|
147
|
+
}, 500);
|
|
148
|
+
hardKillTimer.unref();
|
|
149
|
+
finalKillTimer ??= setTimeout(() => {
|
|
150
|
+
if (!childClosed && !settled) {
|
|
151
|
+
safeKill('SIGKILL');
|
|
152
|
+
settled = true;
|
|
153
|
+
cleanup();
|
|
154
|
+
reject(new Error('community_process_termination_unconfirmed:child_may_still_run=true'));
|
|
155
|
+
}
|
|
156
|
+
}, 3_000);
|
|
157
|
+
finalKillTimer.unref();
|
|
158
|
+
}
|
|
159
|
+
finish();
|
|
160
|
+
};
|
|
161
|
+
const onAbort = () => terminate(new CommunityProcessAbortedError());
|
|
162
|
+
child.once('error', (error) => {
|
|
163
|
+
terminalError ??= error;
|
|
164
|
+
if (!child.pid) {
|
|
165
|
+
childClosed = true;
|
|
166
|
+
finish();
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
child.stderr?.on('data', (chunk) => { stderr = appendBounded(stderr, chunk); });
|
|
170
|
+
child.stdout?.on('data', (chunk) => {
|
|
171
|
+
if (settled)
|
|
172
|
+
return;
|
|
173
|
+
if (preparedOutputFd === undefined) {
|
|
174
|
+
stdout = appendBounded(stdout, chunk);
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
try {
|
|
178
|
+
let written = 0;
|
|
179
|
+
while (written < chunk.byteLength) {
|
|
180
|
+
const count = writeSync(preparedOutputFd, chunk, written, chunk.byteLength - written, outputOffset + written);
|
|
181
|
+
if (count <= 0)
|
|
182
|
+
throw new Error('community_process_output_short_write');
|
|
183
|
+
written += count;
|
|
184
|
+
}
|
|
185
|
+
outputOffset += written;
|
|
186
|
+
}
|
|
187
|
+
catch (error) {
|
|
188
|
+
terminate(error);
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
if (preparedInputFd === undefined) {
|
|
192
|
+
child.stdin?.on('error', (error) => {
|
|
193
|
+
if (error.code !== 'EPIPE')
|
|
194
|
+
terminate(error);
|
|
195
|
+
});
|
|
196
|
+
child.stdin?.end();
|
|
197
|
+
}
|
|
198
|
+
child.once('close', (code, signal) => {
|
|
199
|
+
childClosed = true;
|
|
200
|
+
exitCode = code;
|
|
201
|
+
exitSignal = signal;
|
|
202
|
+
finish();
|
|
203
|
+
});
|
|
204
|
+
invocation.signal?.addEventListener('abort', onAbort, { once: true });
|
|
205
|
+
if (invocation.signal?.aborted)
|
|
206
|
+
onAbort();
|
|
207
|
+
});
|
|
208
|
+
},
|
|
209
|
+
};
|
|
210
|
+
function composePrefix(params) {
|
|
211
|
+
return buildCommunityComposeBaseInvocation(params);
|
|
212
|
+
}
|
|
213
|
+
async function runChecked(runtime, invocation) {
|
|
214
|
+
let result;
|
|
215
|
+
try {
|
|
216
|
+
throwIfAborted(invocation.signal);
|
|
217
|
+
result = await runtime.run(invocation);
|
|
218
|
+
throwIfAborted(invocation.signal);
|
|
219
|
+
}
|
|
220
|
+
catch (error) {
|
|
221
|
+
if (error instanceof CommunityProcessAbortedError || invocation.signal?.aborted) {
|
|
222
|
+
throw new CommunityProcessAbortedError();
|
|
223
|
+
}
|
|
224
|
+
throw new Error(`community_process_execution_failed:${redactDiagnostic(error instanceof Error ? error.message : String(error))}`);
|
|
225
|
+
}
|
|
226
|
+
if (result.exitCode !== 0) {
|
|
227
|
+
throw new Error(`community_process_failed:${redactDiagnostic(invocation.args.at(-1) ?? 'unknown')}:${result.exitCode}:${redactDiagnostic(result.stderr)}`);
|
|
228
|
+
}
|
|
229
|
+
return result;
|
|
230
|
+
}
|
|
231
|
+
function redactDiagnostic(value) {
|
|
232
|
+
return value
|
|
233
|
+
.replace(/(postgres(?:ql)?:\/\/[^:\s/@]+:)[^@\s/]+@/gi, '$1[REDACTED]@')
|
|
234
|
+
.replace(/((?:password|passwd|secret|token|api[_-]?key|authorization)\s*[=:]\s*)[^\s,;]+/gi, '$1[REDACTED]')
|
|
235
|
+
.replace(/(--(?:password|passwd|secret|token|api[_-]?key)\s+)[^\s]+/gi, '$1[REDACTED]')
|
|
236
|
+
.replace(/(\bBearer\s+)[A-Za-z0-9._~+/-]+/gi, '$1[REDACTED]')
|
|
237
|
+
.replace(/("(?:password|passwd|secret|token|apiKey|authorization)"\s*:\s*")[^"]*(")/gi, '$1[REDACTED]$2')
|
|
238
|
+
.trim()
|
|
239
|
+
.slice(-1000);
|
|
240
|
+
}
|
|
241
|
+
const VOLUME_LABELS = {
|
|
242
|
+
managed: 'com.aops.community.managed',
|
|
243
|
+
installId: 'com.aops.community.install-id',
|
|
244
|
+
operationId: 'com.aops.community.operation-id',
|
|
245
|
+
claimTokenSha256: 'com.aops.community.claim-token-sha256',
|
|
246
|
+
};
|
|
247
|
+
function expectedVolumeLabels(claim) {
|
|
248
|
+
return {
|
|
249
|
+
[VOLUME_LABELS.managed]: 'true',
|
|
250
|
+
[VOLUME_LABELS.installId]: claim.installId,
|
|
251
|
+
[VOLUME_LABELS.operationId]: claim.operationId,
|
|
252
|
+
[VOLUME_LABELS.claimTokenSha256]: claim.claimTokenSha256,
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
function assertClaim(state, claim) {
|
|
256
|
+
if (claim.schemaVersion !== 1 || claim.installId !== state.installId ||
|
|
257
|
+
!/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/.test(claim.operationId) ||
|
|
258
|
+
!/^sha256:[a-f0-9]{64}$/.test(claim.claimTokenSha256)) {
|
|
259
|
+
throw new Error('community_postgres_volume_claim_invalid');
|
|
260
|
+
}
|
|
261
|
+
assertCommunityPostgresVolumeName(state, claim.name);
|
|
262
|
+
}
|
|
263
|
+
async function inspectClaimedVolume(runtime, state, claim, signal) {
|
|
264
|
+
assertClaim(state, claim);
|
|
265
|
+
const result = await runChecked(runtime, {
|
|
266
|
+
command: 'docker',
|
|
267
|
+
args: ['volume', 'inspect', '--format', '{{json .}}', claim.name],
|
|
268
|
+
env: {},
|
|
269
|
+
signal,
|
|
270
|
+
});
|
|
271
|
+
let inspected;
|
|
272
|
+
try {
|
|
273
|
+
inspected = JSON.parse(result.stdout.trim());
|
|
274
|
+
}
|
|
275
|
+
catch (error) {
|
|
276
|
+
throw new Error('community_postgres_volume_inspect_invalid', { cause: error });
|
|
277
|
+
}
|
|
278
|
+
const labels = inspected.Labels;
|
|
279
|
+
const expected = expectedVolumeLabels(claim);
|
|
280
|
+
if (inspected.Name !== claim.name || inspected.Driver !== 'local' ||
|
|
281
|
+
!labels || typeof labels !== 'object' || Array.isArray(labels) ||
|
|
282
|
+
JSON.stringify(Object.keys(labels).sort()) !== JSON.stringify(Object.keys(expected).sort()) ||
|
|
283
|
+
Object.entries(expected).some(([key, value]) => labels[key] !== value)) {
|
|
284
|
+
throw new Error('community_postgres_volume_ownership_mismatch');
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
function sameIdentity(left, right) {
|
|
288
|
+
return left.dev === right.dev && left.ino === right.ino;
|
|
289
|
+
}
|
|
290
|
+
function assertHeldBackupIdentity(backupPath, backupRoot, fd, original, rootOriginal) {
|
|
291
|
+
const root = path.resolve(backupRoot);
|
|
292
|
+
const rootStat = lstatSync(root, { bigint: true });
|
|
293
|
+
const visible = lstatSync(backupPath, { bigint: true });
|
|
294
|
+
const held = fstatSync(fd, { bigint: true });
|
|
295
|
+
if (rootStat.isSymbolicLink() || !rootStat.isDirectory() || !sameIdentity(rootStat, rootOriginal) ||
|
|
296
|
+
realpathSync.native(root) !== root ||
|
|
297
|
+
path.dirname(path.resolve(backupPath)) !== root || visible.isSymbolicLink() || !visible.isFile() || visible.nlink !== 1n ||
|
|
298
|
+
!held.isFile() || held.nlink !== 1n || !sameIdentity(held, original) || !sameIdentity(visible, original) ||
|
|
299
|
+
realpathSync.native(backupPath) !== path.resolve(backupPath)) {
|
|
300
|
+
throw new Error('community_backup_output_identity_changed');
|
|
301
|
+
}
|
|
302
|
+
return held;
|
|
303
|
+
}
|
|
304
|
+
function assertHeldBackupStable(fd, expected) {
|
|
305
|
+
const held = fstatSync(fd, { bigint: true });
|
|
306
|
+
if (!held.isFile() || held.nlink !== 1n || !sameIdentity(held, expected) || held.size !== expected.size ||
|
|
307
|
+
held.mtimeNs !== expected.mtimeNs || held.ctimeNs !== expected.ctimeNs) {
|
|
308
|
+
throw new Error('community_backup_output_changed_during_verification');
|
|
309
|
+
}
|
|
310
|
+
return held;
|
|
311
|
+
}
|
|
312
|
+
async function hashFileDescriptor(fd, expected, signal) {
|
|
313
|
+
const hash = createHash('sha256');
|
|
314
|
+
const expectedSize = Number(expected.size);
|
|
315
|
+
if (!Number.isSafeInteger(expectedSize) || expectedSize < 1)
|
|
316
|
+
throw new Error('community_backup_file_invalid');
|
|
317
|
+
const buffer = Buffer.alloc(64 * 1024);
|
|
318
|
+
let offset = 0;
|
|
319
|
+
while (offset < expectedSize) {
|
|
320
|
+
throwIfAborted(signal);
|
|
321
|
+
const count = readSync(fd, buffer, 0, Math.min(buffer.byteLength, expectedSize - offset), offset);
|
|
322
|
+
if (count <= 0)
|
|
323
|
+
throw new Error('community_backup_output_short_read');
|
|
324
|
+
hash.update(buffer.subarray(0, count));
|
|
325
|
+
offset += count;
|
|
326
|
+
}
|
|
327
|
+
throwIfAborted(signal);
|
|
328
|
+
const final = fstatSync(fd, { bigint: true });
|
|
329
|
+
if (!sameIdentity(final, expected) || final.size !== expected.size ||
|
|
330
|
+
final.mtimeNs !== expected.mtimeNs || final.ctimeNs !== expected.ctimeNs) {
|
|
331
|
+
throw new Error('community_backup_output_changed_during_hash');
|
|
332
|
+
}
|
|
333
|
+
return `sha256:${hash.digest('hex')}`;
|
|
334
|
+
}
|
|
335
|
+
export function createCommunityDockerAdapter(options) {
|
|
336
|
+
const runtime = options.runtime ?? communityProcessRuntime;
|
|
337
|
+
const fetchImpl = options.fetchImpl ?? fetch;
|
|
338
|
+
const now = options.now ?? (() => new Date());
|
|
339
|
+
const createId = options.createId ?? randomUUID;
|
|
340
|
+
const signal = options.signal;
|
|
341
|
+
return {
|
|
342
|
+
verifyRelease: options.verifyRelease,
|
|
343
|
+
async createBackup({ paths, state }) {
|
|
344
|
+
const backupPath = path.join(paths.backupRoot, `${now().toISOString().replace(/[:.]/g, '-')}-${createId()}.dump`);
|
|
345
|
+
const prefix = composePrefix({ paths, state });
|
|
346
|
+
const noFollow = typeof constants.O_NOFOLLOW === 'number' ? constants.O_NOFOLLOW : 0;
|
|
347
|
+
const canonicalBackupRoot = realpathSync.native(paths.backupRoot);
|
|
348
|
+
const rootOriginal = lstatSync(paths.backupRoot, { bigint: true });
|
|
349
|
+
if (rootOriginal.isSymbolicLink() || !rootOriginal.isDirectory() || canonicalBackupRoot !== path.resolve(paths.backupRoot)) {
|
|
350
|
+
throw new Error('community_backup_root_unsafe');
|
|
351
|
+
}
|
|
352
|
+
const outputFd = openSync(backupPath, constants.O_CREAT | constants.O_EXCL | constants.O_RDWR | noFollow, 0o600);
|
|
353
|
+
try {
|
|
354
|
+
const original = fstatSync(outputFd, { bigint: true });
|
|
355
|
+
const rootAfterOpen = lstatSync(paths.backupRoot, { bigint: true });
|
|
356
|
+
if (!sameIdentity(rootAfterOpen, rootOriginal) || realpathSync.native(paths.backupRoot) !== canonicalBackupRoot) {
|
|
357
|
+
throw new Error('community_backup_root_identity_changed');
|
|
358
|
+
}
|
|
359
|
+
await runChecked(runtime, {
|
|
360
|
+
...prefix,
|
|
361
|
+
args: [...prefix.args, 'exec', '-T', 'postgres', 'pg_dump',
|
|
362
|
+
'-U', 'aops', '-d', 'aops', '--format=custom', '--no-owner', '--no-privileges'],
|
|
363
|
+
outputFd,
|
|
364
|
+
signal,
|
|
365
|
+
});
|
|
366
|
+
fsyncSync(outputFd);
|
|
367
|
+
const dumped = assertHeldBackupIdentity(backupPath, paths.backupRoot, outputFd, original, rootOriginal);
|
|
368
|
+
if (dumped.size <= 0n)
|
|
369
|
+
throw new Error('community_backup_file_invalid');
|
|
370
|
+
options.afterBackupDump?.(backupPath);
|
|
371
|
+
const verificationBaseline = assertHeldBackupStable(outputFd, dumped);
|
|
372
|
+
options.afterBackupVerificationBaseline?.(backupPath, outputFd);
|
|
373
|
+
assertHeldBackupStable(outputFd, verificationBaseline);
|
|
374
|
+
await runChecked(runtime, {
|
|
375
|
+
...prefix,
|
|
376
|
+
args: [...prefix.args, 'exec', '-T', 'postgres', 'pg_restore', '--list'],
|
|
377
|
+
inputFd: outputFd,
|
|
378
|
+
signal,
|
|
379
|
+
});
|
|
380
|
+
const verified = assertHeldBackupStable(outputFd, verificationBaseline);
|
|
381
|
+
const sha256 = await hashFileDescriptor(outputFd, verified, signal);
|
|
382
|
+
const final = assertHeldBackupIdentity(backupPath, paths.backupRoot, outputFd, original, rootOriginal);
|
|
383
|
+
if (final.size !== verified.size || final.mtimeNs !== verified.mtimeNs || final.ctimeNs !== verified.ctimeNs) {
|
|
384
|
+
throw new Error('community_backup_output_changed_after_verification');
|
|
385
|
+
}
|
|
386
|
+
return {
|
|
387
|
+
path: backupPath,
|
|
388
|
+
sha256,
|
|
389
|
+
byteLength: Number(final.size),
|
|
390
|
+
verified: true,
|
|
391
|
+
createdAt: now().toISOString(),
|
|
392
|
+
sourceRelease: state.activeRelease,
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
finally {
|
|
396
|
+
closeSync(outputFd);
|
|
397
|
+
}
|
|
398
|
+
},
|
|
399
|
+
async stop({ paths, state }) {
|
|
400
|
+
await runChecked(runtime, { ...buildCommunityComposeInvocation({ paths, state, action: 'down' }), signal });
|
|
401
|
+
},
|
|
402
|
+
async pull({ paths, state, release }) {
|
|
403
|
+
await runChecked(runtime, { ...buildCommunityComposeInvocation({ paths, state, release, action: 'pull' }), signal });
|
|
404
|
+
},
|
|
405
|
+
async start({ paths, state, release, postgresVolumeName }) {
|
|
406
|
+
await runChecked(runtime, { ...buildCommunityComposeInvocation({
|
|
407
|
+
paths,
|
|
408
|
+
state,
|
|
409
|
+
release,
|
|
410
|
+
postgresVolumeName,
|
|
411
|
+
action: 'up',
|
|
412
|
+
}), signal });
|
|
413
|
+
},
|
|
414
|
+
async health({ paths }) {
|
|
415
|
+
const port = readCommunityRuntimePort(paths);
|
|
416
|
+
throwIfAborted(signal);
|
|
417
|
+
const timeoutSignal = AbortSignal.timeout(10_000);
|
|
418
|
+
const combinedSignal = signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal;
|
|
419
|
+
let response;
|
|
420
|
+
try {
|
|
421
|
+
response = await fetchImpl(options.healthUrl ?? `http://127.0.0.1:${port}/api/health`, {
|
|
422
|
+
signal: combinedSignal,
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
catch (error) {
|
|
426
|
+
if (signal?.aborted)
|
|
427
|
+
throw new CommunityProcessAbortedError();
|
|
428
|
+
throw error;
|
|
429
|
+
}
|
|
430
|
+
try {
|
|
431
|
+
await response.body?.cancel();
|
|
432
|
+
}
|
|
433
|
+
catch {
|
|
434
|
+
}
|
|
435
|
+
throwIfAborted(signal);
|
|
436
|
+
if (!response.ok)
|
|
437
|
+
throw new Error(`community_health_failed:${response.status}`);
|
|
438
|
+
},
|
|
439
|
+
async dataSmoke({ paths, state }) {
|
|
440
|
+
const prefix = composePrefix({ paths, state });
|
|
441
|
+
const result = await runChecked(runtime, {
|
|
442
|
+
...prefix,
|
|
443
|
+
args: [...prefix.args, 'exec', '-T', 'postgres', 'psql', '-U', 'aops', '-d', 'aops',
|
|
444
|
+
'--tuples-only', '--no-align', '--command', 'SELECT 1'],
|
|
445
|
+
signal,
|
|
446
|
+
});
|
|
447
|
+
if (result.stdout.trim() !== '1')
|
|
448
|
+
throw new Error('community_data_smoke_failed');
|
|
449
|
+
},
|
|
450
|
+
async claimFreshPostgresVolume({ state, claim }) {
|
|
451
|
+
assertClaim(state, claim);
|
|
452
|
+
const listed = await runChecked(runtime, {
|
|
453
|
+
command: 'docker',
|
|
454
|
+
args: ['volume', 'ls', '--quiet'],
|
|
455
|
+
env: {},
|
|
456
|
+
signal,
|
|
457
|
+
});
|
|
458
|
+
const exactNames = listed.stdout.split(/\r?\n/).map((name) => name.trim()).filter(Boolean);
|
|
459
|
+
if (exactNames.includes(claim.name)) {
|
|
460
|
+
throw new Error('community_postgres_volume_already_exists');
|
|
461
|
+
}
|
|
462
|
+
const labels = expectedVolumeLabels(claim);
|
|
463
|
+
await runChecked(runtime, {
|
|
464
|
+
command: 'docker',
|
|
465
|
+
args: [
|
|
466
|
+
'volume', 'create', '--driver', 'local',
|
|
467
|
+
...Object.entries(labels).flatMap(([key, value]) => ['--label', `${key}=${value}`]),
|
|
468
|
+
claim.name,
|
|
469
|
+
],
|
|
470
|
+
env: {},
|
|
471
|
+
signal,
|
|
472
|
+
});
|
|
473
|
+
await inspectClaimedVolume(runtime, state, claim, signal);
|
|
474
|
+
},
|
|
475
|
+
async restoreBackup({ paths, state, backup, snapshot, volumeClaim }) {
|
|
476
|
+
assertClaim(state, volumeClaim);
|
|
477
|
+
if (snapshot.sourcePath !== backup.path || snapshot.sha256 !== backup.sha256 ||
|
|
478
|
+
snapshot.byteLength !== backup.byteLength || !Number.isInteger(snapshot.fd) || snapshot.fd < 0) {
|
|
479
|
+
throw new Error('community_backup_snapshot_invalid');
|
|
480
|
+
}
|
|
481
|
+
await inspectClaimedVolume(runtime, state, volumeClaim, signal);
|
|
482
|
+
const prefix = composePrefix({ paths, state, postgresVolumeName: volumeClaim.name });
|
|
483
|
+
await runChecked(runtime, {
|
|
484
|
+
...prefix,
|
|
485
|
+
args: [...prefix.args, 'up', '--no-build', '--detach', '--wait', 'postgres'],
|
|
486
|
+
signal,
|
|
487
|
+
});
|
|
488
|
+
await inspectClaimedVolume(runtime, state, volumeClaim, signal);
|
|
489
|
+
await runChecked(runtime, {
|
|
490
|
+
...prefix,
|
|
491
|
+
args: [...prefix.args, 'exec', '-T', 'postgres', 'pg_restore',
|
|
492
|
+
'-U', 'aops', '-d', 'aops', '--no-owner', '--no-privileges', '--exit-on-error'],
|
|
493
|
+
inputFd: snapshot.fd,
|
|
494
|
+
signal,
|
|
495
|
+
});
|
|
496
|
+
},
|
|
497
|
+
};
|
|
498
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { inspectCommunityInstall } from './community-lifecycle.js';
|
|
2
|
+
import { inspectCommunityNativeInstall } from './community-native-lifecycle.js';
|
|
3
|
+
import { getActiveApiTarget } from '../utils/config.js';
|
|
4
|
+
function isRemoteTarget(apiBaseUrl) {
|
|
5
|
+
try {
|
|
6
|
+
const host = new URL(apiBaseUrl).hostname.replace(/^\[|\]$/g, '').toLowerCase();
|
|
7
|
+
return host !== 'localhost' && host !== '::1' && !/^127(?:\.\d{1,3}){3}$/.test(host);
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export function resolveCommunityHomeMode(dependencies = {}) {
|
|
14
|
+
try {
|
|
15
|
+
const native = (dependencies.inspectNative ?? inspectCommunityNativeInstall)();
|
|
16
|
+
if (native.status === 'installed')
|
|
17
|
+
return 'operate';
|
|
18
|
+
if (native.status !== 'not-installed')
|
|
19
|
+
return 'setup';
|
|
20
|
+
const oci = (dependencies.inspectOci ?? inspectCommunityInstall)();
|
|
21
|
+
if (oci.status === 'installed')
|
|
22
|
+
return 'operate';
|
|
23
|
+
if (oci.status !== 'not-installed')
|
|
24
|
+
return 'setup';
|
|
25
|
+
const activeTarget = (dependencies.getActiveTarget ?? getActiveApiTarget)();
|
|
26
|
+
return activeTarget && isRemoteTarget(activeTarget.apiBaseUrl) ? 'operate' : 'setup';
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return 'setup';
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { resolveAopsServerEnvPath } from '@aopslabs/aops-runtime-config';
|
|
2
|
+
const INSTANCE_ID = /^[a-z0-9](?:[a-z0-9-]{0,30}[a-z0-9])?$/;
|
|
3
|
+
function instanceId(value) {
|
|
4
|
+
const normalized = (value ?? 'default').trim().toLowerCase();
|
|
5
|
+
if (!INSTANCE_ID.test(normalized))
|
|
6
|
+
throw new Error('community_instance_name_invalid');
|
|
7
|
+
return normalized;
|
|
8
|
+
}
|
|
9
|
+
function portNumber(value) {
|
|
10
|
+
const port = Number(value ?? 5900);
|
|
11
|
+
if (!Number.isSafeInteger(port) || port < 1 || port > 65_535)
|
|
12
|
+
throw new Error('community_port_invalid');
|
|
13
|
+
return port;
|
|
14
|
+
}
|
|
15
|
+
function authProvider(value) {
|
|
16
|
+
const normalized = (value ?? 'trusted-local').trim().toLowerCase();
|
|
17
|
+
if (normalized === 'trusted-local')
|
|
18
|
+
return normalized;
|
|
19
|
+
if (normalized === 'authv2-jwt-session' || normalized === 'remote-session') {
|
|
20
|
+
throw new Error('community_setup_local_auth_retired:use_--auth_trusted-local_or_a_remote-session_target');
|
|
21
|
+
}
|
|
22
|
+
throw new Error('community_setup_auth_invalid:use_--auth_trusted-local');
|
|
23
|
+
}
|
|
24
|
+
export function buildCommunityInstanceContract(input) {
|
|
25
|
+
if (input.runtime !== 'native' && input.runtime !== 'oci') {
|
|
26
|
+
throw new Error('community_setup_runtime_required:use_--runtime_native_or_oci');
|
|
27
|
+
}
|
|
28
|
+
const runtime = input.runtime;
|
|
29
|
+
const exposure = input.exposure ?? 'loopback';
|
|
30
|
+
if (exposure !== 'loopback' && exposure !== 'authenticated-network' && exposure !== 'container') {
|
|
31
|
+
throw new Error('community_setup_exposure_invalid:use_--exposure_loopback_or_container');
|
|
32
|
+
}
|
|
33
|
+
const selectedAuthProvider = authProvider(input.auth);
|
|
34
|
+
const port = portNumber(input.port);
|
|
35
|
+
const publicPort = portNumber(input.publicPort ?? port);
|
|
36
|
+
if (exposure === 'loopback' && input.publicPort !== undefined && publicPort !== port) {
|
|
37
|
+
throw new Error('community_setup_public_port_requires_container_exposure');
|
|
38
|
+
}
|
|
39
|
+
if (exposure === 'authenticated-network' && input.publicPort !== undefined && publicPort !== port) {
|
|
40
|
+
throw new Error('community_setup_public_port_requires_container_exposure');
|
|
41
|
+
}
|
|
42
|
+
if (exposure === 'container' && port !== 5900) {
|
|
43
|
+
throw new Error('community_setup_container_exposure_port_required:5900');
|
|
44
|
+
}
|
|
45
|
+
if (exposure === 'authenticated-network') {
|
|
46
|
+
throw new Error('community_setup_authenticated_network_retired:use_a_remote-session_target');
|
|
47
|
+
}
|
|
48
|
+
if (exposure === 'container' && selectedAuthProvider !== 'trusted-local') {
|
|
49
|
+
throw new Error('community_setup_container_requires_trusted-local');
|
|
50
|
+
}
|
|
51
|
+
const host = '127.0.0.1';
|
|
52
|
+
const server = Object.freeze({
|
|
53
|
+
host,
|
|
54
|
+
port,
|
|
55
|
+
exposure,
|
|
56
|
+
publicPort,
|
|
57
|
+
authProvider: selectedAuthProvider,
|
|
58
|
+
});
|
|
59
|
+
const base = {
|
|
60
|
+
schemaVersion: 1,
|
|
61
|
+
instanceId: instanceId(input.instance),
|
|
62
|
+
runtime,
|
|
63
|
+
server,
|
|
64
|
+
lifecycleAuthority: 'local-instance',
|
|
65
|
+
};
|
|
66
|
+
if (runtime === 'oci') {
|
|
67
|
+
if (input.postgres || input.postgresConfig || input.postgresTls ||
|
|
68
|
+
input.exposure !== undefined || input.auth !== undefined || input.publicPort !== undefined) {
|
|
69
|
+
throw new Error('community_setup_oci_native_options_refused');
|
|
70
|
+
}
|
|
71
|
+
return Object.freeze({
|
|
72
|
+
...base,
|
|
73
|
+
profile: 'oci-managed-stack',
|
|
74
|
+
postgres: Object.freeze({ mode: 'managed-stack' }),
|
|
75
|
+
implementation: 'oci-v1-adapter',
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
if (input.postgres !== 'external' && input.postgres !== 'container') {
|
|
79
|
+
throw new Error('community_setup_native_postgres_required:use_--postgres_external_or_container');
|
|
80
|
+
}
|
|
81
|
+
if (input.postgres === 'container') {
|
|
82
|
+
if (input.postgresConfig || input.postgresTls) {
|
|
83
|
+
throw new Error('community_setup_container_postgres_external_options_refused');
|
|
84
|
+
}
|
|
85
|
+
if (exposure !== 'loopback') {
|
|
86
|
+
throw new Error('community_setup_managed_postgres_requires_loopback_exposure');
|
|
87
|
+
}
|
|
88
|
+
return Object.freeze({
|
|
89
|
+
...base,
|
|
90
|
+
profile: 'native-container-postgres',
|
|
91
|
+
postgres: Object.freeze({ mode: 'container' }),
|
|
92
|
+
implementation: 'native-v1-supervisor',
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
if (!['disable', 'require', 'verify-full'].includes(String(input.postgresTls))) {
|
|
96
|
+
throw new Error('community_setup_external_postgres_tls_required:use_--postgres-tls');
|
|
97
|
+
}
|
|
98
|
+
const configInput = input.postgresConfig?.trim();
|
|
99
|
+
if (configInput) {
|
|
100
|
+
const isWindowsPath = /^[A-Za-z]:[\\/]/.test(configInput);
|
|
101
|
+
if (/[\0\r\n]/.test(configInput) ||
|
|
102
|
+
(!isWindowsPath && /^[A-Za-z][A-Za-z0-9+.-]*:/.test(configInput)) ||
|
|
103
|
+
/[=@;]/.test(configInput) ||
|
|
104
|
+
/(?:^|\s)(?:host|user|password|pwd|dbname|server|data\s+source|user\s+id|uid|database|initial\s+catalog|pghost|pgport|pgdatabase|pguser|pgpassword|pgservice|pgsslmode)\s*=/i.test(configInput)) {
|
|
105
|
+
throw new Error('community_setup_external_postgres_config_must_be_file_reference');
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const configRef = resolveAopsServerEnvPath({ explicitPath: configInput }, input.processEnv ?? process.env).path;
|
|
109
|
+
return Object.freeze({
|
|
110
|
+
...base,
|
|
111
|
+
profile: 'native-external-postgres',
|
|
112
|
+
postgres: Object.freeze({
|
|
113
|
+
mode: 'external',
|
|
114
|
+
configRef,
|
|
115
|
+
tlsPolicy: input.postgresTls,
|
|
116
|
+
}),
|
|
117
|
+
implementation: 'native-v1-supervisor',
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
export function parseCommunityInstanceContract(value) {
|
|
121
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
122
|
+
throw new Error('community_instance_contract_invalid');
|
|
123
|
+
}
|
|
124
|
+
const input = value;
|
|
125
|
+
const ociRuntime = input.runtime === 'oci';
|
|
126
|
+
if (ociRuntime &&
|
|
127
|
+
((input.server?.exposure !== undefined && input.server.exposure !== 'loopback') ||
|
|
128
|
+
(input.server?.authProvider !== undefined && input.server.authProvider !== 'trusted-local') ||
|
|
129
|
+
(input.server?.host !== undefined && input.server.host !== '127.0.0.1') ||
|
|
130
|
+
(input.server?.publicPort !== undefined && input.server.publicPort !== input.server?.port)))
|
|
131
|
+
throw new Error('community_instance_contract_oci_server_invalid');
|
|
132
|
+
const rebuilt = buildCommunityInstanceContract({
|
|
133
|
+
runtime: input.runtime,
|
|
134
|
+
postgres: input.postgres?.mode === 'managed-stack' ? undefined : input.postgres?.mode,
|
|
135
|
+
postgresConfig: input.postgres?.configRef,
|
|
136
|
+
postgresTls: input.postgres?.tlsPolicy,
|
|
137
|
+
exposure: ociRuntime ? undefined : input.server?.exposure,
|
|
138
|
+
auth: ociRuntime ? undefined : input.server?.authProvider,
|
|
139
|
+
publicPort: ociRuntime ? undefined : input.server?.publicPort,
|
|
140
|
+
instance: input.instanceId,
|
|
141
|
+
port: input.server?.port,
|
|
142
|
+
});
|
|
143
|
+
if (input.schemaVersion !== 1 || input.profile !== rebuilt.profile ||
|
|
144
|
+
input.lifecycleAuthority !== 'local-instance' || input.implementation !== rebuilt.implementation) {
|
|
145
|
+
throw new Error('community_instance_contract_schema_invalid');
|
|
146
|
+
}
|
|
147
|
+
return rebuilt;
|
|
148
|
+
}
|