mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-05 14:52:09 +03:00
fix(antigravity): wrap Pro fallback chain in try/catch for timeout resilience (#7290)
* fix(antigravity): wrap executeOnce in try/catch for Pro fallback chain When a Pro-tier candidate times out or throws a network error, the exception now continues to the next candidate instead of aborting the entire chain. Includes diagnostic logging and unit tests. Signed-off-by: Minxi Hou <houminxi@gmail.com> * fix(antigravity): propagate abort signal in Pro fallback catch block Re-throw AbortError and signal.aborted immediately instead of retrying the next candidate. Prevents wasted upstream requests after client disconnect. Signed-off-by: Minxi Hou <houminxi@gmail.com> * fix(mitm): skip DNS modification when sudo unavailable (container) In containers (USER node, no sudo, not root) provisionDnsEntries() now detects the condition up-front and logs a clear message instead of attempting sudo and silently swallowing the error. Adds canElevate() to the injectable deps interface for testability, and supports SKIP_ANTIGRAVITY_DNS=true for explicit opt-out. * fix(antigravity): improve abort detection and fallback error handling Check Error.name === 'AbortError' for non-DOMException environments (polyfills, test harnesses). Capture first 400 from any candidate (not just i===0) so mixed paths surface the 400 instead of a generic error. Return firstResult when last candidate throws, consistent with the all-400 case. * test(mitm): add coverage for container-skip DNS provisioning * test(mitm): harden container-skip DNS test assertions The SKIP_ANTIGRAVITY_DNS=true and canElevate()=false tests used empty agentStates/customHosts, so they could not distinguish 'all steps skipped' from 'only the default step skipped'. Provide non-empty mocks and assert addHostsDns was NOT called. Also add a SKIP_ANTIGRAVITY_DNS=false boundary test confirming the strict === "true" comparison does not block normal provisioning, and verify sudoPassword passthrough in the canElevate=true happy-path test. * refactor(mitm): split provisionDnsEntries below complexity gate provisionDnsEntries() (complexity ~18, this PR's try/catch/log additions pushed it over check-complexity.mjs's threshold of 15) and execute()'s Pro-fallback loop (complexity 27, from wrapping executeOnce() in try/catch for timeout resilience) were both over the gate. Decomposed each into small named helpers, no behavior change: - provision.ts: split into provisionDefaultDns/provisionAgentDns/ provisionCustomHostsDns, each wrapping one best-effort DNS step. - antigravity.ts: extracted the fallback-chain catch/400-handling decisions (handleAntigravityFallbackChainError, isAntigravityAbortError, handleAntigravityFallback400) into a new antigravity/proFallbackChain.ts submodule (pure, no executor instance state), mirroring the existing antigravity/sseCollect.ts submodule pattern. Also fixes the antigravity.ts file-size cap (was pushed to 1854 lines > 1813 frozen ceiling by this PR's own try/catch addition; now 1771). execute/provisionDnsEntries no longer appear with ruleId complexity or max-lines-per-function in the check-complexity.mjs report. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * refactor(antigravity): drop redundant loop continue (cognitive-complexity gate) Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * refactor(antigravity): fold fallback outcome dispatch into switch (cognitive gate) Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Signed-off-by: Minxi Hou <houminxi@gmail.com> Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com> Co-authored-by: HouMinXi <19586012+HouMinXi@users.noreply.github.com> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
This commit is contained in:
@@ -3,7 +3,8 @@
|
||||
* is guarded and unit-testable without spawning the MITM server (#6127 / #6198).
|
||||
*/
|
||||
|
||||
import { addDNSEntry, addDNSEntries } from "./dnsConfig.ts";
|
||||
import { addDNSEntry, addDNSEntries, isSudoAvailable } from "./dnsConfig.ts";
|
||||
import { isRoot } from "../systemCommands.ts";
|
||||
import { ALL_TARGETS } from "../targets/index.ts";
|
||||
import { getAllAgentBridgeStates } from "@/lib/db/agentBridgeState.ts";
|
||||
import { listCustomHosts } from "@/lib/db/inspectorCustomHosts.ts";
|
||||
@@ -23,9 +24,73 @@ export interface DnsProvisionDeps {
|
||||
addHostsDns?: (hosts: string[], sudoPassword: string) => Promise<void>;
|
||||
getAgentStates?: () => ReturnType<typeof getAllAgentBridgeStates>;
|
||||
listEnabledCustomHosts?: () => ReturnType<typeof listCustomHosts>;
|
||||
/** Return true if privileged host-file writes are possible (sudo or root). */
|
||||
canElevate?: () => boolean;
|
||||
logger?: DnsProvisionLogger;
|
||||
}
|
||||
|
||||
/** Fully-resolved dependency set used by the per-step provisioning helpers below. */
|
||||
type ResolvedDnsProvisionDeps = {
|
||||
addDefaultDns: (sudoPassword: string) => Promise<void>;
|
||||
addHostsDns: (hosts: string[], sudoPassword: string) => Promise<void>;
|
||||
getAgentStates: () => ReturnType<typeof getAllAgentBridgeStates>;
|
||||
listEnabledCustomHosts: () => ReturnType<typeof listCustomHosts>;
|
||||
logger: DnsProvisionLogger;
|
||||
};
|
||||
|
||||
/** Antigravity default hosts — best-effort, never throws. */
|
||||
async function provisionDefaultDns(
|
||||
sudoPassword: string,
|
||||
deps: ResolvedDnsProvisionDeps
|
||||
): Promise<void> {
|
||||
try {
|
||||
await deps.addDefaultDns(sudoPassword);
|
||||
} catch (err) {
|
||||
deps.logger.error({ err }, "Failed to add default DNS entries (continuing)");
|
||||
}
|
||||
}
|
||||
|
||||
/** Hosts for agents with `dns_enabled=true` in the DB — best-effort, never throws. */
|
||||
async function provisionAgentDns(
|
||||
sudoPassword: string,
|
||||
deps: ResolvedDnsProvisionDeps
|
||||
): Promise<void> {
|
||||
try {
|
||||
const agentStates = deps.getAgentStates();
|
||||
const agentHostsToAdd: string[] = [];
|
||||
for (const state of agentStates) {
|
||||
if (!state.dns_enabled) continue;
|
||||
const target = ALL_TARGETS.find((t) => t.id === state.agent_id);
|
||||
if (target) {
|
||||
agentHostsToAdd.push(...target.hosts);
|
||||
}
|
||||
}
|
||||
if (agentHostsToAdd.length > 0) {
|
||||
deps.logger.info({ count: agentHostsToAdd.length }, "Adding DNS for agent host(s)...");
|
||||
await deps.addHostsDns(agentHostsToAdd, sudoPassword);
|
||||
}
|
||||
} catch (err) {
|
||||
deps.logger.error({ err }, "Failed to add agent DNS entries (continuing)");
|
||||
}
|
||||
}
|
||||
|
||||
/** Enabled custom hosts — best-effort, never throws. */
|
||||
async function provisionCustomHostsDns(
|
||||
sudoPassword: string,
|
||||
deps: ResolvedDnsProvisionDeps
|
||||
): Promise<void> {
|
||||
try {
|
||||
const customHosts = deps.listEnabledCustomHosts();
|
||||
const customHostNames = customHosts.map((h) => h.host);
|
||||
if (customHostNames.length > 0) {
|
||||
deps.logger.info({ count: customHostNames.length }, "Adding DNS for custom host(s)...");
|
||||
await deps.addHostsDns(customHostNames, sudoPassword);
|
||||
}
|
||||
} catch (err) {
|
||||
deps.logger.error({ err }, "Failed to add custom host DNS entries (continuing)");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provision every AgentBridge DNS entry (Antigravity defaults + agents with
|
||||
* `dns_enabled=true` + enabled custom hosts). **Every step is best-effort**: a failure
|
||||
@@ -45,48 +110,35 @@ export async function provisionDnsEntries(
|
||||
sudoPassword: string,
|
||||
deps: DnsProvisionDeps = {}
|
||||
): Promise<void> {
|
||||
const addDefaultDns = deps.addDefaultDns ?? addDNSEntry;
|
||||
const addHostsDns = deps.addHostsDns ?? addDNSEntries;
|
||||
const getAgentStates = deps.getAgentStates ?? getAllAgentBridgeStates;
|
||||
const listEnabledCustomHosts =
|
||||
deps.listEnabledCustomHosts ?? (() => listCustomHosts({ enabledOnly: true }));
|
||||
const canElevate = deps.canElevate ?? (() => isSudoAvailable() || isRoot());
|
||||
const logger = deps.logger ?? defaultLog;
|
||||
|
||||
// Antigravity default hosts.
|
||||
try {
|
||||
await addDefaultDns(sudoPassword);
|
||||
} catch (err) {
|
||||
logger.error({ err }, "Failed to add default DNS entries (continuing)");
|
||||
// Explicit opt-out: skip all DNS modification when the env var is set.
|
||||
if (process.env.SKIP_ANTIGRAVITY_DNS === "true") {
|
||||
logger.info("Skipping DNS entries - SKIP_ANTIGRAVITY_DNS=true");
|
||||
return;
|
||||
}
|
||||
|
||||
// Collect hosts from agents that have dns_enabled=true in the DB.
|
||||
try {
|
||||
const agentStates = getAgentStates();
|
||||
const agentHostsToAdd: string[] = [];
|
||||
for (const state of agentStates) {
|
||||
if (!state.dns_enabled) continue;
|
||||
const target = ALL_TARGETS.find((t) => t.id === state.agent_id);
|
||||
if (target) {
|
||||
agentHostsToAdd.push(...target.hosts);
|
||||
}
|
||||
}
|
||||
if (agentHostsToAdd.length > 0) {
|
||||
logger.info({ count: agentHostsToAdd.length }, "Adding DNS for agent host(s)...");
|
||||
await addHostsDns(agentHostsToAdd, sudoPassword);
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error({ err }, "Failed to add agent DNS entries (continuing)");
|
||||
// In containers (USER node, no sudo installed, not root) we cannot write
|
||||
// to /etc/hosts. Rather than attempting sudo and swallowing the error,
|
||||
// detect the condition up-front and bail out with a clear message.
|
||||
if (!canElevate()) {
|
||||
logger.info(
|
||||
"Skipping DNS entries - sudo not available and not running as root (likely a container)"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Collect enabled custom hosts.
|
||||
try {
|
||||
const customHosts = listEnabledCustomHosts();
|
||||
const customHostNames = customHosts.map((h) => h.host);
|
||||
if (customHostNames.length > 0) {
|
||||
logger.info({ count: customHostNames.length }, "Adding DNS for custom host(s)...");
|
||||
await addHostsDns(customHostNames, sudoPassword);
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error({ err }, "Failed to add custom host DNS entries (continuing)");
|
||||
}
|
||||
const resolvedDeps: ResolvedDnsProvisionDeps = {
|
||||
addDefaultDns: deps.addDefaultDns ?? addDNSEntry,
|
||||
addHostsDns: deps.addHostsDns ?? addDNSEntries,
|
||||
getAgentStates: deps.getAgentStates ?? getAllAgentBridgeStates,
|
||||
listEnabledCustomHosts:
|
||||
deps.listEnabledCustomHosts ?? (() => listCustomHosts({ enabledOnly: true })),
|
||||
logger,
|
||||
};
|
||||
|
||||
await provisionDefaultDns(sudoPassword, resolvedDeps);
|
||||
await provisionAgentDns(sudoPassword, resolvedDeps);
|
||||
await provisionCustomHostsDns(sudoPassword, resolvedDeps);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user