@hivemagnus/magnus-server 0.0.0-stage → 1.0.2
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/.env.example +171 -0
- package/LICENSE +202 -0
- package/README.md +169 -2
- package/agent-version.json +1 -0
- package/dist/ai/HEARTBEAT.md +56 -0
- package/dist/ai/IDENTITY.md +54 -0
- package/dist/ai/MEMORY.md +40 -0
- package/dist/ai/TOOLS.md +538 -0
- package/dist/ai/USER.md +32 -0
- package/dist/cli/args.js +62 -0
- package/dist/cli/env-catalog.js +131 -0
- package/dist/cli/preflight.js +89 -0
- package/dist/cli.js +175 -0
- package/dist/config/cors.js +16 -0
- package/dist/config/env.js +392 -0
- package/dist/config/features.js +16 -0
- package/dist/config/index.js +3 -0
- package/dist/config/listener-security.js +38 -0
- package/dist/config/version.js +31 -0
- package/dist/db/client.js +44 -0
- package/dist/db/generated/prisma/browser.js +18 -0
- package/dist/db/generated/prisma/client.js +35 -0
- package/dist/db/generated/prisma/commonInputTypes.js +11 -0
- package/dist/db/generated/prisma/enums.js +42 -0
- package/dist/db/generated/prisma/internal/class.js +50 -0
- package/dist/db/generated/prisma/internal/prismaNamespace.js +1598 -0
- package/dist/db/generated/prisma/internal/prismaNamespaceBrowser.js +1569 -0
- package/dist/db/generated/prisma/models/A2ATask.js +2 -0
- package/dist/db/generated/prisma/models/AdmissionFence.js +2 -0
- package/dist/db/generated/prisma/models/Agent.js +2 -0
- package/dist/db/generated/prisma/models/AiConversation.js +2 -0
- package/dist/db/generated/prisma/models/AiDashboard.js +2 -0
- package/dist/db/generated/prisma/models/AiMemory.js +2 -0
- package/dist/db/generated/prisma/models/AiMessage.js +2 -0
- package/dist/db/generated/prisma/models/AiScheduledOp.js +2 -0
- package/dist/db/generated/prisma/models/AlertConditionState.js +2 -0
- package/dist/db/generated/prisma/models/AlertRule.js +2 -0
- package/dist/db/generated/prisma/models/AlertViolation.js +2 -0
- package/dist/db/generated/prisma/models/AnomalyEvent.js +2 -0
- package/dist/db/generated/prisma/models/ApplicationObservation.js +2 -0
- package/dist/db/generated/prisma/models/ArdentInstallation.js +2 -0
- package/dist/db/generated/prisma/models/ArdentProfile.js +2 -0
- package/dist/db/generated/prisma/models/ArdentScopeEnrollment.js +2 -0
- package/dist/db/generated/prisma/models/CommandResourceFence.js +2 -0
- package/dist/db/generated/prisma/models/CommandResourceLease.js +2 -0
- package/dist/db/generated/prisma/models/CommandResult.js +2 -0
- package/dist/db/generated/prisma/models/CronJob.js +2 -0
- package/dist/db/generated/prisma/models/CronJobExecution.js +2 -0
- package/dist/db/generated/prisma/models/DeployJob.js +2 -0
- package/dist/db/generated/prisma/models/DriftScan.js +2 -0
- package/dist/db/generated/prisma/models/EngineeringMetricSample.js +2 -0
- package/dist/db/generated/prisma/models/EngineeringRecoveryExercise.js +2 -0
- package/dist/db/generated/prisma/models/ExecutionRun.js +2 -0
- package/dist/db/generated/prisma/models/FilePatchTask.js +2 -0
- package/dist/db/generated/prisma/models/GpuSnapshot.js +2 -0
- package/dist/db/generated/prisma/models/HealthSnapshot.js +2 -0
- package/dist/db/generated/prisma/models/HermesCoreRollout.js +2 -0
- package/dist/db/generated/prisma/models/HermesCoreRolloutHost.js +2 -0
- package/dist/db/generated/prisma/models/HermesCredentialPool.js +2 -0
- package/dist/db/generated/prisma/models/HermesCredentialPoolAttachment.js +2 -0
- package/dist/db/generated/prisma/models/HermesCronJob.js +2 -0
- package/dist/db/generated/prisma/models/HermesHook.js +2 -0
- package/dist/db/generated/prisma/models/HermesInstallation.js +2 -0
- package/dist/db/generated/prisma/models/HermesMcpServer.js +2 -0
- package/dist/db/generated/prisma/models/HermesMcpServerAttachment.js +2 -0
- package/dist/db/generated/prisma/models/HermesMessagingBinding.js +2 -0
- package/dist/db/generated/prisma/models/HermesOperationJob.js +2 -0
- package/dist/db/generated/prisma/models/HermesPluginInstall.js +2 -0
- package/dist/db/generated/prisma/models/HermesProfile.js +2 -0
- package/dist/db/generated/prisma/models/HermesProviderConfig.js +2 -0
- package/dist/db/generated/prisma/models/HermesRegistryClient.js +2 -0
- package/dist/db/generated/prisma/models/HermesRegistryInventory.js +2 -0
- package/dist/db/generated/prisma/models/HermesRegistryReceipt.js +2 -0
- package/dist/db/generated/prisma/models/HermesRegistryRevision.js +2 -0
- package/dist/db/generated/prisma/models/HermesRegistryTarget.js +2 -0
- package/dist/db/generated/prisma/models/HermesRole.js +2 -0
- package/dist/db/generated/prisma/models/HermesRoleApplication.js +2 -0
- package/dist/db/generated/prisma/models/HermesSkillAssignment.js +2 -0
- package/dist/db/generated/prisma/models/HermesToolsetPolicy.js +2 -0
- package/dist/db/generated/prisma/models/IncidentLesson.js +2 -0
- package/dist/db/generated/prisma/models/LibraryCategory.js +2 -0
- package/dist/db/generated/prisma/models/LibraryFile.js +2 -0
- package/dist/db/generated/prisma/models/LogEntry.js +2 -0
- package/dist/db/generated/prisma/models/MagnusReleaseCanary.js +2 -0
- package/dist/db/generated/prisma/models/MaintenanceWindow.js +2 -0
- package/dist/db/generated/prisma/models/ManagedCronDefinition.js +2 -0
- package/dist/db/generated/prisma/models/ManagedCronOccurrence.js +2 -0
- package/dist/db/generated/prisma/models/Node.js +2 -0
- package/dist/db/generated/prisma/models/Notification.js +2 -0
- package/dist/db/generated/prisma/models/NotificationPreference.js +2 -0
- package/dist/db/generated/prisma/models/RemediationExecution.js +2 -0
- package/dist/db/generated/prisma/models/RemediationRule.js +2 -0
- package/dist/db/generated/prisma/models/RemoteCommand.js +2 -0
- package/dist/db/generated/prisma/models/ResourceDrainParticipant.js +2 -0
- package/dist/db/generated/prisma/models/ResourceEnrollment.js +2 -0
- package/dist/db/generated/prisma/models/ResourceParticipantWork.js +2 -0
- package/dist/db/generated/prisma/models/Role.js +2 -0
- package/dist/db/generated/prisma/models/RoleApplication.js +2 -0
- package/dist/db/generated/prisma/models/RolloutTarget.js +2 -0
- package/dist/db/generated/prisma/models/SecurityExportOutbox.js +2 -0
- package/dist/db/generated/prisma/models/SecurityFusionAction.js +2 -0
- package/dist/db/generated/prisma/models/SecurityFusionActionApproval.js +2 -0
- package/dist/db/generated/prisma/models/SecurityFusionCase.js +2 -0
- package/dist/db/generated/prisma/models/SecurityFusionCaseActivity.js +2 -0
- package/dist/db/generated/prisma/models/SecurityFusionCaseEntity.js +2 -0
- package/dist/db/generated/prisma/models/SecurityFusionCaseEvent.js +2 -0
- package/dist/db/generated/prisma/models/SecurityFusionCaseRelationship.js +2 -0
- package/dist/db/generated/prisma/models/SecurityFusionCollector.js +2 -0
- package/dist/db/generated/prisma/models/SecurityFusionCommandOutbox.js +2 -0
- package/dist/db/generated/prisma/models/SecurityFusionDeletionReceipt.js +2 -0
- package/dist/db/generated/prisma/models/SecurityFusionDelivery.js +2 -0
- package/dist/db/generated/prisma/models/SecurityFusionEntity.js +2 -0
- package/dist/db/generated/prisma/models/SecurityFusionEntityAlias.js +2 -0
- package/dist/db/generated/prisma/models/SecurityFusionEvent.js +2 -0
- package/dist/db/generated/prisma/models/SecurityFusionEventEntity.js +2 -0
- package/dist/db/generated/prisma/models/SecurityFusionEvidence.js +2 -0
- package/dist/db/generated/prisma/models/SecurityFusionInvestigation.js +2 -0
- package/dist/db/generated/prisma/models/SecurityFusionNonce.js +2 -0
- package/dist/db/generated/prisma/models/SecurityFusionReceiptChainHead.js +2 -0
- package/dist/db/generated/prisma/models/SecurityFusionRuleVersion.js +2 -0
- package/dist/db/generated/prisma/models/SecurityFusionSourceState.js +2 -0
- package/dist/db/generated/prisma/models/SecurityServiceState.js +2 -0
- package/dist/db/generated/prisma/models/SharedResourceFence.js +2 -0
- package/dist/db/generated/prisma/models/SharedResourceLease.js +2 -0
- package/dist/db/generated/prisma/models/SharedResourceLeaseResource.js +2 -0
- package/dist/db/generated/prisma/models/SkillInstallation.js +2 -0
- package/dist/db/generated/prisma/models/SkillRegistryEntry.js +2 -0
- package/dist/db/generated/prisma/models/SkillTag.js +2 -0
- package/dist/db/generated/prisma/models/SkillVersion.js +2 -0
- package/dist/db/generated/prisma/models/TeamApprovalDecision.js +2 -0
- package/dist/db/generated/prisma/models/User.js +2 -0
- package/dist/db/generated/prisma/models/WebhookDelivery.js +2 -0
- package/dist/db/generated/prisma/models/WebhookEndpoint.js +2 -0
- package/dist/db/generated/prisma/models.js +2 -0
- package/dist/db/index.js +3 -0
- package/dist/db/read-retry.js +35 -0
- package/dist/db/seed.js +348 -0
- package/dist/index.js +162 -0
- package/dist/middleware/auth.js +361 -0
- package/dist/middleware/body-limit.js +14 -0
- package/dist/middleware/correlation-id.js +13 -0
- package/dist/middleware/error-handler.js +28 -0
- package/dist/middleware/iam-token.js +21 -0
- package/dist/middleware/rate-limit.js +78 -0
- package/dist/middleware/request-logger.js +85 -0
- package/dist/middleware/security-headers.js +23 -0
- package/dist/middleware/tracing.js +120 -0
- package/dist/middleware/user-status.js +33 -0
- package/dist/openapi/spec.js +2656 -0
- package/dist/queue/redis.js +20 -0
- package/dist/queue/requeue.js +27 -0
- package/dist/queue/workers.js +342 -0
- package/dist/routes/ai-schedule.js +73 -0
- package/dist/routes/alerts.js +79 -0
- package/dist/routes/anomalies.js +101 -0
- package/dist/routes/ardent.js +82 -0
- package/dist/routes/assistant.js +417 -0
- package/dist/routes/audit.js +138 -0
- package/dist/routes/codex.js +65 -0
- package/dist/routes/commands.js +226 -0
- package/dist/routes/credentials.js +58 -0
- package/dist/routes/cron.js +292 -0
- package/dist/routes/dependencies.js +49 -0
- package/dist/routes/deploy.js +18 -0
- package/dist/routes/discovery.js +168 -0
- package/dist/routes/docs.js +24 -0
- package/dist/routes/drift.js +126 -0
- package/dist/routes/events-stream.js +87 -0
- package/dist/routes/files.js +214 -0
- package/dist/routes/fleet-checks.js +21 -0
- package/dist/routes/health.js +133 -0
- package/dist/routes/heartbeat.js +545 -0
- package/dist/routes/hermes-registry.js +278 -0
- package/dist/routes/hermes.js +61 -0
- package/dist/routes/library.js +402 -0
- package/dist/routes/logs.js +172 -0
- package/dist/routes/managed-cron.js +23 -0
- package/dist/routes/mcp.js +39 -0
- package/dist/routes/metrics.js +187 -0
- package/dist/routes/nodes.js +951 -0
- package/dist/routes/notifications.js +130 -0
- package/dist/routes/ops.js +258 -0
- package/dist/routes/orchestra.js +430 -0
- package/dist/routes/predictions.js +23 -0
- package/dist/routes/profiling.js +68 -0
- package/dist/routes/quick-command.js +72 -0
- package/dist/routes/remediation.js +111 -0
- package/dist/routes/resource-leases.js +297 -0
- package/dist/routes/roles.js +370 -0
- package/dist/routes/rollout.js +124 -0
- package/dist/routes/scan.js +116 -0
- package/dist/routes/security-backup.js +58 -0
- package/dist/routes/security-fusion.js +605 -0
- package/dist/routes/security-integration.js +71 -0
- package/dist/routes/self-update.js +129 -0
- package/dist/routes/shared-resource-leases.js +78 -0
- package/dist/routes/skills.js +485 -0
- package/dist/routes/stats.js +96 -0
- package/dist/routes/team-approvals.js +23 -0
- package/dist/routes/terminal-ticket.js +15 -0
- package/dist/routes/terminal.js +17 -0
- package/dist/routes/topology.js +37 -0
- package/dist/routes/users.js +219 -0
- package/dist/routes/webhooks.js +137 -0
- package/dist/schemas/command-reconciliation.js +14 -0
- package/dist/schemas/commands.js +51 -0
- package/dist/schemas/cron.js +33 -0
- package/dist/schemas/deploy.js +20 -0
- package/dist/schemas/files.js +14 -0
- package/dist/schemas/hermes.js +69 -0
- package/dist/schemas/index.js +11 -0
- package/dist/schemas/library.js +48 -0
- package/dist/schemas/logs.js +35 -0
- package/dist/schemas/managed-cron.js +127 -0
- package/dist/schemas/node-responses.js +87 -0
- package/dist/schemas/node-session-receipt.js +84 -0
- package/dist/schemas/nodes.js +36 -0
- package/dist/schemas/orchestra.js +118 -0
- package/dist/schemas/resource-participant-reconciliation.js +99 -0
- package/dist/schemas/roles.js +54 -0
- package/dist/schemas/shared-resource-lease.js +132 -0
- package/dist/schemas/skills.js +44 -0
- package/dist/schemas/users.js +28 -0
- package/dist/scripts/run-disposable-recovery-exercise.js +39 -0
- package/dist/sdk/index.js +2 -0
- package/dist/sdk/node-api-client.js +474 -0
- package/dist/security/approval-assertion.js +66 -0
- package/dist/security/command-completion-registry.js +35 -0
- package/dist/security/command-request.js +12 -0
- package/dist/security/command-result-signature.js +34 -0
- package/dist/security/managed-http.js +45 -0
- package/dist/security/native-completion-registry.js +44 -0
- package/dist/server.js +178 -0
- package/dist/services/admission-fence.js +71 -0
- package/dist/services/ai-assistant-service.js +2523 -0
- package/dist/services/ai-correlation-service.js +225 -0
- package/dist/services/ai-export-service.js +141 -0
- package/dist/services/ai-insights-service.js +219 -0
- package/dist/services/ai-schedule-service.js +145 -0
- package/dist/services/ai-scratchpad-service.js +222 -0
- package/dist/services/ai-tools.js +1343 -0
- package/dist/services/alert-service.js +313 -0
- package/dist/services/anomaly-detector.js +419 -0
- package/dist/services/api-local-work.js +79 -0
- package/dist/services/application-probes.js +309 -0
- package/dist/services/ardent-scope-enrollment.js +123 -0
- package/dist/services/ardent-service.js +86 -0
- package/dist/services/audit-service.js +308 -0
- package/dist/services/codex-service.js +209 -0
- package/dist/services/command-lifecycle.js +60 -0
- package/dist/services/command-resource-leases.js +305 -0
- package/dist/services/control-host-node.js +242 -0
- package/dist/services/credential-rotation-service.js +294 -0
- package/dist/services/dependency-service.js +145 -0
- package/dist/services/disposable-recovery-runner.js +595 -0
- package/dist/services/docker-group-health.js +121 -0
- package/dist/services/engineering-assurance.js +492 -0
- package/dist/services/engineering-evidence-ledger.js +94 -0
- package/dist/services/engineering-metric-evidence.js +26 -0
- package/dist/services/event-bus.js +227 -0
- package/dist/services/fleet-check-log.js +102 -0
- package/dist/services/forecast-service.js +154 -0
- package/dist/services/health-retention.js +175 -0
- package/dist/services/heartbeat-stream.js +63 -0
- package/dist/services/hermes-catalog-import.js +62 -0
- package/dist/services/hermes-cli-parser.js +671 -0
- package/dist/services/hermes-core-rollout.js +90 -0
- package/dist/services/hermes-dashboard-endpoint.js +13 -0
- package/dist/services/hermes-plugin-source.js +149 -0
- package/dist/services/hermes-registry-service.js +258 -0
- package/dist/services/hermes-service.js +566 -0
- package/dist/services/hivemem-client.js +85 -0
- package/dist/services/host-local-writer.js +82 -0
- package/dist/services/incident-evidence.js +110 -0
- package/dist/services/incident-lessons.js +186 -0
- package/dist/services/llm-providers/anthropic.js +143 -0
- package/dist/services/llm-providers/index.js +7 -0
- package/dist/services/llm-providers/llama-cpp.js +237 -0
- package/dist/services/llm-providers/ollama.js +292 -0
- package/dist/services/llm-providers/openai-compatible.js +200 -0
- package/dist/services/llm-providers/provider-error.js +36 -0
- package/dist/services/llm-providers/registry.js +45 -0
- package/dist/services/llm-providers/types.js +2 -0
- package/dist/services/log-emitter.js +19 -0
- package/dist/services/log-retention.js +66 -0
- package/dist/services/maintenance-service.js +29 -0
- package/dist/services/managed-cron.js +493 -0
- package/dist/services/managed-ssh-admission.js +68 -0
- package/dist/services/monitor-pulse.js +10 -0
- package/dist/services/node-issue-monitor.js +71 -0
- package/dist/services/node-service.js +35 -0
- package/dist/services/notification-retention.js +51 -0
- package/dist/services/notification-service.js +94 -0
- package/dist/services/operational-observations.js +112 -0
- package/dist/services/operations-config.js +67 -0
- package/dist/services/ops-service.js +491 -0
- package/dist/services/orchestra-extras-service.js +370 -0
- package/dist/services/orchestra-resources-service.js +530 -0
- package/dist/services/os-scheduler-writer.js +73 -0
- package/dist/services/password-service.js +22 -0
- package/dist/services/prediction-service.js +284 -0
- package/dist/services/recovery-exercises.js +682 -0
- package/dist/services/remediation-service.js +325 -0
- package/dist/services/resource-enrollment.js +433 -0
- package/dist/services/resource-node-lock.js +18 -0
- package/dist/services/resource-participant-work.js +302 -0
- package/dist/services/role-service.js +388 -0
- package/dist/services/rollout-service.js +440 -0
- package/dist/services/security-export.js +185 -0
- package/dist/services/security-fusion-actions.js +22 -0
- package/dist/services/security-fusion-approvals.js +83 -0
- package/dist/services/security-fusion-evidence-integrity.js +144 -0
- package/dist/services/security-fusion-ingest.js +101 -0
- package/dist/services/security-fusion-outbox.js +185 -0
- package/dist/services/security-fusion-receipt-chain.js +134 -0
- package/dist/services/security-fusion-service.js +751 -0
- package/dist/services/security-fusion-verification.js +50 -0
- package/dist/services/self-update-service.js +200 -0
- package/dist/services/shared-resource-leases.js +413 -0
- package/dist/services/skill-publication-bundle.js +90 -0
- package/dist/services/skill-publication-settlement.js +36 -0
- package/dist/services/skill-publication-state.js +40 -0
- package/dist/services/skill-registry-service.js +327 -0
- package/dist/services/skill-service.js +148 -0
- package/dist/services/ssh-exec-service.js +139 -0
- package/dist/services/ssh-host-verification.js +27 -0
- package/dist/services/tailscale-node-key-service.js +75 -0
- package/dist/services/team-approvals.js +118 -0
- package/dist/services/topology-service.js +138 -0
- package/dist/services/transfer-authority-snapshot.js +151 -0
- package/dist/services/webhook-credential-migration.js +97 -0
- package/dist/services/webhook-security.js +242 -0
- package/dist/services/webhook-service.js +494 -0
- package/dist/types/auth.js +3 -0
- package/dist/types/index.js +3 -0
- package/dist/types/library.js +3 -0
- package/dist/types/notifications.js +3 -0
- package/dist/types/roles.js +3 -0
- package/dist/types/shared-contracts.js +3 -0
- package/dist/utils/env.js +141 -0
- package/dist/utils/heartbeat-peer.js +19 -0
- package/dist/utils/idempotency.js +45 -0
- package/dist/utils/ip.js +25 -0
- package/dist/utils/json-fields.js +94 -0
- package/dist/utils/local-tls.js +39 -0
- package/dist/utils/magnus-home.js +15 -0
- package/dist/utils/node-url.js +59 -0
- package/dist/utils/redact.js +26 -0
- package/dist/utils/safe-equal.js +13 -0
- package/dist/utils/sentinel-nodes.js +16 -0
- package/dist/utils/validate.js +41 -0
- package/package.json +119 -4
- package/prisma/migrations/20261031000000_baseline/migration.sql +2935 -0
- package/prisma/migrations/migration_lock.toml +3 -0
- package/prisma/schema.prisma +2185 -0
- package/prisma.config.ts +14 -0
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
const secret32 = (name, description) => ({ name, group: 'secrets', required: true, secret: true, description: `${description} At least 32 characters; must differ from every other secret.` });
|
|
2
|
+
export const ENV_CATALOG = [
|
|
3
|
+
// ── Core ──
|
|
4
|
+
{ name: 'NODE_ENV', group: 'core', default: 'development', description: 'development, production or test. Production enforces TLS, DATABASE_URL and strict CORS.' },
|
|
5
|
+
{ name: 'LOG_LEVEL', group: 'core', default: 'info', description: 'debug, info, warn, error or fatal.' },
|
|
6
|
+
{ name: 'MAGNUS_HOME', group: 'paths', default: '~/.magnus', description: 'Base directory for server-owned data (audit logs, credential rotation state, backups).' },
|
|
7
|
+
{ name: 'MAGNUS_ENV_FILE', group: 'paths', description: 'Env file loaded at startup (set by `--env-file`). Variables already in the environment win.' },
|
|
8
|
+
{ name: 'AUDIT_LOG_DIR', group: 'paths', default: '$MAGNUS_HOME/audit-logs', description: 'Directory for the append-only audit log files.' },
|
|
9
|
+
{ name: 'CREDENTIAL_ROTATION_STATE_PATH', group: 'paths', default: '$MAGNUS_HOME/state/credential-rotations.json', description: 'Encrypted credential rotation state file.' },
|
|
10
|
+
{ name: 'SKILLS_DIR', group: 'paths', description: 'Skill catalogue directory. Defaults to the bundled @hivemagnus/magnus-skills package.' },
|
|
11
|
+
// ── Database / Redis ──
|
|
12
|
+
{ name: 'DATABASE_URL', group: 'database', requiredWhen: 'NODE_ENV=production', secret: true, default: 'postgresql://magnus@127.0.0.1:5432/magnus (development only)', description: 'PostgreSQL connection string.' },
|
|
13
|
+
{ name: 'DATABASE_POOL_SIZE', group: 'database', default: '20', description: 'Maximum PostgreSQL connections.' },
|
|
14
|
+
{ name: 'REDIS_URL', group: 'redis', default: 'redis://127.0.0.1:6379', secret: true, description: 'Redis connection URL (queues, rate limits, event bus).' },
|
|
15
|
+
{ name: 'REDIS_PREFIX', group: 'redis', default: 'magnus:', description: 'Key prefix for every Redis key.' },
|
|
16
|
+
// ── Listener ──
|
|
17
|
+
{ name: 'CONTROL_API_PORT', group: 'listener', default: '4000', description: 'HTTPS listener port.' },
|
|
18
|
+
{ name: 'CONTROL_API_BIND_HOST', group: 'listener', description: 'Bind address. Cleartext HTTP is always confined to loopback.' },
|
|
19
|
+
{ name: 'CONTROL_API_URL', group: 'listener', default: 'https://localhost:4000', description: 'Public URL of this server, given to agents.' },
|
|
20
|
+
{ name: 'TLS_CERT_PATH', group: 'listener', requiredWhen: 'NODE_ENV=production', description: 'PEM certificate. Relative paths resolve against the working directory.' },
|
|
21
|
+
{ name: 'TLS_KEY_PATH', group: 'listener', requiredWhen: 'NODE_ENV=production', secret: true, description: 'PEM private key matching TLS_CERT_PATH.' },
|
|
22
|
+
{ name: 'ALLOW_INSECURE_HTTP', group: 'listener', default: 'false', description: 'Development only: serve cleartext HTTP on loopback when no certificate is configured.' },
|
|
23
|
+
{ name: 'CORS_ORIGIN', group: 'listener', default: 'https://localhost:3000', description: 'Comma-separated browser origins allowed to call the API (the dashboard).' },
|
|
24
|
+
{ name: 'DASHBOARD_PUBLIC_ORIGIN', group: 'listener', description: 'Public HTTPS origin of the dashboard, used for links in notifications.' },
|
|
25
|
+
// ── Required secrets ──
|
|
26
|
+
secret32('API_SECRET', 'Dashboard-to-server shared secret (x-api-secret).'),
|
|
27
|
+
{ ...secret32('JWT_SECRET', 'HS256 key for locally issued user tokens.'), required: false, requiredWhen: 'IAM_CORE_URL unset' },
|
|
28
|
+
secret32('NODE_MANAGER_SECRET', 'Shared secret agents use to authenticate heartbeats.'),
|
|
29
|
+
secret32('AUDIT_HMAC_SECRET', 'Dedicated audit-chain HMAC key.'),
|
|
30
|
+
secret32('RESULT_SIGNING_SECRET', 'HMAC key that verifies agent command results.'),
|
|
31
|
+
secret32('LOG_INGEST_SIGNING_SECRET', 'HMAC key that verifies agent log uploads.'),
|
|
32
|
+
secret32('CREDENTIAL_ROTATION_KEY', 'Encryption key for the credential rotation store.'),
|
|
33
|
+
secret32('METRICS_TOKEN', 'Bearer token for GET /metrics (Prometheus).'),
|
|
34
|
+
secret32('WEBHOOK_ENCRYPTION_KEY', 'Encryption key for stored webhook credentials.'),
|
|
35
|
+
// ── Agents ──
|
|
36
|
+
{ name: 'ENABLE_AGENT_DISCOVERY', group: 'agents', default: 'false', description: 'Allow agents to self-register through the discovery endpoint.' },
|
|
37
|
+
{ name: 'AGENT_DISCOVERY_SECRET', group: 'agents', secret: true, requiredWhen: 'ENABLE_AGENT_DISCOVERY=true', description: 'Shared secret for agent self-registration.' },
|
|
38
|
+
{ name: 'AGENT_TLS_DOMAIN_SUFFIX', group: 'agents', description: 'DNS suffix used to derive agent certificate hostnames.' },
|
|
39
|
+
{ name: 'MAGNUS_NODE_DNS_SUFFIX', group: 'agents', description: 'Optional DNS suffix (for example a private overlay domain) under which agents report their FQDN.' },
|
|
40
|
+
{ name: 'SSH_USERNAME', group: 'agents', description: 'SSH user for agent install and upgrade over SSH.' },
|
|
41
|
+
{ name: 'SSH_PRIVATE_KEY_PATH', group: 'agents', description: 'SSH private key for SSH_USERNAME (preferred over a password).' },
|
|
42
|
+
{ name: 'SSH_HOST_KEY_PINS_FILE', group: 'agents', description: 'Required for SSH operations: trusted hostname:port to SHA256 host-key fingerprint arrays in a local JSON file.' },
|
|
43
|
+
{ name: 'SSH_PASSWORD', group: 'agents', secret: true, description: 'SSH password for SSH_USERNAME.' },
|
|
44
|
+
{ name: 'MAGNUS_DOCKER_USER', group: 'agents', description: 'Account whose docker group membership the server checks on its own host.' },
|
|
45
|
+
{ name: 'MAGNUS_RESOURCE_LEASES_ENABLED', group: 'agents', default: 'false', description: 'Enable durable node and shared-resource lease coordination.' },
|
|
46
|
+
// ── Hive IAM ──
|
|
47
|
+
{ name: 'IAM_CORE_URL', group: 'iam', description: 'Hive IAM base URL. When set, RS256 tokens from Hive IAM replace local JWTs.' },
|
|
48
|
+
{ name: 'IAM_TOKEN_ISSUER', group: 'iam', requiredWhen: 'IAM_CORE_URL set', description: 'Exact `iss` claim required on IAM access tokens.' },
|
|
49
|
+
{ name: 'IAM_TOKEN_AUDIENCE', group: 'iam', requiredWhen: 'IAM_CORE_URL set', description: 'Exact `aud` claim required on IAM access tokens.' },
|
|
50
|
+
// ── Hive Router ──
|
|
51
|
+
{ name: 'MAGNUS_ROUTER_URL', group: 'router', description: 'OpenAI-compatible model router (for example Hive Router) for managed Hermes Agents. HIVE_ROUTER_URL is accepted as an alias.' },
|
|
52
|
+
{ name: 'MAGNUS_ROUTER_MODEL', group: 'router', description: 'Default model served by the router. HIVE_ROUTER_MODEL is accepted as an alias.' },
|
|
53
|
+
{ name: 'MAGNUS_ROUTER_API_KEY', group: 'router', secret: true, description: 'Router API key. HIVE_ROUTER_API_KEY is accepted as an alias.' },
|
|
54
|
+
{ name: 'HIVE_ROUTER_HEALTH_URL', group: 'router', description: 'Optional router health endpoint.' },
|
|
55
|
+
// ── HiveMem ──
|
|
56
|
+
{ name: 'HIVEMEM_URL', group: 'hivemem', description: 'HiveMem shared agent memory base URL.' },
|
|
57
|
+
{ name: 'HIVEMEM_TOKEN', group: 'hivemem', secret: true, description: 'HiveMem API token.' },
|
|
58
|
+
{ name: 'MAGNUS_LESSONS_HIVEMEM_URL', group: 'hivemem', description: 'Optional HiveMem URL for operational lessons.' },
|
|
59
|
+
{ name: 'MAGNUS_LESSONS_HIVEMEM_TOKEN', group: 'hivemem', secret: true, description: 'Token for MAGNUS_LESSONS_HIVEMEM_URL.' },
|
|
60
|
+
// ── Security Fusion / external security console ──
|
|
61
|
+
{ name: 'SECURITY_FUSION_MODE', group: 'security', default: 'off', description: 'off (feature disabled), local (cases and evidence in this server) or remote (export to an external security console).' },
|
|
62
|
+
{ name: 'SECURITY_FUSION_RECEIPT_SIGNING_KEY', group: 'security', secret: true, requiredWhen: 'SECURITY_FUSION_MODE=local', description: 'Dedicated key that signs Security Fusion deletion receipts (min 32 chars).' },
|
|
63
|
+
{ name: 'SECURITY_FUSION_EVIDENCE_DIR', group: 'security', description: 'Private evidence directory (local mode).' },
|
|
64
|
+
{ name: 'SECURITY_FUSION_CONTAINMENT_COMMAND', group: 'security', description: 'Absolute path of an operator-installed containment helper on managed nodes (local mode release).' },
|
|
65
|
+
{ name: 'MAGNUS_SECURITY_CONSOLE_URL', group: 'security', description: 'External security console URL returned to clients in remote mode.' },
|
|
66
|
+
{ name: 'SECURITY_FUSION_API_URL', group: 'security', description: 'External security console API base URL (HTTPS) for exports and summaries (remote mode).' },
|
|
67
|
+
{ name: 'SECURITY_EXPORT_ENABLED', group: 'security', default: 'false', description: 'Export heartbeat service observations to SECURITY_FUSION_API_URL (remote mode).' },
|
|
68
|
+
{ name: 'SECURITY_EXPORT_SECRET', group: 'security', secret: true, description: 'HMAC key for signed exports to the external console.' },
|
|
69
|
+
{ name: 'SECURITY_SUMMARY_TOKEN', group: 'security', secret: true, description: 'Bearer token for reading the external console summary.' },
|
|
70
|
+
{ name: 'SECURITY_INVENTORY_TOKEN_SHA256', group: 'security', description: 'SHA-256 digest of the bearer the external console uses to read node inventory.' },
|
|
71
|
+
{ name: 'SECURITY_BACKUP_TOKEN_SHA256', group: 'security', description: 'SHA-256 digest of the bearer allowed to upload encrypted security backups.' },
|
|
72
|
+
{ name: 'SECURITY_BACKUP_DIRECTORY', group: 'security', default: '$MAGNUS_HOME/security-backups', description: 'Destination for uploaded security backups.' },
|
|
73
|
+
{ name: 'SECURITY_FUSION_MATTERMOST_URL', group: 'security', description: 'Optional Mattermost server for Security Fusion case notifications.' },
|
|
74
|
+
{ name: 'SECURITY_FUSION_MATTERMOST_BOT_TOKEN', group: 'security', secret: true, description: 'Mattermost bot token.' },
|
|
75
|
+
{ name: 'SECURITY_FUSION_MATTERMOST_CHANNEL_ID', group: 'security', description: 'Mattermost channel ID.' },
|
|
76
|
+
// ── Hermes Agent plugin API ──
|
|
77
|
+
{ name: 'MAGNUS_PLUGIN_API_ENABLED', group: 'plugins', default: 'false', description: 'Accept Hermes Agent plugin credentials on the plugin-scoped routes.' },
|
|
78
|
+
{ name: 'MAGNUS_PLUGIN_READ_TOKEN_DIGESTS_JSON', group: 'plugins', requiredWhen: 'MAGNUS_PLUGIN_API_ENABLED=true', description: 'JSON object {consumerId: sha256(token)} of 1-16 read-only plugin credentials.' },
|
|
79
|
+
{ name: 'MAGNUS_PLUGIN_OPERATOR_TOKEN_SHA256', group: 'plugins', requiredWhen: 'MAGNUS_PLUGIN_API_ENABLED=true', description: 'SHA-256 digest of the server-local plugin operator credential.' },
|
|
80
|
+
{ name: 'MAGNUS_APPROVAL_BRIDGE_CONSUMER', group: 'plugins', description: 'Plugin read consumer allowed to list approval plans and relay signed decisions.' },
|
|
81
|
+
{ name: 'APPROVAL_SIGNER_PUBLIC_KEY_PEM', group: 'plugins', description: 'Ed25519 public key (PEM) that signs approval decisions. _B64 (DER) and _PATH variants are also accepted.' },
|
|
82
|
+
{ name: 'APPROVAL_TELEGRAM_ACTOR_SHA256', group: 'plugins', description: 'Actor binding digest for decisions from the Telegram channel.' },
|
|
83
|
+
{ name: 'APPROVAL_IPHONE_ACTOR_SHA256', group: 'plugins', description: 'Actor binding digest for decisions from the iPhone channel.' },
|
|
84
|
+
{ name: 'MAGNUS_COLLECTOR_TOKEN_SHA256', group: 'plugins', description: 'SHA-256 digest of a read-only external observability collector credential.' },
|
|
85
|
+
{ name: 'MAGNUS_FLEET_CHECK_TOKEN_SHA256', group: 'plugins', description: 'SHA-256 digest of the fleet-check reporter credential.' },
|
|
86
|
+
// ── Hermes registry ──
|
|
87
|
+
{ name: 'MAGNUS_HERMES_REGISTRY_ENABLED', group: 'hermes', default: 'false', description: 'Import the packaged skill catalogue and monitor tracked plugin sources in the background.' },
|
|
88
|
+
{ name: 'MAGNUS_REGISTRY_DEPLOY_HOSTS', group: 'hermes', default: '', description: 'Comma-separated short hostnames (optionally host:stage) allowed to pull registry deployments. Stage 0 hosts are canaries.' },
|
|
89
|
+
{ name: 'MAGNUS_REGISTRY_SUBMIT_ONLY_HOSTS', group: 'hermes', default: '', description: 'Comma-separated short hostnames that may submit registry changes but never pull.' },
|
|
90
|
+
// ── AI assistant ──
|
|
91
|
+
{ name: 'OLLAMA_URL', group: 'ai', default: 'http://127.0.0.1:11434', description: 'Ollama endpoint for the fleet assistant.' },
|
|
92
|
+
{ name: 'OLLAMA_MODEL', group: 'ai', description: 'Ollama model name.' },
|
|
93
|
+
{ name: 'LLAMACPP_URL', group: 'ai', default: 'http://127.0.0.1:8080', description: 'llama.cpp (OpenAI-compatible) endpoint.' },
|
|
94
|
+
{ name: 'OPENAI_API_KEY', group: 'ai', secret: true, description: 'Optional OpenAI provider key.' },
|
|
95
|
+
{ name: 'OPENROUTER_API_KEY', group: 'ai', secret: true, description: 'Optional OpenRouter provider key.' },
|
|
96
|
+
{ name: 'ANTHROPIC_API_KEY', group: 'ai', secret: true, description: 'Optional Anthropic provider key.' },
|
|
97
|
+
// ── Retention ──
|
|
98
|
+
{ name: 'LOG_RETENTION_DAYS', group: 'retention', default: '30', description: 'Days to keep ingested node logs.' },
|
|
99
|
+
{ name: 'HEALTH_SNAPSHOT_RETENTION_DAYS', group: 'retention', default: '7', description: 'Days to keep health snapshots.' },
|
|
100
|
+
{ name: 'NOTIFICATION_MAX_RETENTION_DAYS', group: 'retention', default: '90', description: 'Days to keep notifications.' },
|
|
101
|
+
];
|
|
102
|
+
/** JSON document printed by `magnus-server env-schema`. */
|
|
103
|
+
export function buildEnvSchemaDocument() {
|
|
104
|
+
return {
|
|
105
|
+
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
106
|
+
title: 'magnus-server environment',
|
|
107
|
+
variables: ENV_CATALOG.map(variable => ({
|
|
108
|
+
name: variable.name,
|
|
109
|
+
group: variable.group,
|
|
110
|
+
required: variable.required === true,
|
|
111
|
+
...(variable.requiredWhen ? { requiredWhen: variable.requiredWhen } : {}),
|
|
112
|
+
secret: variable.secret === true,
|
|
113
|
+
...(variable.default !== undefined ? { default: variable.default } : {}),
|
|
114
|
+
description: variable.description,
|
|
115
|
+
})),
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
/** Plain-text table for `magnus-server env-schema --format table`. */
|
|
119
|
+
export function renderEnvTable() {
|
|
120
|
+
const rows = ENV_CATALOG.map(variable => [
|
|
121
|
+
variable.name,
|
|
122
|
+
variable.group,
|
|
123
|
+
variable.required ? 'yes' : variable.requiredWhen ? `if ${variable.requiredWhen}` : 'no',
|
|
124
|
+
variable.default ?? '',
|
|
125
|
+
]);
|
|
126
|
+
const header = ['NAME', 'GROUP', 'REQUIRED', 'DEFAULT'];
|
|
127
|
+
const widths = header.map((title, index) => Math.max(title.length, ...rows.map(row => row[index].length)));
|
|
128
|
+
const line = (cells) => cells.map((cell, index) => cell.padEnd(widths[index])).join(' ').trimEnd();
|
|
129
|
+
return [line(header), line(widths.map(width => '-'.repeat(width))), ...rows.map(line)].join('\n') + '\n';
|
|
130
|
+
}
|
|
131
|
+
//# sourceMappingURL=env-catalog.js.map
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/** Reachability and configuration checks behind `magnus-server check`. */
|
|
2
|
+
import { existsSync, readdirSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import pg from 'pg';
|
|
5
|
+
import { Redis } from 'ioredis';
|
|
6
|
+
function errorMessage(error) {
|
|
7
|
+
return error instanceof Error ? error.message : String(error);
|
|
8
|
+
}
|
|
9
|
+
/** Strip credentials from a connection URL before printing it. */
|
|
10
|
+
export function redactUrl(raw) {
|
|
11
|
+
try {
|
|
12
|
+
const url = new URL(raw);
|
|
13
|
+
if (url.password)
|
|
14
|
+
url.password = '***';
|
|
15
|
+
return url.toString();
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return '<invalid URL>';
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/** Connect to PostgreSQL and compare applied migrations with the packaged ones. */
|
|
22
|
+
export async function checkDatabase(url, migrationsDir) {
|
|
23
|
+
const client = new pg.Client({ connectionString: url, connectionTimeoutMillis: 5_000 });
|
|
24
|
+
try {
|
|
25
|
+
await client.connect();
|
|
26
|
+
await client.query('SELECT 1');
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
await client.end().catch(() => undefined);
|
|
30
|
+
return [{ name: 'database', status: 'fail', message: `cannot reach ${redactUrl(url)}: ${errorMessage(error)}` }];
|
|
31
|
+
}
|
|
32
|
+
const results = [{ name: 'database', status: 'pass', message: `connected to ${redactUrl(url)}` }];
|
|
33
|
+
try {
|
|
34
|
+
const packaged = existsSync(migrationsDir)
|
|
35
|
+
? readdirSync(migrationsDir, { withFileTypes: true })
|
|
36
|
+
.filter(entry => entry.isDirectory() && existsSync(join(migrationsDir, entry.name, 'migration.sql')))
|
|
37
|
+
.map(entry => entry.name)
|
|
38
|
+
: [];
|
|
39
|
+
const table = await client.query(`SELECT to_regclass('public._prisma_migrations') IS NOT NULL AS exists`);
|
|
40
|
+
const applied = table.rows[0]?.exists
|
|
41
|
+
? new Set((await client.query('SELECT migration_name FROM _prisma_migrations WHERE finished_at IS NOT NULL AND rolled_back_at IS NULL')).rows
|
|
42
|
+
.map(row => row.migration_name))
|
|
43
|
+
: new Set();
|
|
44
|
+
const pending = packaged.filter(name => !applied.has(name));
|
|
45
|
+
results.push(pending.length === 0
|
|
46
|
+
? { name: 'migrations', status: 'pass', message: `${packaged.length} applied` }
|
|
47
|
+
: { name: 'migrations', status: 'warn', message: `${pending.length} pending; run \`magnus-server migrate\`` });
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
results.push({ name: 'migrations', status: 'warn', message: `cannot read migration state: ${errorMessage(error)}` });
|
|
51
|
+
}
|
|
52
|
+
finally {
|
|
53
|
+
await client.end().catch(() => undefined);
|
|
54
|
+
}
|
|
55
|
+
return results;
|
|
56
|
+
}
|
|
57
|
+
/** PING Redis with a short timeout. */
|
|
58
|
+
export async function checkRedis(url) {
|
|
59
|
+
const redis = new Redis(url, { lazyConnect: true, connectTimeout: 5_000, maxRetriesPerRequest: 0, enableOfflineQueue: false, retryStrategy: () => null });
|
|
60
|
+
redis.on('error', () => undefined);
|
|
61
|
+
try {
|
|
62
|
+
await redis.connect();
|
|
63
|
+
const reply = await redis.ping();
|
|
64
|
+
return { name: 'redis', status: reply === 'PONG' ? 'pass' : 'fail', message: `${redactUrl(url)} answered ${reply}` };
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
return { name: 'redis', status: 'fail', message: `cannot reach ${redactUrl(url)}: ${errorMessage(error)}` };
|
|
68
|
+
}
|
|
69
|
+
finally {
|
|
70
|
+
redis.disconnect();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/** TLS is mandatory in production; cleartext is a loopback-only development opt-in. */
|
|
74
|
+
export function checkTls(env) {
|
|
75
|
+
const cert = env['TLS_CERT_PATH'];
|
|
76
|
+
const key = env['TLS_KEY_PATH'];
|
|
77
|
+
if (cert && key) {
|
|
78
|
+
const missing = [cert, key].filter(path => !existsSync(path));
|
|
79
|
+
return missing.length === 0
|
|
80
|
+
? { name: 'tls', status: 'pass', message: `certificate ${cert}` }
|
|
81
|
+
: { name: 'tls', status: 'fail', message: `missing ${missing.join(', ')}` };
|
|
82
|
+
}
|
|
83
|
+
if (env['NODE_ENV'] === 'production')
|
|
84
|
+
return { name: 'tls', status: 'fail', message: 'TLS_CERT_PATH and TLS_KEY_PATH are required in production' };
|
|
85
|
+
return (env['ALLOW_INSECURE_HTTP'] ?? '').toLowerCase() === 'true'
|
|
86
|
+
? { name: 'tls', status: 'warn', message: 'no certificate; cleartext HTTP on loopback (development opt-in)' }
|
|
87
|
+
: { name: 'tls', status: 'fail', message: 'no certificate; set TLS_CERT_PATH/TLS_KEY_PATH or ALLOW_INSECURE_HTTP=true for local development' };
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=preflight.js.map
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// magnus-server: npm and container entry point. Server modules read the
|
|
3
|
+
// environment when they are imported, so they load only after the env files
|
|
4
|
+
// named on the command line have been applied.
|
|
5
|
+
import { spawn } from 'node:child_process';
|
|
6
|
+
import { existsSync } from 'node:fs';
|
|
7
|
+
import { createRequire } from 'node:module';
|
|
8
|
+
import { dirname, join, resolve } from 'node:path';
|
|
9
|
+
import { fileURLToPath } from 'node:url';
|
|
10
|
+
import { parseArgs, USAGE, UsageError } from './cli/args.js';
|
|
11
|
+
import { buildEnvSchemaDocument, renderEnvTable } from './cli/env-catalog.js';
|
|
12
|
+
const PACKAGE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
13
|
+
const MIGRATIONS_DIR = join(PACKAGE_ROOT, 'prisma', 'migrations');
|
|
14
|
+
const PRISMA_CONFIG = join(PACKAGE_ROOT, 'prisma.config.ts');
|
|
15
|
+
class FatalError extends Error {
|
|
16
|
+
}
|
|
17
|
+
const fatal = (...lines) => { throw new FatalError(lines.join('\n')); };
|
|
18
|
+
function loadEnvFiles(paths) {
|
|
19
|
+
for (const path of paths) {
|
|
20
|
+
const absolute = resolve(path);
|
|
21
|
+
if (!existsSync(absolute))
|
|
22
|
+
fatal(`Configuration error: env file ${absolute} (--env-file) does not exist.`);
|
|
23
|
+
try {
|
|
24
|
+
process.loadEnvFile(absolute);
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
fatal(`Configuration error: cannot read env file ${absolute}: ${error instanceof Error ? error.message : String(error)}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
// The server entry point loads MAGNUS_ENV_FILE itself; point it at the
|
|
31
|
+
// first explicit file so it never falls back to a package-local .env.
|
|
32
|
+
if (paths[0])
|
|
33
|
+
process.env['MAGNUS_ENV_FILE'] = resolve(paths[0]);
|
|
34
|
+
dropEmptyValues();
|
|
35
|
+
}
|
|
36
|
+
/** Empty assignments (env files, Compose `${VAR:-}`) mean "unset". */
|
|
37
|
+
function dropEmptyValues() {
|
|
38
|
+
for (const [key, value] of Object.entries(process.env))
|
|
39
|
+
if (value === '')
|
|
40
|
+
delete process.env[key];
|
|
41
|
+
}
|
|
42
|
+
async function version() {
|
|
43
|
+
const { CONTROL_API_VERSION } = await import('./config/version.js');
|
|
44
|
+
return CONTROL_API_VERSION;
|
|
45
|
+
}
|
|
46
|
+
/** Run `prisma migrate deploy` against the packaged schema and migrations. */
|
|
47
|
+
async function migrate() {
|
|
48
|
+
if (!process.env['DATABASE_URL'] && process.env['NODE_ENV'] === 'production')
|
|
49
|
+
fatal('Configuration error: DATABASE_URL is required to run migrations in production.');
|
|
50
|
+
let prismaCli;
|
|
51
|
+
try {
|
|
52
|
+
const require = createRequire(import.meta.url);
|
|
53
|
+
prismaCli = join(dirname(require.resolve('prisma/package.json')), 'build', 'index.js');
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return fatal('Migration error: the prisma CLI package is not installed next to magnus-server.');
|
|
57
|
+
}
|
|
58
|
+
const code = await new Promise((resolvePromise, reject) => {
|
|
59
|
+
const child = spawn(process.execPath, [prismaCli, 'migrate', 'deploy', '--config', PRISMA_CONFIG], {
|
|
60
|
+
cwd: PACKAGE_ROOT, env: process.env, stdio: 'inherit',
|
|
61
|
+
});
|
|
62
|
+
child.on('error', reject);
|
|
63
|
+
child.on('close', exitCode => resolvePromise(exitCode ?? 1));
|
|
64
|
+
});
|
|
65
|
+
if (code !== 0)
|
|
66
|
+
fatal(`Migration error: prisma migrate deploy exited with code ${code}.`);
|
|
67
|
+
// Base rows (system actor, sentinel nodes, starter library); idempotent.
|
|
68
|
+
const { getDb, disconnectDb } = await import('./db/client.js');
|
|
69
|
+
const { seedDatabase } = await import('./db/seed.js');
|
|
70
|
+
try {
|
|
71
|
+
await seedDatabase(getDb(), () => undefined);
|
|
72
|
+
console.log(' ✓ Base data present (system actor, sentinel nodes, library)');
|
|
73
|
+
}
|
|
74
|
+
finally {
|
|
75
|
+
await disconnectDb();
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
async function commandCheck() {
|
|
79
|
+
const { listMissingSecrets } = await import('./utils/env.js');
|
|
80
|
+
const { loadEnv } = await import('./config/env.js');
|
|
81
|
+
const { featureFlags } = await import('./config/features.js');
|
|
82
|
+
const { resolveDatabaseUrl } = await import('./db/client.js');
|
|
83
|
+
const { checkDatabase, checkRedis, checkTls } = await import('./cli/preflight.js');
|
|
84
|
+
const results = [];
|
|
85
|
+
const missing = listMissingSecrets();
|
|
86
|
+
results.push(missing.length === 0
|
|
87
|
+
? { name: 'secrets', status: 'pass', message: 'all required secrets are set' }
|
|
88
|
+
: { name: 'secrets', status: 'fail', message: `missing or too short: ${missing.join('; ')}` });
|
|
89
|
+
try {
|
|
90
|
+
loadEnv();
|
|
91
|
+
results.push({ name: 'environment', status: 'pass', message: 'schema validation passed' });
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
results.push({ name: 'environment', status: 'fail', message: (error instanceof Error ? error.message : String(error)).replace(/\s+/g, ' ').slice(0, 600) });
|
|
95
|
+
}
|
|
96
|
+
results.push(checkTls(process.env));
|
|
97
|
+
let databaseUrl = null;
|
|
98
|
+
try {
|
|
99
|
+
databaseUrl = resolveDatabaseUrl();
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
results.push({ name: 'database', status: 'fail', message: error instanceof Error ? error.message : String(error) });
|
|
103
|
+
}
|
|
104
|
+
if (databaseUrl)
|
|
105
|
+
results.push(...await checkDatabase(databaseUrl, MIGRATIONS_DIR));
|
|
106
|
+
results.push(await checkRedis(process.env['REDIS_URL'] ?? 'redis://127.0.0.1:6379'));
|
|
107
|
+
const symbol = { pass: '✓', warn: '!', fail: '✗' };
|
|
108
|
+
console.log(`magnus-server ${await version()} configuration check`);
|
|
109
|
+
for (const result of results)
|
|
110
|
+
console.log(` ${symbol[result.status]} ${result.name.padEnd(12)} ${result.message}`);
|
|
111
|
+
const features = featureFlags();
|
|
112
|
+
const onOff = (value) => (value ? 'on' : 'off');
|
|
113
|
+
console.log('Integrations:');
|
|
114
|
+
console.log(` Hive IAM ${onOff(features.iam)}`);
|
|
115
|
+
console.log(` Hive Router ${onOff(features.router)}`);
|
|
116
|
+
console.log(` HiveMem ${onOff(features.hivemem)}`);
|
|
117
|
+
console.log(` Security Fusion ${features.securityFusion}${features.securityFusion === 'remote' && features.securityConsoleUrl ? ` (${features.securityConsoleUrl})` : ''}`);
|
|
118
|
+
console.log(` Hermes plugin API ${onOff(features.pluginApi)}`);
|
|
119
|
+
console.log(` Hermes registry ${onOff(features.hermesRegistry)}`);
|
|
120
|
+
console.log(` Resource leases ${onOff(features.resourceLeases)}`);
|
|
121
|
+
console.log(` Agent discovery ${onOff(features.agentDiscovery)}`);
|
|
122
|
+
const failures = results.filter(result => result.status === 'fail').length;
|
|
123
|
+
console.log(failures === 0 ? 'Result: OK' : `Result: ${failures} problem(s) found`);
|
|
124
|
+
return failures === 0 ? 0 : 1;
|
|
125
|
+
}
|
|
126
|
+
async function commandStart(options) {
|
|
127
|
+
if (options.migrate)
|
|
128
|
+
await migrate();
|
|
129
|
+
await import('./index.js');
|
|
130
|
+
}
|
|
131
|
+
async function run(argv) {
|
|
132
|
+
const options = parseArgs(argv);
|
|
133
|
+
dropEmptyValues();
|
|
134
|
+
switch (options.command) {
|
|
135
|
+
case 'help':
|
|
136
|
+
process.stdout.write(USAGE);
|
|
137
|
+
return 0;
|
|
138
|
+
case 'version':
|
|
139
|
+
console.log(await version());
|
|
140
|
+
return 0;
|
|
141
|
+
case 'env-schema':
|
|
142
|
+
if (options.format === 'table')
|
|
143
|
+
process.stdout.write(renderEnvTable());
|
|
144
|
+
else
|
|
145
|
+
console.log(JSON.stringify(buildEnvSchemaDocument(), null, 2));
|
|
146
|
+
return 0;
|
|
147
|
+
case 'check':
|
|
148
|
+
loadEnvFiles(options.envFiles);
|
|
149
|
+
return commandCheck();
|
|
150
|
+
case 'migrate':
|
|
151
|
+
loadEnvFiles(options.envFiles);
|
|
152
|
+
await migrate();
|
|
153
|
+
return 0;
|
|
154
|
+
case 'start':
|
|
155
|
+
loadEnvFiles(options.envFiles);
|
|
156
|
+
await commandStart(options);
|
|
157
|
+
return -1; // the server keeps the process alive
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
run(process.argv.slice(2)).then(code => { if (code >= 0)
|
|
161
|
+
process.exit(code); }, (error) => {
|
|
162
|
+
if (error instanceof UsageError) {
|
|
163
|
+
console.error(`magnus-server: ${error.message}. Run \`magnus-server --help\` for usage.`);
|
|
164
|
+
process.exit(2);
|
|
165
|
+
}
|
|
166
|
+
if (error instanceof FatalError) {
|
|
167
|
+
console.error(error.message);
|
|
168
|
+
process.exit(1);
|
|
169
|
+
}
|
|
170
|
+
console.error(`Startup error: ${error instanceof Error ? error.message : String(error)}`);
|
|
171
|
+
if (process.env['MAGNUS_DEBUG'])
|
|
172
|
+
console.error(error);
|
|
173
|
+
process.exit(1);
|
|
174
|
+
});
|
|
175
|
+
//# sourceMappingURL=cli.js.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** Resolve CORS once and reject every production wildcard source. */
|
|
2
|
+
export function resolveCorsConfiguration(env) {
|
|
3
|
+
const production = env['NODE_ENV'] === 'production';
|
|
4
|
+
const configured = env['CORS_ORIGIN']
|
|
5
|
+
?? (production
|
|
6
|
+
? (env['DASHBOARD_ORIGIN'] ?? env['PUBLIC_DASHBOARD_URL'] ?? 'https://magnus')
|
|
7
|
+
: '*');
|
|
8
|
+
const origins = configured.split(',').map((origin) => origin.trim()).filter(Boolean);
|
|
9
|
+
if (production && origins.includes('*')) {
|
|
10
|
+
throw new Error('CORS_ORIGIN cannot contain a wildcard in production');
|
|
11
|
+
}
|
|
12
|
+
if (configured === '*')
|
|
13
|
+
return { origin: '*', credentials: false };
|
|
14
|
+
return { origin: origins, credentials: true };
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=cors.js.map
|