fix(POSTV1-01): 允许非关键外部服务降级启动

This commit is contained in:
suyx
2026-08-05 11:22:42 +08:00
parent 443e8b94f0
commit 99fe07a761
4 changed files with 21 additions and 16 deletions
+6 -6
View File
@@ -1,6 +1,6 @@
import { createConnection } from "node:net"; import { createConnection } from "node:net";
import { RealAmapAdapter } from "./amap-adapter.js"; import { MockAmapAdapter, RealAmapAdapter } from "./amap-adapter.js";
const API_CREDENTIALS = ["Dada/P0A/api/resend", "Dada/P0A/api/amap", "Dada/P0A/admin/pepper"] as const; const API_CREDENTIALS = ["Dada/P0A/api/resend", "Dada/P0A/api/amap", "Dada/P0A/admin/pepper"] as const;
@@ -15,7 +15,7 @@ export async function receiveApiCredentials(input: NodeJS.ReadableStream = proce
if (names.length !== expected.length || names.some((name, index) => name !== expected[index])) { if (names.length !== expected.length || names.some((name, index) => name !== expected[index])) {
throw new Error("API credential channel contains an unexpected credential scope."); throw new Error("API credential channel contains an unexpected credential scope.");
} }
if (expected.some((name) => typeof parsed[name] !== "string" || parsed[name] === "")) { if (expected.some((name) => typeof parsed[name] !== "string")) {
throw new Error("API credential channel contains an invalid credential value."); throw new Error("API credential channel contains an invalid credential value.");
} }
return parsed as Record<(typeof API_CREDENTIALS)[number], string>; return parsed as Record<(typeof API_CREDENTIALS)[number], string>;
@@ -27,12 +27,12 @@ export async function receiveApiCredentials(input: NodeJS.ReadableStream = proce
} }
export function initializeApiCredentialClients(credentials: Record<(typeof API_CREDENTIALS)[number], string>) { export function initializeApiCredentialClients(credentials: Record<(typeof API_CREDENTIALS)[number], string>) {
const configured = API_CREDENTIALS.every((name) => credentials[name].length > 0);
try { try {
if (!configured) throw new Error("API credential client initialization failed."); const adminPepper = credentials["Dada/P0A/admin/pepper"];
if (!adminPepper) throw new Error("admin_pepper_not_configured");
return { return {
adminAllowlistPepper: Buffer.from(credentials["Dada/P0A/admin/pepper"], "utf8"), adminAllowlistPepper: Buffer.from(adminPepper, "utf8"),
amap: new RealAmapAdapter(credentials["Dada/P0A/api/amap"]), amap: credentials["Dada/P0A/api/amap"] ? new RealAmapAdapter(credentials["Dada/P0A/api/amap"]) : new MockAmapAdapter(),
}; };
} finally { } finally {
for (const name of API_CREDENTIALS) credentials[name] = ""; for (const name of API_CREDENTIALS) credentials[name] = "";
+1 -3
View File
@@ -13,7 +13,7 @@ export async function receiveWorkerCredentials(input: NodeJS.ReadableStream = pr
if (names.length !== expected.length || names.some((name, index) => name !== expected[index])) { if (names.length !== expected.length || names.some((name, index) => name !== expected[index])) {
throw new Error("Worker credential channel contains an unexpected credential scope."); throw new Error("Worker credential channel contains an unexpected credential scope.");
} }
if (expected.some((name) => typeof parsed[name] !== "string" || parsed[name] === "")) { if (expected.some((name) => typeof parsed[name] !== "string")) {
throw new Error("Worker credential channel contains an invalid credential value."); throw new Error("Worker credential channel contains an invalid credential value.");
} }
return parsed as Record<(typeof WORKER_CREDENTIALS)[number], string>; return parsed as Record<(typeof WORKER_CREDENTIALS)[number], string>;
@@ -25,9 +25,7 @@ export async function receiveWorkerCredentials(input: NodeJS.ReadableStream = pr
} }
export function initializeWorkerCredentialClient(credentials: Record<(typeof WORKER_CREDENTIALS)[number], string>) { export function initializeWorkerCredentialClient(credentials: Record<(typeof WORKER_CREDENTIALS)[number], string>) {
const configured = WORKER_CREDENTIALS.every((name) => credentials[name].length > 0);
for (const name of WORKER_CREDENTIALS) credentials[name] = ""; for (const name of WORKER_CREDENTIALS) credentials[name] = "";
if (!configured) throw new Error("Worker credential client initialization failed.");
} }
export function attachWorkerSupervisorControl(pipeName: string, shutdown: () => Promise<void> | void) { export function attachWorkerSupervisorControl(pipeName: string, shutdown: () => Promise<void> | void) {
+2 -2
View File
@@ -50,7 +50,7 @@ internal static class CredentialProcessLauncher
var credentials = new Dictionary<string, string>(StringComparer.Ordinal); var credentials = new Dictionary<string, string>(StringComparer.Ordinal);
foreach (var target in CredentialCatalog.RequiredFor(role)) foreach (var target in CredentialCatalog.RequiredFor(role))
{ {
credentials[target] = store.Read(target) ?? throw new MissingCredentialException(target); credentials[target] = store.Read(target) ?? string.Empty;
} }
startInfo.UseShellExecute = false; startInfo.UseShellExecute = false;
@@ -98,7 +98,7 @@ internal static class CredentialProcessLauncher
var credentials = new Dictionary<string, string>(StringComparer.Ordinal); var credentials = new Dictionary<string, string>(StringComparer.Ordinal);
foreach (var target in CredentialCatalog.RequiredFor(role)) foreach (var target in CredentialCatalog.RequiredFor(role))
{ {
credentials[target] = store.Read(target) ?? throw new MissingCredentialException(target); credentials[target] = store.Read(target) ?? string.Empty;
} }
startInfo.UseShellExecute = false; startInfo.UseShellExecute = false;
@@ -1,4 +1,5 @@
using System.Diagnostics; using System.Diagnostics;
using System.Security.Cryptography;
namespace Dada.Supervisor; namespace Dada.Supervisor;
@@ -34,10 +35,7 @@ internal sealed class SupervisorRuntime : IAsyncDisposable
{ {
return SupervisorState.StorageUnavailable; return SupervisorState.StorageUnavailable;
} }
if (CredentialCatalog.RequiredFor(ChildRole.Api).Concat(CredentialCatalog.RequiredFor(ChildRole.Worker)).Any(target => !credentials.IsConfigured(target))) EnsureAdminPepper();
{
return SupervisorState.StartupFailed;
}
var node = Path.Combine(AppContext.BaseDirectory, "runtime", "node.exe"); var node = Path.Combine(AppContext.BaseDirectory, "runtime", "node.exe");
var apiEntry = Path.Combine(AppContext.BaseDirectory, "server", "api.mjs"); var apiEntry = Path.Combine(AppContext.BaseDirectory, "server", "api.mjs");
@@ -52,6 +50,14 @@ internal sealed class SupervisorRuntime : IAsyncDisposable
return TryLog(new StructuredLogEvent("ready", ErrorCategory: "none")) ? SupervisorState.Ready : SupervisorState.StorageUnavailable; return TryLog(new StructuredLogEvent("ready", ErrorCategory: "none")) ? SupervisorState.Ready : SupervisorState.StorageUnavailable;
} }
private void EnsureAdminPepper()
{
if (credentials.IsConfigured(CredentialCatalog.AdminPepper)) return;
var pepper = Convert.ToBase64String(RandomNumberGenerator.GetBytes(32));
credentials.Write(CredentialCatalog.AdminPepper, pepper);
Array.Clear(System.Text.Encoding.UTF8.GetBytes(pepper));
}
private ManagedComponentSupervisor CreateComponent(string node, string entry, ChildRole role, SupervisorState degradedState) private ManagedComponentSupervisor CreateComponent(string node, string entry, ChildRole role, SupervisorState degradedState)
{ {
var component = new ManagedComponentSupervisor(async cancellationToken => var component = new ManagedComponentSupervisor(async cancellationToken =>
@@ -60,6 +66,7 @@ internal sealed class SupervisorRuntime : IAsyncDisposable
startInfo.WorkingDirectory = AppContext.BaseDirectory; startInfo.WorkingDirectory = AppContext.BaseDirectory;
startInfo.Environment["DADA_SQLITE_NATIVE_BINDING"] = Path.Combine(AppContext.BaseDirectory, "server", "native", "better_sqlite3.node"); startInfo.Environment["DADA_SQLITE_NATIVE_BINDING"] = Path.Combine(AppContext.BaseDirectory, "server", "native", "better_sqlite3.node");
startInfo.Environment["DADA_SUPPORT_GATE_ROOT"] = Path.Combine(AppContext.BaseDirectory, "web", "support-gate"); startInfo.Environment["DADA_SUPPORT_GATE_ROOT"] = Path.Combine(AppContext.BaseDirectory, "web", "support-gate");
startInfo.Environment["DADA_WEB_ROOT"] = Path.Combine(AppContext.BaseDirectory, "web");
startInfo.Environment["DADA_INSTANCE_CONFIG_PATH"] = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Dada", "P0A", "config", "instance.json"); startInfo.Environment["DADA_INSTANCE_CONFIG_PATH"] = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Dada", "P0A", "config", "instance.json");
startInfo.ArgumentList.Add(entry); startInfo.ArgumentList.Add(entry);
var child = await ManagedChildProcess.StartAsync(startInfo, role, credentials, cancellationToken); var child = await ManagedChildProcess.StartAsync(startInfo, role, credentials, cancellationToken);