@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,888 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { readAopsServerEnvConfig, resolveAopsServerEnvPath, } from '@aopslabs/aops-runtime-config';
|
|
4
|
+
import { inspectCommunityInstall } from './community-lifecycle.js';
|
|
5
|
+
import { buildCommunityPnpmInvocation, isCommunityNativeNpmPackageSource, inspectCommunityNativeInstall, inspectCommunityNativeSource, loadExternalPostgresUrl, loadExternalServerAuthConfig, resolveCommunityNativeCockpitPort, resolveCommunityNativeDefaultSourceRoot, } from './community-native-lifecycle.js';
|
|
6
|
+
import { buildCommunityInstanceContract, } from './community-instance-contract.js';
|
|
7
|
+
import { createCliApiClientFromOptions, fetchCliBootstrapHealth } from '../utils/api.js';
|
|
8
|
+
import { findApiTargetByBaseUrlReadOnly, getActiveApiTargetReadOnly, getApiTargetReadOnly, } from '../utils/config.js';
|
|
9
|
+
import { inspectSetupAgentAssets, } from './setup-agent-assets-bridge.js';
|
|
10
|
+
import { inspectLocalPostgres, } from './setup-local-postgres.js';
|
|
11
|
+
import { createCommunityDiagnostic } from './community-diagnostic.js';
|
|
12
|
+
export const SETUP_PATHS = [
|
|
13
|
+
{
|
|
14
|
+
id: 'native-external',
|
|
15
|
+
number: '1',
|
|
16
|
+
title: 'Npm server with your existing PostgreSQL',
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
id: 'native-container',
|
|
20
|
+
number: '2',
|
|
21
|
+
title: 'Npm server with automatic Docker PostgreSQL',
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
id: 'native-local',
|
|
25
|
+
number: '3',
|
|
26
|
+
title: 'Npm server with PostgreSQL installed on this computer',
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
id: 'cli-existing',
|
|
30
|
+
number: '4',
|
|
31
|
+
title: 'CLI connected to an existing AOPS server',
|
|
32
|
+
},
|
|
33
|
+
];
|
|
34
|
+
export function normalizeBootstrapAuthContract(bootstrap) {
|
|
35
|
+
const auth = bootstrap.auth && typeof bootstrap.auth === 'object'
|
|
36
|
+
? bootstrap.auth
|
|
37
|
+
: null;
|
|
38
|
+
const providerValue = bootstrap.authProvider ?? auth?.provider;
|
|
39
|
+
const authProvider = providerValue === 'trusted-local'
|
|
40
|
+
? 'trusted-local'
|
|
41
|
+
: providerValue === 'remote-session' || providerValue === 'authv2-jwt-session'
|
|
42
|
+
? 'remote-session'
|
|
43
|
+
: null;
|
|
44
|
+
const explicitAuthRequired = typeof bootstrap.authRequired === 'boolean' ? bootstrap.authRequired : null;
|
|
45
|
+
const authRequired = explicitAuthRequired !== null
|
|
46
|
+
? explicitAuthRequired === (authProvider === 'remote-session')
|
|
47
|
+
? explicitAuthRequired
|
|
48
|
+
: null
|
|
49
|
+
: authProvider !== null
|
|
50
|
+
? authProvider === 'remote-session'
|
|
51
|
+
: null;
|
|
52
|
+
const firstAdminState = auth && typeof auth.firstAdminState === 'string'
|
|
53
|
+
? auth.firstAdminState
|
|
54
|
+
: null;
|
|
55
|
+
return {
|
|
56
|
+
authProvider,
|
|
57
|
+
authRequired,
|
|
58
|
+
firstAdminState: firstAdminState === 'not-applicable' || firstAdminState === 'required' ||
|
|
59
|
+
firstAdminState === 'ready' || firstAdminState === 'blocked' || firstAdminState === 'unknown'
|
|
60
|
+
? firstAdminState
|
|
61
|
+
: null,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
function normalizeNonEmpty(value) {
|
|
65
|
+
if (typeof value !== 'string')
|
|
66
|
+
return undefined;
|
|
67
|
+
const normalized = value.trim();
|
|
68
|
+
return normalized || undefined;
|
|
69
|
+
}
|
|
70
|
+
function normalizeSafeApiBaseUrl(value) {
|
|
71
|
+
const normalized = normalizeNonEmpty(value);
|
|
72
|
+
if (!normalized)
|
|
73
|
+
return undefined;
|
|
74
|
+
try {
|
|
75
|
+
const parsed = new URL(normalized);
|
|
76
|
+
if (!['http:', 'https:'].includes(parsed.protocol) || parsed.username || parsed.password ||
|
|
77
|
+
parsed.search || parsed.hash || parsed.pathname !== '/') {
|
|
78
|
+
throw new Error('setup_api_base_url_invalid');
|
|
79
|
+
}
|
|
80
|
+
return parsed.origin;
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
throw new Error('setup_api_base_url_invalid');
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function normalizePort(value) {
|
|
87
|
+
const port = Number(value ?? 5900);
|
|
88
|
+
if (!Number.isSafeInteger(port) || port < 1 || port > 65_535) {
|
|
89
|
+
throw new Error('setup_port_invalid');
|
|
90
|
+
}
|
|
91
|
+
return port;
|
|
92
|
+
}
|
|
93
|
+
export function parseSetupPath(value) {
|
|
94
|
+
const normalized = normalizeNonEmpty(value)?.toLowerCase();
|
|
95
|
+
if (!normalized)
|
|
96
|
+
return undefined;
|
|
97
|
+
return SETUP_PATHS.find((entry) => entry.id === normalized || entry.number === normalized)?.id;
|
|
98
|
+
}
|
|
99
|
+
function pathDescriptor(id) {
|
|
100
|
+
return id ? SETUP_PATHS.find((entry) => entry.id === id) : undefined;
|
|
101
|
+
}
|
|
102
|
+
function isLoopbackUrl(value) {
|
|
103
|
+
try {
|
|
104
|
+
const host = new URL(value).hostname.replace(/^\[|\]$/g, '').toLowerCase();
|
|
105
|
+
return host === 'localhost' || host === '::1' || /^127(?:\.\d{1,3}){3}$/.test(host);
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function isLoopbackPostgresAtPort(value, port) {
|
|
112
|
+
try {
|
|
113
|
+
const parsed = new URL(value);
|
|
114
|
+
return isLoopbackUrl(value) && Number(parsed.port || 5432) === port;
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
function commandAvailable(command) {
|
|
121
|
+
const locator = process.platform === 'win32' ? 'where.exe' : 'which';
|
|
122
|
+
const result = spawnSync(locator, [command], {
|
|
123
|
+
encoding: 'utf8',
|
|
124
|
+
windowsHide: true,
|
|
125
|
+
timeout: 1_000,
|
|
126
|
+
stdio: 'ignore',
|
|
127
|
+
});
|
|
128
|
+
return result.status === 0;
|
|
129
|
+
}
|
|
130
|
+
function dockerRuntimeAvailable(compose) {
|
|
131
|
+
const info = spawnSync('docker', ['info', '--format', '{{json .ServerVersion}}'], {
|
|
132
|
+
encoding: 'utf8',
|
|
133
|
+
windowsHide: true,
|
|
134
|
+
timeout: 2_000,
|
|
135
|
+
stdio: 'ignore',
|
|
136
|
+
});
|
|
137
|
+
if (info.status !== 0)
|
|
138
|
+
return false;
|
|
139
|
+
if (!compose)
|
|
140
|
+
return true;
|
|
141
|
+
const composeVersion = spawnSync('docker', ['compose', 'version'], {
|
|
142
|
+
encoding: 'utf8',
|
|
143
|
+
windowsHide: true,
|
|
144
|
+
timeout: 2_000,
|
|
145
|
+
stdio: 'ignore',
|
|
146
|
+
});
|
|
147
|
+
return composeVersion.status === 0;
|
|
148
|
+
}
|
|
149
|
+
function safeReason(error, fallback) {
|
|
150
|
+
const message = error instanceof Error ? error.message : '';
|
|
151
|
+
return /^[a-z0-9_:-]+$/i.test(message) ? message : fallback;
|
|
152
|
+
}
|
|
153
|
+
function inferInstalledPath(native, oci) {
|
|
154
|
+
if (native.status === 'installed' && native.state?.profile === 'native-external-postgres') {
|
|
155
|
+
return 'native-external';
|
|
156
|
+
}
|
|
157
|
+
if (native.status === 'installed' && native.state?.profile === 'native-container-postgres') {
|
|
158
|
+
return 'native-container';
|
|
159
|
+
}
|
|
160
|
+
return undefined;
|
|
161
|
+
}
|
|
162
|
+
function installationStateCheck(selected, native, oci, desiredSecurity) {
|
|
163
|
+
if (!selected) {
|
|
164
|
+
return {
|
|
165
|
+
id: 'installation-state',
|
|
166
|
+
state: 'unknown',
|
|
167
|
+
required: false,
|
|
168
|
+
summary: 'Installation state will be evaluated after a path is selected.',
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
if (selected === 'cli-existing') {
|
|
172
|
+
return {
|
|
173
|
+
id: 'installation-state',
|
|
174
|
+
state: 'not-applicable',
|
|
175
|
+
required: false,
|
|
176
|
+
summary: 'The CLI-only path does not install a local server runtime.',
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
if (oci.status !== 'not-installed') {
|
|
180
|
+
return {
|
|
181
|
+
id: 'installation-state',
|
|
182
|
+
state: 'action-required',
|
|
183
|
+
required: true,
|
|
184
|
+
summary: 'A legacy application-image installation owns this local instance.',
|
|
185
|
+
next: 'Use the explicit server lifecycle to inspect or remove that installation before selecting an npm-server setup path.',
|
|
186
|
+
data: { blocking: true, legacyProfile: 'oci-managed-stack' },
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
const installedPath = inferInstalledPath(native, oci);
|
|
190
|
+
const compatibleInstalledPath = selected === 'native-local' && installedPath === 'native-external'
|
|
191
|
+
? selected
|
|
192
|
+
: installedPath;
|
|
193
|
+
if (compatibleInstalledPath && compatibleInstalledPath !== selected) {
|
|
194
|
+
return {
|
|
195
|
+
id: 'installation-state',
|
|
196
|
+
state: 'action-required',
|
|
197
|
+
required: true,
|
|
198
|
+
summary: 'A different local installation profile already owns this instance.',
|
|
199
|
+
next: 'Use the installed path, or run the explicit migration/reset workflow before switching paths.',
|
|
200
|
+
data: { blocking: true, selectedPath: selected, installedPath },
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
if (native.status === 'runtime-conflict' || (native.status !== 'not-installed' && oci.status !== 'not-installed')) {
|
|
204
|
+
const diagnostic = createCommunityDiagnostic(new Error(native.error ?? 'community_instance_runtime_conflict'), {
|
|
205
|
+
code: 'setup_install_runtime_conflict',
|
|
206
|
+
phase: 'readiness',
|
|
207
|
+
summary: 'Conflicting local runtime state requires repair before setup can continue.',
|
|
208
|
+
nextActions: ['Run `aops doctor --json` and resolve the reported runtime conflict.'],
|
|
209
|
+
});
|
|
210
|
+
return {
|
|
211
|
+
id: 'installation-state',
|
|
212
|
+
state: 'action-required',
|
|
213
|
+
required: true,
|
|
214
|
+
summary: diagnostic.summary,
|
|
215
|
+
next: diagnostic.nextActions[0],
|
|
216
|
+
data: {
|
|
217
|
+
blocking: true,
|
|
218
|
+
reasonCode: diagnostic.cause.code,
|
|
219
|
+
phase: diagnostic.phase,
|
|
220
|
+
diagnostic,
|
|
221
|
+
},
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
const expected = selected === 'native-external' || selected === 'native-local'
|
|
225
|
+
? native.status === 'installed' && native.state?.profile === 'native-external-postgres'
|
|
226
|
+
: selected === 'native-container'
|
|
227
|
+
? native.status === 'installed' && native.state?.profile === 'native-container-postgres'
|
|
228
|
+
: false;
|
|
229
|
+
const partial = native.status === 'partial';
|
|
230
|
+
if (partial) {
|
|
231
|
+
const diagnostic = createCommunityDiagnostic(new Error(native.error ?? 'community_native_install_partial'), {
|
|
232
|
+
code: 'setup_install_state_incomplete',
|
|
233
|
+
phase: 'readiness',
|
|
234
|
+
});
|
|
235
|
+
return {
|
|
236
|
+
id: 'installation-state',
|
|
237
|
+
state: 'action-required',
|
|
238
|
+
required: true,
|
|
239
|
+
summary: diagnostic.summary,
|
|
240
|
+
next: diagnostic.nextActions[0] ?? 'Run `aops doctor --json` before resuming setup.',
|
|
241
|
+
data: {
|
|
242
|
+
blocking: true,
|
|
243
|
+
reasonCode: diagnostic.cause.code,
|
|
244
|
+
phase: diagnostic.phase,
|
|
245
|
+
diagnostic,
|
|
246
|
+
},
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
if (expected) {
|
|
250
|
+
const authProvider = native.state?.server?.authProvider;
|
|
251
|
+
const exposure = native.state?.server?.exposure;
|
|
252
|
+
const securityMatches = !desiredSecurity ||
|
|
253
|
+
(authProvider === desiredSecurity.authProvider &&
|
|
254
|
+
exposure === desiredSecurity.exposure);
|
|
255
|
+
if (!securityMatches) {
|
|
256
|
+
return {
|
|
257
|
+
id: 'installation-state',
|
|
258
|
+
state: 'action-required',
|
|
259
|
+
required: true,
|
|
260
|
+
summary: 'The installed server security mode differs from the selected mode.',
|
|
261
|
+
next: 'Apply the selected setup path to reconfigure server authentication and exposure.',
|
|
262
|
+
data: {
|
|
263
|
+
installed: true,
|
|
264
|
+
securityMatches: false,
|
|
265
|
+
authProvider: authProvider ?? null,
|
|
266
|
+
exposure: exposure ?? null,
|
|
267
|
+
desiredAuthProvider: desiredSecurity.authProvider,
|
|
268
|
+
desiredExposure: desiredSecurity.exposure,
|
|
269
|
+
},
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
return {
|
|
273
|
+
id: 'installation-state',
|
|
274
|
+
state: 'ready',
|
|
275
|
+
required: true,
|
|
276
|
+
summary: 'The selected local installation profile is present.',
|
|
277
|
+
data: {
|
|
278
|
+
installed: true,
|
|
279
|
+
securityMatches,
|
|
280
|
+
authProvider: authProvider ?? null,
|
|
281
|
+
exposure: exposure ?? null,
|
|
282
|
+
},
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
return {
|
|
286
|
+
id: 'installation-state',
|
|
287
|
+
state: 'action-required',
|
|
288
|
+
required: true,
|
|
289
|
+
summary: 'The selected local installation profile is not installed yet.',
|
|
290
|
+
next: 'Run `aops setup init --apply` to continue the selected setup path.',
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
async function probeEndpoint(options) {
|
|
294
|
+
try {
|
|
295
|
+
const api = await createCliApiClientFromOptions({
|
|
296
|
+
apiBaseUrl: options.apiBaseUrl,
|
|
297
|
+
targetName: options.targetName,
|
|
298
|
+
timeoutMs: options.timeoutMs,
|
|
299
|
+
readOnly: true,
|
|
300
|
+
});
|
|
301
|
+
const bootstrap = await fetchCliBootstrapHealth(api, {
|
|
302
|
+
timeoutMs: options.timeoutMs,
|
|
303
|
+
});
|
|
304
|
+
const { authProvider, authRequired, firstAdminState } = normalizeBootstrapAuthContract(bootstrap);
|
|
305
|
+
let authenticated = authRequired === false ? true : authRequired === true ? false : null;
|
|
306
|
+
let principalUserId = null;
|
|
307
|
+
if (authRequired === true) {
|
|
308
|
+
try {
|
|
309
|
+
const me = await api.client.requestXfJson('/api/auth/me', { method: 'GET' }, { retry401: false, timeoutMs: options.timeoutMs });
|
|
310
|
+
const userId = me.result?.data?.principal?.userId;
|
|
311
|
+
principalUserId = typeof userId === 'string' && userId.trim() ? userId.trim() : null;
|
|
312
|
+
authenticated =
|
|
313
|
+
me.status === 200 &&
|
|
314
|
+
me.result?.ok === true &&
|
|
315
|
+
Boolean(principalUserId) &&
|
|
316
|
+
me.result?.data?.authProvider === authProvider &&
|
|
317
|
+
me.result?.data?.authRequired === authRequired;
|
|
318
|
+
}
|
|
319
|
+
catch {
|
|
320
|
+
authenticated = false;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
return {
|
|
324
|
+
reachable: true,
|
|
325
|
+
authRequired,
|
|
326
|
+
authProvider,
|
|
327
|
+
authenticated,
|
|
328
|
+
principalUserId,
|
|
329
|
+
firstAdminState,
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
catch {
|
|
333
|
+
return {
|
|
334
|
+
reachable: false,
|
|
335
|
+
authRequired: null,
|
|
336
|
+
authProvider: null,
|
|
337
|
+
authenticated: null,
|
|
338
|
+
principalUserId: null,
|
|
339
|
+
firstAdminState: null,
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
async function probeCockpit(origin, timeoutMs) {
|
|
344
|
+
const request = async (pathname) => {
|
|
345
|
+
try {
|
|
346
|
+
const response = await fetch(`${origin}${pathname}`, {
|
|
347
|
+
headers: pathname.startsWith('/api') ? { origin } : undefined,
|
|
348
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
349
|
+
});
|
|
350
|
+
return response.ok;
|
|
351
|
+
}
|
|
352
|
+
catch {
|
|
353
|
+
return false;
|
|
354
|
+
}
|
|
355
|
+
};
|
|
356
|
+
const [reachable, apiReachable] = await Promise.all([
|
|
357
|
+
request('/_aops/cockpit-health'),
|
|
358
|
+
request('/api/health'),
|
|
359
|
+
]);
|
|
360
|
+
return { reachable, apiReachable, origin };
|
|
361
|
+
}
|
|
362
|
+
function agentAssetCheck(status) {
|
|
363
|
+
if (status.availability === 'task-136-pending') {
|
|
364
|
+
return {
|
|
365
|
+
id: 'agent-assets',
|
|
366
|
+
state: 'action-required',
|
|
367
|
+
required: false,
|
|
368
|
+
summary: status.summary,
|
|
369
|
+
next: status.nextActions[0],
|
|
370
|
+
data: status.data,
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
return status.state === 'ready'
|
|
374
|
+
? {
|
|
375
|
+
id: 'agent-assets',
|
|
376
|
+
state: 'ready',
|
|
377
|
+
required: true,
|
|
378
|
+
summary: status.summary,
|
|
379
|
+
data: status.data,
|
|
380
|
+
}
|
|
381
|
+
: {
|
|
382
|
+
id: 'agent-assets',
|
|
383
|
+
state: 'action-required',
|
|
384
|
+
required: true,
|
|
385
|
+
summary: status.summary,
|
|
386
|
+
next: status.nextActions[0],
|
|
387
|
+
data: status.data,
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
export async function inspectSetupReadiness(options = {}) {
|
|
391
|
+
const processEnv = options.processEnv ?? process.env;
|
|
392
|
+
const cwd = path.resolve(options.cwd ?? process.cwd());
|
|
393
|
+
const probes = options.probes ?? {};
|
|
394
|
+
const explicitApiBaseUrl = normalizeSafeApiBaseUrl(options.apiBaseUrl);
|
|
395
|
+
const localPort = normalizePort(options.port);
|
|
396
|
+
const native = probes.nativeInspection ?? inspectCommunityNativeInstall({
|
|
397
|
+
instanceName: options.instance,
|
|
398
|
+
dataRoot: options.dataRoot,
|
|
399
|
+
});
|
|
400
|
+
const oci = probes.ociInspection ?? inspectCommunityInstall({
|
|
401
|
+
instanceName: options.instance,
|
|
402
|
+
dataRoot: options.dataRoot,
|
|
403
|
+
});
|
|
404
|
+
let activeTarget = probes.activeTarget ?? null;
|
|
405
|
+
if (probes.activeTarget === undefined) {
|
|
406
|
+
try {
|
|
407
|
+
activeTarget = getActiveApiTargetReadOnly() ?? null;
|
|
408
|
+
}
|
|
409
|
+
catch {
|
|
410
|
+
activeTarget = null;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
const requestedTargetName = normalizeNonEmpty(options.targetName);
|
|
414
|
+
let selectedTarget = probes.selectedTarget ?? null;
|
|
415
|
+
if (probes.selectedTarget === undefined) {
|
|
416
|
+
try {
|
|
417
|
+
if (requestedTargetName) {
|
|
418
|
+
selectedTarget =
|
|
419
|
+
getApiTargetReadOnly(requestedTargetName) ?? null;
|
|
420
|
+
}
|
|
421
|
+
else if (explicitApiBaseUrl) {
|
|
422
|
+
selectedTarget =
|
|
423
|
+
findApiTargetByBaseUrlReadOnly(explicitApiBaseUrl) ?? null;
|
|
424
|
+
}
|
|
425
|
+
else {
|
|
426
|
+
selectedTarget = activeTarget;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
catch {
|
|
430
|
+
selectedTarget = null;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
if (selectedTarget &&
|
|
434
|
+
explicitApiBaseUrl &&
|
|
435
|
+
selectedTarget.apiBaseUrl !== explicitApiBaseUrl) {
|
|
436
|
+
selectedTarget = null;
|
|
437
|
+
}
|
|
438
|
+
if (probes.selectedTarget === undefined &&
|
|
439
|
+
probes.activeTarget !== undefined &&
|
|
440
|
+
!requestedTargetName &&
|
|
441
|
+
explicitApiBaseUrl) {
|
|
442
|
+
selectedTarget = activeTarget?.apiBaseUrl === explicitApiBaseUrl ? activeTarget : null;
|
|
443
|
+
}
|
|
444
|
+
const explicitPath = parseSetupPath(options.path);
|
|
445
|
+
const installedPath = inferInstalledPath(native, oci);
|
|
446
|
+
const activeTargetPath = selectedTarget && !isLoopbackUrl(selectedTarget.apiBaseUrl) ? 'cli-existing' : undefined;
|
|
447
|
+
const selected = explicitPath ?? installedPath ?? activeTargetPath;
|
|
448
|
+
const descriptor = pathDescriptor(selected);
|
|
449
|
+
const desiredSecurity = options.auth && options.exposure
|
|
450
|
+
? Object.freeze({
|
|
451
|
+
authProvider: options.auth,
|
|
452
|
+
exposure: options.exposure,
|
|
453
|
+
})
|
|
454
|
+
: undefined;
|
|
455
|
+
const source = explicitPath
|
|
456
|
+
? 'explicit'
|
|
457
|
+
: installedPath
|
|
458
|
+
? 'installed-state'
|
|
459
|
+
: activeTargetPath
|
|
460
|
+
? 'active-target'
|
|
461
|
+
: 'unselected';
|
|
462
|
+
const checks = [
|
|
463
|
+
descriptor
|
|
464
|
+
? {
|
|
465
|
+
id: 'installation-path',
|
|
466
|
+
state: 'ready',
|
|
467
|
+
required: true,
|
|
468
|
+
summary: `Path ${descriptor.number} selected: ${descriptor.title}.`,
|
|
469
|
+
data: {
|
|
470
|
+
id: descriptor.id,
|
|
471
|
+
number: descriptor.number,
|
|
472
|
+
...(desiredSecurity
|
|
473
|
+
? {
|
|
474
|
+
authProvider: desiredSecurity.authProvider,
|
|
475
|
+
exposure: desiredSecurity.exposure,
|
|
476
|
+
desiredAuthProvider: desiredSecurity.authProvider,
|
|
477
|
+
desiredExposure: desiredSecurity.exposure,
|
|
478
|
+
}
|
|
479
|
+
: {}),
|
|
480
|
+
},
|
|
481
|
+
}
|
|
482
|
+
: {
|
|
483
|
+
id: 'installation-path',
|
|
484
|
+
state: 'action-required',
|
|
485
|
+
required: true,
|
|
486
|
+
summary: 'No installation path has been selected or inferred.',
|
|
487
|
+
next: 'Choose path 1, 2, 3, or 4.',
|
|
488
|
+
data: { choices: SETUP_PATHS },
|
|
489
|
+
},
|
|
490
|
+
];
|
|
491
|
+
checks.push(installationStateCheck(selected, native, oci, desiredSecurity));
|
|
492
|
+
const explicitPostgresConfig = normalizeNonEmpty(options.postgresConfig);
|
|
493
|
+
const safePostgresConfig = explicitPostgresConfig
|
|
494
|
+
? buildCommunityInstanceContract({
|
|
495
|
+
runtime: 'native',
|
|
496
|
+
postgres: 'external',
|
|
497
|
+
postgresConfig: explicitPostgresConfig,
|
|
498
|
+
postgresTls: options.postgresTls ?? 'require',
|
|
499
|
+
processEnv,
|
|
500
|
+
}).postgres.configRef
|
|
501
|
+
: undefined;
|
|
502
|
+
const envResolution = resolveAopsServerEnvPath({ explicitPath: safePostgresConfig }, processEnv);
|
|
503
|
+
let envSnapshot;
|
|
504
|
+
let envError;
|
|
505
|
+
try {
|
|
506
|
+
envSnapshot = readAopsServerEnvConfig(processEnv, envResolution.path);
|
|
507
|
+
}
|
|
508
|
+
catch (error) {
|
|
509
|
+
envError = safeReason(error, 'aops_server_env_invalid');
|
|
510
|
+
}
|
|
511
|
+
if (selected === 'native-external' || selected === 'native-local') {
|
|
512
|
+
const postgresEnvReady = Boolean(envSnapshot?.exists && envSnapshot.repoDialect === 'pg' && envSnapshot.repoUrl);
|
|
513
|
+
let envAuthProvider = null;
|
|
514
|
+
let envAuthError = null;
|
|
515
|
+
if (envSnapshot?.exists) {
|
|
516
|
+
const configuredProvider = String(envSnapshot.assignments.AOPS_AUTH_PROVIDER ?? 'trusted-local')
|
|
517
|
+
.trim()
|
|
518
|
+
.toLowerCase();
|
|
519
|
+
try {
|
|
520
|
+
if (configuredProvider !== 'trusted-local') {
|
|
521
|
+
throw new Error('community_local_auth_provider_retired:repair_with_trusted-local');
|
|
522
|
+
}
|
|
523
|
+
envAuthProvider = loadExternalServerAuthConfig(envResolution.path, 'trusted-local').authProvider;
|
|
524
|
+
}
|
|
525
|
+
catch (error) {
|
|
526
|
+
envAuthError = safeReason(error, 'aops_server_env_auth_invalid');
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
const authEnvReady = envAuthProvider !== null;
|
|
530
|
+
const authProviderMatches = !desiredSecurity || envAuthProvider === desiredSecurity.authProvider;
|
|
531
|
+
const selectedLocalPostgresPort = Number(options.localPostgresPort ?? 5432);
|
|
532
|
+
const localPostgresTargetMatches = selected !== 'native-local' || isLoopbackPostgresAtPort(String(envSnapshot?.repoUrl ?? ''), selectedLocalPostgresPort);
|
|
533
|
+
const postgresReady = postgresEnvReady && localPostgresTargetMatches;
|
|
534
|
+
const envReady = postgresReady && authEnvReady && authProviderMatches;
|
|
535
|
+
checks.push({
|
|
536
|
+
id: 'global-server-env',
|
|
537
|
+
state: envReady ? 'ready' : 'action-required',
|
|
538
|
+
required: true,
|
|
539
|
+
summary: envReady
|
|
540
|
+
? 'The selected server env contains a PostgreSQL repository target.'
|
|
541
|
+
: postgresEnvReady && authEnvReady && !authProviderMatches
|
|
542
|
+
? 'The selected server env authentication provider does not match the selected server mode.'
|
|
543
|
+
: postgresEnvReady && !authEnvReady
|
|
544
|
+
? 'The selected server env has an invalid authentication provider or AuthV2 secret closure.'
|
|
545
|
+
: selected === 'native-local' && postgresEnvReady
|
|
546
|
+
? 'The selected server env points to a non-local PostgreSQL target or a different local port.'
|
|
547
|
+
: 'The selected server env is missing, unsafe, or does not contain a PostgreSQL target.',
|
|
548
|
+
next: envReady
|
|
549
|
+
? undefined
|
|
550
|
+
: postgresEnvReady && authEnvReady && !authProviderMatches
|
|
551
|
+
? `Run \`aops setup server-env --auth ${desiredSecurity.authProvider}\` for the selected private env.`
|
|
552
|
+
: selected === 'native-local'
|
|
553
|
+
? 'Path 3 creates a private local server env during apply; use `--postgres-config <private-path>` to preserve an existing remote env, or use path 1 for that remote target.'
|
|
554
|
+
: 'Run `aops setup server-env` or provide a safe `--postgres-config` file path.',
|
|
555
|
+
data: {
|
|
556
|
+
path: envResolution.path,
|
|
557
|
+
source: envResolution.source,
|
|
558
|
+
exists: envSnapshot?.exists ?? false,
|
|
559
|
+
repoDialect: envSnapshot?.repoDialect ?? null,
|
|
560
|
+
redactedRepoUrl: envSnapshot?.redactedRepoUrl ?? null,
|
|
561
|
+
postgresReady,
|
|
562
|
+
localPostgresTargetMatches,
|
|
563
|
+
authProvider: envAuthProvider,
|
|
564
|
+
desiredAuthProvider: desiredSecurity?.authProvider ?? null,
|
|
565
|
+
authProviderMatches,
|
|
566
|
+
authError: envAuthError,
|
|
567
|
+
error: envError ?? null,
|
|
568
|
+
blocking: selected === 'native-local' &&
|
|
569
|
+
postgresEnvReady &&
|
|
570
|
+
!localPostgresTargetMatches,
|
|
571
|
+
},
|
|
572
|
+
});
|
|
573
|
+
const installedPostgresTls = source === 'installed-state' &&
|
|
574
|
+
native.status === 'installed' &&
|
|
575
|
+
native.state?.profile === 'native-external-postgres'
|
|
576
|
+
? native.state.postgres.tlsPolicy
|
|
577
|
+
: undefined;
|
|
578
|
+
const tlsPolicy = selected === 'native-local'
|
|
579
|
+
? 'disable'
|
|
580
|
+
: options.postgresTls ?? installedPostgresTls;
|
|
581
|
+
if (!tlsPolicy) {
|
|
582
|
+
checks.push({
|
|
583
|
+
id: 'postgresql-tls',
|
|
584
|
+
state: 'action-required',
|
|
585
|
+
required: true,
|
|
586
|
+
summary: 'A PostgreSQL TLS policy has not been selected.',
|
|
587
|
+
next: 'Choose require (default), verify-full, or an explicit disable policy.',
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
else if (!envReady) {
|
|
591
|
+
checks.push({
|
|
592
|
+
id: 'postgresql-tls',
|
|
593
|
+
state: 'action-required',
|
|
594
|
+
required: true,
|
|
595
|
+
summary: 'PostgreSQL TLS readiness cannot be validated until the server env is ready.',
|
|
596
|
+
data: { policy: tlsPolicy },
|
|
597
|
+
});
|
|
598
|
+
}
|
|
599
|
+
else {
|
|
600
|
+
try {
|
|
601
|
+
loadExternalPostgresUrl(envResolution.path, tlsPolicy);
|
|
602
|
+
checks.push({
|
|
603
|
+
id: 'postgresql-tls',
|
|
604
|
+
state: 'ready',
|
|
605
|
+
required: true,
|
|
606
|
+
summary: `PostgreSQL configuration satisfies the ${tlsPolicy} TLS policy.`,
|
|
607
|
+
data: { policy: tlsPolicy },
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
catch (error) {
|
|
611
|
+
checks.push({
|
|
612
|
+
id: 'postgresql-tls',
|
|
613
|
+
state: 'action-required',
|
|
614
|
+
required: true,
|
|
615
|
+
summary: 'PostgreSQL or TLS configuration is not ready.',
|
|
616
|
+
next: 'Repair the selected server env and TLS root certificate reference.',
|
|
617
|
+
data: { policy: tlsPolicy, error: safeReason(error, 'postgresql_tls_config_invalid') },
|
|
618
|
+
});
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
else {
|
|
623
|
+
checks.push({
|
|
624
|
+
id: 'global-server-env',
|
|
625
|
+
state: 'not-applicable',
|
|
626
|
+
required: false,
|
|
627
|
+
summary: 'This path does not require an external PostgreSQL server env.',
|
|
628
|
+
data: { path: envResolution.path, source: envResolution.source },
|
|
629
|
+
});
|
|
630
|
+
checks.push({
|
|
631
|
+
id: 'postgresql-tls',
|
|
632
|
+
state: 'not-applicable',
|
|
633
|
+
required: false,
|
|
634
|
+
summary: 'External PostgreSQL TLS configuration is not used by this path.',
|
|
635
|
+
});
|
|
636
|
+
}
|
|
637
|
+
if (selected === 'native-local') {
|
|
638
|
+
const local = probes.localPostgres ?? await inspectLocalPostgres({
|
|
639
|
+
host: options.localPostgresHost,
|
|
640
|
+
port: options.localPostgresPort,
|
|
641
|
+
timeoutMs: options.timeoutMs,
|
|
642
|
+
});
|
|
643
|
+
const next = local.reachable
|
|
644
|
+
? undefined
|
|
645
|
+
: [local.guidance.summary, ...local.guidance.commands, local.guidance.url].join(' ');
|
|
646
|
+
checks.push({
|
|
647
|
+
id: 'local-postgresql',
|
|
648
|
+
state: local.reachable ? 'ready' : 'action-required',
|
|
649
|
+
required: true,
|
|
650
|
+
summary: local.reachable
|
|
651
|
+
? `A local PostgreSQL endpoint is reachable at ${local.host}:${local.port}.`
|
|
652
|
+
: local.status === 'installed-not-running'
|
|
653
|
+
? `PostgreSQL tools were found, but no server is reachable at ${local.host}:${local.port}.`
|
|
654
|
+
: `PostgreSQL was not detected at ${local.host}:${local.port}.`,
|
|
655
|
+
next,
|
|
656
|
+
data: {
|
|
657
|
+
host: local.host,
|
|
658
|
+
port: local.port,
|
|
659
|
+
status: local.status,
|
|
660
|
+
psqlAvailable: local.psqlAvailable,
|
|
661
|
+
psqlVersion: local.psqlVersion,
|
|
662
|
+
install: local.reachable ? null : local.guidance,
|
|
663
|
+
},
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
else {
|
|
667
|
+
checks.push({
|
|
668
|
+
id: 'local-postgresql',
|
|
669
|
+
state: 'not-applicable',
|
|
670
|
+
required: false,
|
|
671
|
+
summary: 'Local PostgreSQL discovery and provisioning are not used by this path.',
|
|
672
|
+
});
|
|
673
|
+
}
|
|
674
|
+
const hasCommand = probes.commandAvailable ?? commandAvailable;
|
|
675
|
+
if (selected === 'native-external' || selected === 'native-container' || selected === 'native-local') {
|
|
676
|
+
const selectedSourceRoot = path.resolve(options.sourceRoot ?? resolveCommunityNativeDefaultSourceRoot(cwd));
|
|
677
|
+
let nativeSourceReady = false;
|
|
678
|
+
let npmPackage = false;
|
|
679
|
+
let nativeSourceError = null;
|
|
680
|
+
if (probes.commandAvailable) {
|
|
681
|
+
nativeSourceReady = options.sourceRoot ? hasCommand('pnpm') : true;
|
|
682
|
+
}
|
|
683
|
+
else {
|
|
684
|
+
try {
|
|
685
|
+
inspectCommunityNativeSource(selectedSourceRoot);
|
|
686
|
+
npmPackage = isCommunityNativeNpmPackageSource(selectedSourceRoot);
|
|
687
|
+
if (!npmPackage)
|
|
688
|
+
buildCommunityPnpmInvocation(selectedSourceRoot, ['--version'], processEnv);
|
|
689
|
+
nativeSourceReady = true;
|
|
690
|
+
}
|
|
691
|
+
catch (error) {
|
|
692
|
+
nativeSourceError = safeReason(error, 'community_native_source_runtime_invalid');
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
const needsDocker = selected === 'native-container';
|
|
696
|
+
const dockerReady = !needsDocker
|
|
697
|
+
? true
|
|
698
|
+
: probes.commandAvailable
|
|
699
|
+
? hasCommand('docker')
|
|
700
|
+
: hasCommand('docker') && dockerRuntimeAvailable(false);
|
|
701
|
+
const runtimeReady = nativeSourceReady && dockerReady;
|
|
702
|
+
checks.push({
|
|
703
|
+
id: 'runtime',
|
|
704
|
+
state: runtimeReady ? 'ready' : 'action-required',
|
|
705
|
+
required: true,
|
|
706
|
+
summary: runtimeReady
|
|
707
|
+
? npmPackage
|
|
708
|
+
? 'The installed npm server runtime is ready; no source build is required.'
|
|
709
|
+
: 'Native source runtime requirements are available.'
|
|
710
|
+
: needsDocker
|
|
711
|
+
? 'The installed npm server runtime (or an explicit source checkout) and a running Docker daemon are required.'
|
|
712
|
+
: 'Install the matching @aopslabs/aops-server package separately, or provide a valid development checkout with pnpm 11.',
|
|
713
|
+
next: runtimeReady
|
|
714
|
+
? undefined
|
|
715
|
+
: needsDocker
|
|
716
|
+
? 'Install and start Docker Desktop/Engine, then retry the npm-server setup.'
|
|
717
|
+
: 'Install the matching npm server package or provide a valid Community source checkout.',
|
|
718
|
+
data: {
|
|
719
|
+
node: process.version,
|
|
720
|
+
sourceRoot: selectedSourceRoot,
|
|
721
|
+
sourceKind: npmPackage ? 'npm-package' : 'source-checkout',
|
|
722
|
+
nativeSource: nativeSourceReady,
|
|
723
|
+
docker: needsDocker ? dockerReady : null,
|
|
724
|
+
error: nativeSourceError,
|
|
725
|
+
},
|
|
726
|
+
});
|
|
727
|
+
}
|
|
728
|
+
else if (selected === 'cli-existing') {
|
|
729
|
+
checks.push({
|
|
730
|
+
id: 'runtime',
|
|
731
|
+
state: 'not-applicable',
|
|
732
|
+
required: false,
|
|
733
|
+
summary: 'The CLI-only path does not require a local server or container runtime.',
|
|
734
|
+
});
|
|
735
|
+
}
|
|
736
|
+
else {
|
|
737
|
+
checks.push({
|
|
738
|
+
id: 'runtime',
|
|
739
|
+
state: 'unknown',
|
|
740
|
+
required: false,
|
|
741
|
+
summary: 'Runtime requirements depend on the selected installation path.',
|
|
742
|
+
});
|
|
743
|
+
}
|
|
744
|
+
const endpointUrl = selected === 'cli-existing'
|
|
745
|
+
? explicitApiBaseUrl ?? selectedTarget?.apiBaseUrl
|
|
746
|
+
: explicitApiBaseUrl ?? `http://127.0.0.1:${localPort}`;
|
|
747
|
+
const endpoint = probes.endpoint ?? (endpointUrl
|
|
748
|
+
? await probeEndpoint({
|
|
749
|
+
apiBaseUrl: endpointUrl,
|
|
750
|
+
targetName: selected === 'cli-existing' ? selectedTarget?.name : undefined,
|
|
751
|
+
timeoutMs: options.timeoutMs ?? 1_500,
|
|
752
|
+
})
|
|
753
|
+
: {
|
|
754
|
+
reachable: false,
|
|
755
|
+
authRequired: null,
|
|
756
|
+
authProvider: null,
|
|
757
|
+
authenticated: null,
|
|
758
|
+
principalUserId: null,
|
|
759
|
+
firstAdminState: null,
|
|
760
|
+
});
|
|
761
|
+
checks.push({
|
|
762
|
+
id: 'host',
|
|
763
|
+
state: endpoint.reachable ? 'ready' : 'action-required',
|
|
764
|
+
required: true,
|
|
765
|
+
summary: endpoint.reachable
|
|
766
|
+
? `${selected === 'cli-existing' ? 'Remote' : 'Local'} AOPS host is reachable.`
|
|
767
|
+
: `${selected === 'cli-existing' ? 'Remote' : 'Local'} AOPS host is not reachable yet.`,
|
|
768
|
+
next: endpoint.reachable
|
|
769
|
+
? undefined
|
|
770
|
+
: selected === 'cli-existing'
|
|
771
|
+
? 'Configure a reachable target or pass `--api-base-url`.'
|
|
772
|
+
: 'Install or start the selected local runtime.',
|
|
773
|
+
data: { apiBaseUrl: endpointUrl ?? null },
|
|
774
|
+
});
|
|
775
|
+
const localPath = selected === 'native-external' || selected === 'native-container' || selected === 'native-local';
|
|
776
|
+
if (localPath) {
|
|
777
|
+
const cockpitPort = native.state?.cockpit?.port ?? resolveCommunityNativeCockpitPort(localPort);
|
|
778
|
+
const cockpitOrigin = `http://127.0.0.1:${cockpitPort}`;
|
|
779
|
+
const cockpit = probes.cockpit ?? (native.status === 'installed'
|
|
780
|
+
? await probeCockpit(cockpitOrigin, options.timeoutMs ?? 1_500)
|
|
781
|
+
: { reachable: false, apiReachable: false, origin: cockpitOrigin });
|
|
782
|
+
const ready = cockpit.reachable && cockpit.apiReachable;
|
|
783
|
+
checks.push({
|
|
784
|
+
id: 'cockpit',
|
|
785
|
+
state: ready ? 'ready' : 'action-required',
|
|
786
|
+
required: true,
|
|
787
|
+
summary: ready
|
|
788
|
+
? `AOPS Cockpit is reachable at ${cockpit.origin} and its API proxy is healthy.`
|
|
789
|
+
: cockpit.reachable
|
|
790
|
+
? `AOPS Cockpit is reachable at ${cockpit.origin}, but its AOPS Server proxy is unavailable.`
|
|
791
|
+
: `AOPS Cockpit is not reachable at ${cockpit.origin}.`,
|
|
792
|
+
next: ready ? undefined : cockpit.reachable
|
|
793
|
+
? 'Run `aops server start`, then verify with `aops cockpit health`.'
|
|
794
|
+
: 'Run `aops cockpit start`, then verify with `aops cockpit health`.',
|
|
795
|
+
data: cockpit,
|
|
796
|
+
});
|
|
797
|
+
}
|
|
798
|
+
else {
|
|
799
|
+
checks.push({
|
|
800
|
+
id: 'cockpit',
|
|
801
|
+
state: 'not-applicable',
|
|
802
|
+
required: false,
|
|
803
|
+
summary: 'The CLI-only path does not manage a local Cockpit process.',
|
|
804
|
+
});
|
|
805
|
+
}
|
|
806
|
+
const targetRequired = selected === 'cli-existing';
|
|
807
|
+
const targetSelected = selectedTarget?.active === true ||
|
|
808
|
+
(selectedTarget !== null && activeTarget?.name === selectedTarget.name);
|
|
809
|
+
const targetProviderMatches = !selectedTarget || endpoint.authProvider === selectedTarget.authProvider;
|
|
810
|
+
const loginReady = targetProviderMatches &&
|
|
811
|
+
(endpoint.authRequired === false ||
|
|
812
|
+
(endpoint.authRequired === true && endpoint.authenticated === true));
|
|
813
|
+
checks.push({
|
|
814
|
+
id: 'target-login',
|
|
815
|
+
state: !targetRequired
|
|
816
|
+
? 'not-applicable'
|
|
817
|
+
: !selectedTarget
|
|
818
|
+
? 'action-required'
|
|
819
|
+
: !targetSelected
|
|
820
|
+
? 'action-required'
|
|
821
|
+
: loginReady
|
|
822
|
+
? 'ready'
|
|
823
|
+
: 'action-required',
|
|
824
|
+
required: targetRequired,
|
|
825
|
+
summary: !targetRequired
|
|
826
|
+
? 'A remote target is not required for this local installation path.'
|
|
827
|
+
: !selectedTarget
|
|
828
|
+
? explicitApiBaseUrl
|
|
829
|
+
? 'The endpoint was probed, but no persistent existing-server target is selected.'
|
|
830
|
+
: 'No existing-server target is configured.'
|
|
831
|
+
: !targetSelected
|
|
832
|
+
? 'The matching existing-server target is saved but is not selected.'
|
|
833
|
+
: loginReady
|
|
834
|
+
? 'The selected target and authenticated session are ready.'
|
|
835
|
+
: endpoint.authRequired === null
|
|
836
|
+
? 'The selected target is reachable, but its authentication contract could not be verified.'
|
|
837
|
+
: !targetProviderMatches
|
|
838
|
+
? 'The selected target authentication provider does not match the server contract.'
|
|
839
|
+
: selectedTarget.hasCredentials
|
|
840
|
+
? 'The selected target has stored credentials, but `/api/auth/me` did not verify them.'
|
|
841
|
+
: 'The selected target requires login credentials.',
|
|
842
|
+
next: targetRequired && !selectedTarget
|
|
843
|
+
? 'Add and select an AOPS target.'
|
|
844
|
+
: targetRequired && !targetSelected
|
|
845
|
+
? `Run \`aops target use ${selectedTarget?.name ?? '<name>'} --apply\`.`
|
|
846
|
+
: targetRequired && endpoint.authRequired === null
|
|
847
|
+
? `Run \`aops target doctor${selectedTarget?.name ? ` ${selectedTarget.name}` : ''} --json\` and repair the server auth contract.`
|
|
848
|
+
: targetRequired && !targetProviderMatches
|
|
849
|
+
? `Run \`aops target doctor${selectedTarget?.name ? ` ${selectedTarget.name}` : ''} --json\` and repair the provider mismatch.`
|
|
850
|
+
: targetRequired && !loginReady
|
|
851
|
+
? `Run \`aops auth login${selectedTarget?.name ? ` --target ${selectedTarget.name}` : ''}\`.`
|
|
852
|
+
: undefined,
|
|
853
|
+
data: selectedTarget
|
|
854
|
+
? {
|
|
855
|
+
name: selectedTarget.name,
|
|
856
|
+
apiBaseUrl: selectedTarget.apiBaseUrl,
|
|
857
|
+
authProvider: endpoint.authProvider ?? selectedTarget.authProvider,
|
|
858
|
+
tlsPolicy: selectedTarget.tlsPolicy ?? null,
|
|
859
|
+
active: targetSelected,
|
|
860
|
+
providerMatches: targetProviderMatches,
|
|
861
|
+
hasCredentials: selectedTarget.hasCredentials,
|
|
862
|
+
authenticated: endpoint.authenticated === true,
|
|
863
|
+
principalUserId: endpoint.principalUserId ?? null,
|
|
864
|
+
}
|
|
865
|
+
: {
|
|
866
|
+
apiBaseUrl: endpointUrl ?? null,
|
|
867
|
+
authProvider: endpoint.authProvider ?? null,
|
|
868
|
+
authenticated: false,
|
|
869
|
+
},
|
|
870
|
+
});
|
|
871
|
+
const assets = probes.agentAssets ?? await inspectSetupAgentAssets(options.agentAssetsProvider);
|
|
872
|
+
checks.push(agentAssetCheck(assets));
|
|
873
|
+
const requiredAction = checks.some((check) => check.required && check.state !== 'ready' && check.state !== 'not-applicable');
|
|
874
|
+
const nextActions = [...new Set(checks.map((check) => check.next).filter((value) => Boolean(value)))];
|
|
875
|
+
return Object.freeze({
|
|
876
|
+
schemaVersion: 1,
|
|
877
|
+
mutationFree: true,
|
|
878
|
+
status: requiredAction ? 'action-required' : 'ready',
|
|
879
|
+
path: Object.freeze({
|
|
880
|
+
id: descriptor?.id ?? null,
|
|
881
|
+
number: descriptor?.number ?? null,
|
|
882
|
+
title: descriptor?.title ?? null,
|
|
883
|
+
source,
|
|
884
|
+
}),
|
|
885
|
+
checks: Object.freeze(checks),
|
|
886
|
+
nextActions: Object.freeze(nextActions),
|
|
887
|
+
});
|
|
888
|
+
}
|