File size: 5,417 Bytes
6111b2b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
// Server startup script
import initializeCloudSync from "./shared/services/initializeCloudSync";
import { enforceWebRuntimeEnv } from "./lib/env/runtimeEnv";
import { enforceSecrets } from "./shared/utils/secretsValidator";
import { initAuditLog, cleanupExpiredLogs, logAuditEvent } from "./lib/compliance/index";
import { initConsoleInterceptor } from "./lib/consoleInterceptor";
import { startBudgetResetJob } from "./lib/jobs/budgetResetJob";
import { startReasoningCacheCleanupJob } from "./lib/jobs/reasoningCacheCleanupJob";
import { getSettings } from "./lib/db/settings";
import { applyRuntimeSettings } from "./lib/config/runtimeSettings";
import { setSystemPromptConfig } from "@omniroute/open-sse/services/systemPrompt.ts";
import { startRuntimeConfigHotReload } from "./lib/config/hotReload";
import { startSpendBatchWriter } from "./lib/spend/batchWriter";
import { registerDefaultGuardrails } from "./lib/guardrails";
import { ensurePersistentManagementPasswordHash } from "./lib/auth/managementPassword";
import { skillExecutor } from "./lib/skills/executor";
import { registerBuiltinSkills } from "./lib/skills/builtins";
import { createLogger } from "./shared/utils/logger";

const startupLog = createLogger("server-init");

function getErrorMessage(error: unknown) {
  return error instanceof Error ? error.message : String(error);
}

async function startServer() {
  // Trigger request-log layout migration during startup, before serving requests.
  await import("./lib/usage/migrations");

  // Console interceptor: capture all console output to log file (must be first)
  initConsoleInterceptor();

  // FASE-01: Validate required secrets before anything else (fail-fast)
  enforceSecrets();
  enforceWebRuntimeEnv();

  // Compliance: Initialize audit_log table
  try {
    initAuditLog();
    startupLog.info("Audit log table initialized");
  } catch (err) {
    startupLog.warn({ err }, "Could not initialize audit log");
  }

  // Compliance: One-time cleanup of expired logs
  try {
    const cleanup = await cleanupExpiredLogs();
    if (
      cleanup.deletedUsage ||
      cleanup.deletedCallLogs ||
      cleanup.deletedProxyLogs ||
      cleanup.deletedRequestDetailLogs ||
      cleanup.deletedAuditLogs ||
      cleanup.deletedMcpAuditLogs
    ) {
      startupLog.info({ cleanup }, "Expired log cleanup completed");
    }
  } catch (err) {
    startupLog.warn({ err }, "Log cleanup failed");
  }

  startupLog.info("Starting server with cloud sync");

  try {
    let settings = await getSettings();
    const passwordState = await ensurePersistentManagementPasswordHash({
      logger: { log: (message: string) => startupLog.info(message) },
      settings,
      source: "startup",
    });
    settings = passwordState.settings;
    const runtimeChanges = await applyRuntimeSettings(settings, { force: true, source: "startup" });
    if (runtimeChanges.length > 0) {
      startupLog.info(
        { sections: runtimeChanges.map((entry) => entry.section) },
        "Runtime settings hydrated"
      );
    }

    // Restore the Global System Prompt into the in-memory config. It lives in the
    // `settings.systemPrompt` key but is NOT covered by applyRuntimeSettings, so without
    // this the toggle/prompt revert to defaults on every restart (#2470).
    if (settings.systemPrompt) {
      setSystemPromptConfig(settings.systemPrompt);
      startupLog.info("Global System Prompt restored from settings");
    }

    // Initialize cloud sync
    startSpendBatchWriter();
    registerDefaultGuardrails();
    registerBuiltinSkills(skillExecutor);
    startupLog.info("Spend batch writer started");
    startupLog.info("Guardrail registry initialized");
    startupLog.info("Builtin skill handlers registered");

    // Load active plugins on startup so they survive restarts
    try {
      const { pluginManager } = await import("./lib/plugins/manager");
      await pluginManager.loadAll();
      startupLog.info("Plugin manager loaded active plugins");
    } catch (err) {
      startupLog.warn({ err }, "Plugin manager loadAll failed (non-fatal)");
    }

    await initializeCloudSync();
    startBudgetResetJob();
    startReasoningCacheCleanupJob();
    startRuntimeConfigHotReload();
    startupLog.info("Server started with cloud sync initialized");

    // Log server start event to audit log
    logAuditEvent({
      action: "server.start",
      actor: "system",
      target: "server-runtime",
      resourceType: "maintenance",
      status: "success",
      details: { timestamp: new Date().toISOString() },
    });
  } catch (error) {
    startupLog.error({ err: error }, "Error initializing cloud sync");
    process.exit(1);
  }

  // Pricing sync: opt-in external pricing data (non-blocking, never fatal)
  if (process.env.PRICING_SYNC_ENABLED === "true") {
    try {
      const { initPricingSync } = await import("./lib/pricingSync");
      await initPricingSync();
    } catch (err) {
      startupLog.warn({ error: getErrorMessage(err) }, "Pricing sync could not initialize");
    }
  }
}

// Start the server initialization
startServer().catch((err) => {
  startupLog.error({ err }, "Server initialization failed");
  process.exit(1);
});

// Export for use as module if needed
export default startServer;