switchroom 0.18.29 → 0.18.30
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/bin/handoff-briefing.sh +8 -2
- package/dist/agent-scheduler/index.js +111 -7
- package/dist/auth-broker/index.js +154 -16
- package/dist/cli/autoaccept-poll.js +8 -3
- package/dist/cli/drive-write-pretool.mjs +8 -3
- package/dist/cli/ms-365-write-pretool.mjs +158 -11
- package/dist/cli/notion-write-pretool.mjs +103 -4
- package/dist/cli/switchroom.js +2074 -1585
- package/dist/host-control/main.js +110 -13
- package/dist/vault/approvals/kernel-server.js +116 -13
- package/dist/vault/broker/server.js +314 -145
- package/package.json +3 -3
- package/profiles/_base/start.sh.hbs +73 -20
- package/telegram-plugin/dist/bridge/bridge.js +71 -47
- package/telegram-plugin/dist/gateway/gateway.js +560 -96
- package/telegram-plugin/dist/server.js +89 -64
- package/telegram-plugin/gateway/gateway.ts +212 -17
- package/telegram-plugin/gateway/model-command.ts +104 -0
- package/telegram-plugin/gateway/session-model-file.ts +40 -0
- package/telegram-plugin/gateway/unhandled-message.ts +177 -0
- package/telegram-plugin/llm-error-present.ts +24 -0
- package/telegram-plugin/model-unavailable.ts +55 -0
- package/telegram-plugin/operator-events.ts +113 -0
- package/telegram-plugin/pending-user-notice.ts +88 -0
- package/telegram-plugin/shared/local-time.ts +43 -0
- package/telegram-plugin/tests/catch-all-forwarded-history.test.ts +103 -0
- package/telegram-plugin/tests/catch-all-unhandled-message.test.ts +264 -0
- package/telegram-plugin/tests/gateway-session-model-relaunch.test.ts +26 -2
- package/telegram-plugin/tests/litellm-proxy-auth-misconfig.test.ts +278 -0
- package/telegram-plugin/tests/local-time.test.ts +68 -1
- package/telegram-plugin/tests/model-command.test.ts +133 -0
- package/telegram-plugin/tests/session-model-file.test.ts +23 -0
- package/vendor/hindsight-memory/scripts/backfill_transcripts.py +399 -2
- package/vendor/hindsight-memory/scripts/lib/client.py +47 -0
- package/vendor/hindsight-memory/scripts/lib/content.py +53 -1
- package/vendor/hindsight-memory/scripts/lib/turnlog.py +450 -0
- package/vendor/hindsight-memory/scripts/tests/test_backfill_from_logs.py +467 -0
- package/vendor/hindsight-memory/tests/test_content.py +35 -0
|
@@ -4013,7 +4013,7 @@ var init_zod = __esm(() => {
|
|
|
4013
4013
|
});
|
|
4014
4014
|
|
|
4015
4015
|
// src/config/schema.ts
|
|
4016
|
-
var CodeRepoEntrySchema, AgentBindMountSchema, HttpDiffPollSchema, PollSpecSchema, TelegramMessageActionSchema, WebhookActionSchema, ActionSpecSchema, ScheduleEntrySchema, AgentSoulSchema, AgentToolsSchema, AgentMemorySchema, HookEntrySchema, AgentHooksSchema, SubagentSchema, SessionSchema, SessionContinuitySchema, webhookDispatchRule, TelegramChannelSchema, ChannelsSchema, TIMEZONE_REGEX, ApproverIdSchema, GoogleWorkspaceTierSchema, GoogleWorkspaceConfigSchema, LiteLLMConfigSchema, HindsightPerOpLlmSchema, HindsightConfigSchema, MicrosoftWorkspaceConfigSchema, NotionWorkspaceConfigSchema, AgentGoogleWorkspaceConfigSchema, AgentMicrosoftWorkspaceConfigSchema, AgentNotionWorkspaceConfigSchema, ReactionsSchema, ReactionDispatchSchema, ReleaseBlock, NetworkIsolationSchema, servesField, knowsField, profileFields, ProfileSchema, _omitExtends, defaultsFields, AgentDefaultsSchema, AgentSchema, TelegramConfigSchema, MemoryBackendConfigSchema, VaultConfigSchema, QuotaConfigSchema, AutoReleaseCheckSchema, HostControlConfigSchema, WebServiceConfigSchema, FleetHealthConfigSchema, HostdConfigSchema, CronEgressSchema, CronConfigSchema, UserSchema, SwitchroomConfigSchema;
|
|
4016
|
+
var CodeRepoEntrySchema, AgentBindMountSchema, HttpDiffPollSchema, PollSpecSchema, TelegramMessageActionSchema, WebhookActionSchema, ActionSpecSchema, ScheduleEntrySchema, AgentSoulSchema, AgentToolsSchema, AgentMemorySchema, HookEntrySchema, AgentHooksSchema, SubagentSchema, SessionSchema, SessionContinuitySchema, webhookDispatchRule, TelegramChannelSchema, ChannelsSchema, TIMEZONE_REGEX, ApproverIdSchema, GoogleWorkspaceTierSchema, GoogleWorkspaceConfigSchema, LiteLLMConfigSchema, HindsightPerOpLlmSchema, HindsightConfigSchema, MicrosoftWorkspaceConfigSchema, NotionWorkspaceConfigSchema, AgentGoogleWorkspaceConfigSchema, MicrosoftAccountEmailSchema, MicrosoftToolTokenSchema, MicrosoftAccountBindingSchema, AgentMicrosoftWorkspaceConfigSchema, AgentNotionWorkspaceConfigSchema, ReactionsSchema, ReactionDispatchSchema, ReleaseBlock, NetworkIsolationSchema, servesField, knowsField, profileFields, ProfileSchema, _omitExtends, defaultsFields, AgentDefaultsSchema, AgentSchema, TelegramConfigSchema, MemoryBackendConfigSchema, VaultConfigSchema, QuotaConfigSchema, AutoReleaseCheckSchema, HostControlConfigSchema, WebServiceConfigSchema, FleetHealthConfigSchema, HostdConfigSchema, CronEgressSchema, CronConfigSchema, UserSchema, SwitchroomConfigSchema;
|
|
4017
4017
|
var init_schema = __esm(() => {
|
|
4018
4018
|
init_zod();
|
|
4019
4019
|
CodeRepoEntrySchema = exports_external.object({
|
|
@@ -4411,11 +4411,52 @@ var init_schema = __esm(() => {
|
|
|
4411
4411
|
approvers: exports_external.array(ApproverIdSchema).min(1).optional().describe("Per-agent approver override. When set, replaces (does not extend) " + "the top-level drive.approvers list for this agent's onboarding card."),
|
|
4412
4412
|
tier: GoogleWorkspaceTierSchema.optional().describe("Per-agent tier override (RFC G Phase 1). When set, replaces the " + "top-level google_workspace.tier for this agent. Common case: most " + "agents on `core`, one specialist on `extended` for Slides access.")
|
|
4413
4413
|
}).optional();
|
|
4414
|
+
MicrosoftAccountEmailSchema = exports_external.string().regex(/^[^@\s:]+@[^@\s:]+\.[^@\s:]+$/, {
|
|
4415
|
+
message: "microsoft_workspace.account must be a Microsoft account email like " + "'alice@outlook.com' or 'alice@contoso.com' (colons not allowed)"
|
|
4416
|
+
}).transform((v) => v.trim().toLowerCase());
|
|
4417
|
+
MicrosoftToolTokenSchema = exports_external.string().min(1).regex(/^[a-z0-9-]+$/, {
|
|
4418
|
+
message: "microsoft_workspace tools[] tokens are forwarded to softeria's " + "--enabled-tools regex; each must be lowercase kebab-case " + "([a-z0-9-]+, e.g. 'mail', 'calendar', 'send-mail') so an unescaped " + "regex metacharacter can't crash the launcher"
|
|
4419
|
+
});
|
|
4420
|
+
MicrosoftAccountBindingSchema = exports_external.object({
|
|
4421
|
+
account: MicrosoftAccountEmailSchema.describe("The Microsoft account this binding uses. Must be a key in top-level " + "`microsoft_accounts:` with this agent in its `enabled_for[]`."),
|
|
4422
|
+
tools: exports_external.array(MicrosoftToolTokenSchema).min(1).optional().describe("Per-account tool allowlist → softeria `--enabled-tools <regex>` " + "(tokens joined with `|`). Omitted = all tools exposed for this account."),
|
|
4423
|
+
org_mode: exports_external.boolean().optional().describe("Per-binding org_mode override (RFC #1873 §6.4).")
|
|
4424
|
+
});
|
|
4414
4425
|
AgentMicrosoftWorkspaceConfigSchema = exports_external.object({
|
|
4415
|
-
account:
|
|
4416
|
-
|
|
4417
|
-
|
|
4418
|
-
|
|
4426
|
+
account: MicrosoftAccountEmailSchema.optional().describe("RFC #1873: the Microsoft account this agent uses for the M365 MCP. " + "Must be a key in top-level `microsoft_accounts:` with this agent " + "listed in its `enabled_for[]`. Read by the auth-broker " + "(get-credentials, provider=microsoft) and by the scaffold to " + "decide whether to emit the `ms-365` MCP entry. Normalized to " + "lowercase so it matches the microsoft_accounts key (which is " + "also normalized). Mutually exclusive with `accounts` (plural)."),
|
|
4427
|
+
tools: exports_external.array(MicrosoftToolTokenSchema).min(1).optional().describe("Per-account tool allowlist for the SINGULAR `account` form → " + "softeria `--enabled-tools <regex>`. Omitted = all tools. Only " + "valid with the singular `account`; using it together with the " + "plural `accounts` (which carries per-binding `tools`) is an error."),
|
|
4428
|
+
org_mode: exports_external.boolean().optional().describe("Per-agent org_mode override (RFC #1873 §6.4). When set, replaces " + "the top-level microsoft_workspace.org_mode for this agent. " + "Defaults to top-level value (which defaults to false)."),
|
|
4429
|
+
accounts: exports_external.array(MicrosoftAccountBindingSchema).min(1).optional().describe("Plural multi-account form: bind MULTIPLE Microsoft accounts to " + "this agent, each with its own tool scope. Mutually exclusive with " + "the singular `account`. Each account gets its own `ms-365-<slug>` " + "MCP server.")
|
|
4430
|
+
}).superRefine((v, ctx) => {
|
|
4431
|
+
const hasSingular = v.account !== undefined;
|
|
4432
|
+
const hasPlural = v.accounts !== undefined;
|
|
4433
|
+
if (hasSingular && hasPlural) {
|
|
4434
|
+
ctx.addIssue({
|
|
4435
|
+
code: exports_external.ZodIssueCode.custom,
|
|
4436
|
+
message: "microsoft_workspace: use EITHER `account` (singular) OR " + "`accounts` (plural array), not both",
|
|
4437
|
+
path: ["accounts"]
|
|
4438
|
+
});
|
|
4439
|
+
}
|
|
4440
|
+
if (hasPlural && v.tools !== undefined) {
|
|
4441
|
+
ctx.addIssue({
|
|
4442
|
+
code: exports_external.ZodIssueCode.custom,
|
|
4443
|
+
message: "microsoft_workspace: block-level `tools` applies to the singular " + "`account` only; with `accounts` put `tools` inside each binding",
|
|
4444
|
+
path: ["tools"]
|
|
4445
|
+
});
|
|
4446
|
+
}
|
|
4447
|
+
if (hasPlural) {
|
|
4448
|
+
const seen = new Set;
|
|
4449
|
+
for (const b of v.accounts) {
|
|
4450
|
+
if (seen.has(b.account)) {
|
|
4451
|
+
ctx.addIssue({
|
|
4452
|
+
code: exports_external.ZodIssueCode.custom,
|
|
4453
|
+
message: `microsoft_workspace: duplicate account '${b.account}' in accounts[]`,
|
|
4454
|
+
path: ["accounts"]
|
|
4455
|
+
});
|
|
4456
|
+
}
|
|
4457
|
+
seen.add(b.account);
|
|
4458
|
+
}
|
|
4459
|
+
}
|
|
4419
4460
|
}).optional();
|
|
4420
4461
|
AgentNotionWorkspaceConfigSchema = exports_external.object({
|
|
4421
4462
|
databases: exports_external.array(exports_external.string().regex(/^[a-z0-9][a-z0-9_-]{0,62}$/, {
|
|
@@ -4790,6 +4831,26 @@ var init_schema = __esm(() => {
|
|
|
4790
4831
|
for (const [name, a] of Object.entries(cfg.agents ?? {})) {
|
|
4791
4832
|
checkServes(a.serves, ["agents", name, "serves"]);
|
|
4792
4833
|
}
|
|
4834
|
+
const microsoftAccounts = cfg.microsoft_accounts;
|
|
4835
|
+
for (const [name, a] of Object.entries(cfg.agents ?? {})) {
|
|
4836
|
+
const mw = a.microsoft_workspace;
|
|
4837
|
+
const accounts = mw?.accounts;
|
|
4838
|
+
if (!accounts)
|
|
4839
|
+
continue;
|
|
4840
|
+
accounts.forEach((b, i) => {
|
|
4841
|
+
const acct = b?.account?.trim().toLowerCase();
|
|
4842
|
+
if (!acct)
|
|
4843
|
+
return;
|
|
4844
|
+
const enabledFor = microsoftAccounts?.[acct]?.enabled_for ?? [];
|
|
4845
|
+
if (!enabledFor.includes(name)) {
|
|
4846
|
+
ctx.addIssue({
|
|
4847
|
+
code: exports_external.ZodIssueCode.custom,
|
|
4848
|
+
message: `agent '${name}' binds Microsoft account '${acct}' but is not in ` + `microsoft_accounts['${acct}'].enabled_for[] — operator must run ` + `\`switchroom auth microsoft enable ${acct} ${name}\``,
|
|
4849
|
+
path: ["agents", name, "microsoft_workspace", "accounts", i, "account"]
|
|
4850
|
+
});
|
|
4851
|
+
}
|
|
4852
|
+
});
|
|
4853
|
+
}
|
|
4793
4854
|
});
|
|
4794
4855
|
});
|
|
4795
4856
|
|
|
@@ -5184,6 +5245,25 @@ var init_merge = __esm(() => {
|
|
|
5184
5245
|
})(mergeAgentConfig ||= {});
|
|
5185
5246
|
});
|
|
5186
5247
|
|
|
5248
|
+
// src/config/timezone.ts
|
|
5249
|
+
function isResolvableTimezone(zone) {
|
|
5250
|
+
try {
|
|
5251
|
+
new Intl.DateTimeFormat("en-US", { timeZone: zone });
|
|
5252
|
+
return true;
|
|
5253
|
+
} catch {
|
|
5254
|
+
return false;
|
|
5255
|
+
}
|
|
5256
|
+
}
|
|
5257
|
+
var CONTAINER_DEFAULT_UTC_ZONES;
|
|
5258
|
+
var init_timezone = __esm(() => {
|
|
5259
|
+
CONTAINER_DEFAULT_UTC_ZONES = new Set([
|
|
5260
|
+
"UTC",
|
|
5261
|
+
"Etc/UTC",
|
|
5262
|
+
"Etc/Universal",
|
|
5263
|
+
"Universal"
|
|
5264
|
+
]);
|
|
5265
|
+
});
|
|
5266
|
+
|
|
5187
5267
|
// node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/nodes/identity.js
|
|
5188
5268
|
var require_identity = __commonJS((exports) => {
|
|
5189
5269
|
var ALIAS = Symbol.for("yaml.alias");
|
|
@@ -12492,8 +12572,30 @@ function loadConfig(configPath) {
|
|
|
12492
12572
|
if (notionIssues.length > 0) {
|
|
12493
12573
|
throw new ConfigError(`Invalid notion_workspace configuration in ${filePath}`, notionIssues);
|
|
12494
12574
|
}
|
|
12575
|
+
validateAllTimezones(config, filePath);
|
|
12495
12576
|
return config;
|
|
12496
12577
|
}
|
|
12578
|
+
function validateAllTimezones(config, filePath) {
|
|
12579
|
+
const issues = [];
|
|
12580
|
+
const check = (zone, where) => {
|
|
12581
|
+
if (zone == null)
|
|
12582
|
+
return;
|
|
12583
|
+
if (!isResolvableTimezone(zone)) {
|
|
12584
|
+
issues.push(` ${where}: "${zone}" is not a resolvable IANA timezone ` + `(shape is valid but no such zone exists — check for a typo, ` + `e.g. "Australia/Melbourne", "America/New_York", "UTC").`);
|
|
12585
|
+
}
|
|
12586
|
+
};
|
|
12587
|
+
check(config.switchroom?.timezone, "switchroom.timezone");
|
|
12588
|
+
check(config.defaults?.timezone, "defaults.timezone");
|
|
12589
|
+
for (const [profileName, profile] of Object.entries(config.profiles ?? {})) {
|
|
12590
|
+
check(profile?.timezone, `profiles.${profileName}.timezone`);
|
|
12591
|
+
}
|
|
12592
|
+
for (const [agentName, agentRaw] of Object.entries(config.agents)) {
|
|
12593
|
+
check(agentRaw?.timezone, `agents.${agentName}.timezone`);
|
|
12594
|
+
}
|
|
12595
|
+
if (issues.length > 0) {
|
|
12596
|
+
throw new ConfigError(`Invalid timezone configuration in ${filePath}`, issues);
|
|
12597
|
+
}
|
|
12598
|
+
}
|
|
12497
12599
|
function validateAllCronTopicAliases(config, filePath) {
|
|
12498
12600
|
const issues = [];
|
|
12499
12601
|
for (const [agentName, agentRaw] of Object.entries(config.agents)) {
|
|
@@ -12536,6 +12638,7 @@ var init_loader = __esm(() => {
|
|
|
12536
12638
|
init_paths();
|
|
12537
12639
|
init_overlay_loader();
|
|
12538
12640
|
init_merge();
|
|
12641
|
+
init_timezone();
|
|
12539
12642
|
import_yaml2 = __toESM(require_dist(), 1);
|
|
12540
12643
|
ConfigError = class ConfigError extends Error {
|
|
12541
12644
|
details;
|
|
@@ -18109,7 +18212,7 @@ var require_lib = __commonJS((exports, module) => {
|
|
|
18109
18212
|
|
|
18110
18213
|
// src/vault/broker/server.ts
|
|
18111
18214
|
import * as net from "node:net";
|
|
18112
|
-
import { mkdirSync as
|
|
18215
|
+
import { mkdirSync as mkdirSync7, chmodSync as chmodSync5, chownSync, existsSync as existsSync12, readFileSync as readFileSync10, readdirSync as readdirSync4, statSync as statSync6, unlinkSync as unlinkSync6, writeFileSync as writeFileSync3, renameSync as renameSync8 } from "node:fs";
|
|
18113
18216
|
|
|
18114
18217
|
// src/agents/compose.ts
|
|
18115
18218
|
init_schema();
|
|
@@ -18473,14 +18576,7 @@ init_merge();
|
|
|
18473
18576
|
|
|
18474
18577
|
// src/agents/scaffold.ts
|
|
18475
18578
|
init_merge();
|
|
18476
|
-
|
|
18477
|
-
// src/config/timezone.ts
|
|
18478
|
-
var CONTAINER_DEFAULT_UTC_ZONES = new Set([
|
|
18479
|
-
"UTC",
|
|
18480
|
-
"Etc/UTC",
|
|
18481
|
-
"Etc/Universal",
|
|
18482
|
-
"Universal"
|
|
18483
|
-
]);
|
|
18579
|
+
init_timezone();
|
|
18484
18580
|
|
|
18485
18581
|
// src/cli/agent-config.ts
|
|
18486
18582
|
import { join } from "node:path";
|
|
@@ -19531,42 +19627,108 @@ var DOCKER_HOOKS_PATH = `${DOCKER_TELEGRAM_PLUGIN_PATH}/hooks`;
|
|
|
19531
19627
|
|
|
19532
19628
|
// src/agents/compose.ts
|
|
19533
19629
|
init_merge();
|
|
19630
|
+
init_timezone();
|
|
19534
19631
|
|
|
19535
19632
|
// src/setup/host-capabilities.ts
|
|
19536
19633
|
init_paths();
|
|
19537
19634
|
|
|
19635
|
+
// src/vault/grants-db-path.ts
|
|
19636
|
+
import * as os from "node:os";
|
|
19637
|
+
import * as path from "node:path";
|
|
19638
|
+
import * as fs from "node:fs";
|
|
19639
|
+
var GRANTS_DB_DIRNAME = "vault-broker";
|
|
19640
|
+
var GRANTS_DB_FILENAME = "vault-grants.db";
|
|
19641
|
+
var GRANTS_DB_CONTAINER_DIR = `/root/.switchroom/${GRANTS_DB_DIRNAME}`;
|
|
19642
|
+
var GRANTS_DB_CONTAINER_PATH = `${GRANTS_DB_CONTAINER_DIR}/${GRANTS_DB_FILENAME}`;
|
|
19643
|
+
function getGrantsDbDir(home2 = os.homedir()) {
|
|
19644
|
+
return path.join(home2, ".switchroom", GRANTS_DB_DIRNAME);
|
|
19645
|
+
}
|
|
19646
|
+
function getGrantsDbPath(home2 = os.homedir()) {
|
|
19647
|
+
return path.join(getGrantsDbDir(home2), GRANTS_DB_FILENAME);
|
|
19648
|
+
}
|
|
19649
|
+
function getLegacyGrantsDbPath(newDbPath = getGrantsDbPath()) {
|
|
19650
|
+
return path.join(path.dirname(path.dirname(newDbPath)), GRANTS_DB_FILENAME);
|
|
19651
|
+
}
|
|
19652
|
+
var WAL_SIDECAR_SUFFIXES = ["-wal", "-shm"];
|
|
19653
|
+
function migrateLegacyGrantsDbLocation(newDbPath = getGrantsDbPath(), deps) {
|
|
19654
|
+
const rename = deps?.rename ?? fs.renameSync;
|
|
19655
|
+
const legacyPath = getLegacyGrantsDbPath(newDbPath);
|
|
19656
|
+
if (path.resolve(legacyPath) === path.resolve(newDbPath)) {
|
|
19657
|
+
return { migrated: false };
|
|
19658
|
+
}
|
|
19659
|
+
if (!fs.existsSync(legacyPath))
|
|
19660
|
+
return { migrated: false };
|
|
19661
|
+
if (fs.existsSync(newDbPath))
|
|
19662
|
+
return { migrated: false };
|
|
19663
|
+
fs.mkdirSync(path.dirname(newDbPath), { recursive: true, mode: 448 });
|
|
19664
|
+
const movedSidecars = [];
|
|
19665
|
+
const rollbackSidecars = () => {
|
|
19666
|
+
for (const m of [...movedSidecars].reverse()) {
|
|
19667
|
+
try {
|
|
19668
|
+
rename(m.to, m.from);
|
|
19669
|
+
} catch {}
|
|
19670
|
+
}
|
|
19671
|
+
};
|
|
19672
|
+
for (const suffix of WAL_SIDECAR_SUFFIXES) {
|
|
19673
|
+
const from = `${legacyPath}${suffix}`;
|
|
19674
|
+
const to = `${newDbPath}${suffix}`;
|
|
19675
|
+
if (!fs.existsSync(from))
|
|
19676
|
+
continue;
|
|
19677
|
+
try {
|
|
19678
|
+
rename(from, to);
|
|
19679
|
+
movedSidecars.push({ from, to });
|
|
19680
|
+
} catch (err) {
|
|
19681
|
+
rollbackSidecars();
|
|
19682
|
+
throw new Error(`grants-db migration: failed to move WAL sidecar ${from} -> ${to} ` + `(${err.message}); aborted with the legacy DB intact — ` + `will retry on the next apply/boot`);
|
|
19683
|
+
}
|
|
19684
|
+
}
|
|
19685
|
+
try {
|
|
19686
|
+
rename(legacyPath, newDbPath);
|
|
19687
|
+
} catch (err) {
|
|
19688
|
+
if (fs.existsSync(newDbPath) && !fs.existsSync(legacyPath)) {
|
|
19689
|
+
return { migrated: false };
|
|
19690
|
+
}
|
|
19691
|
+
rollbackSidecars();
|
|
19692
|
+
throw err;
|
|
19693
|
+
}
|
|
19694
|
+
try {
|
|
19695
|
+
fs.chmodSync(newDbPath, 384);
|
|
19696
|
+
} catch {}
|
|
19697
|
+
return { migrated: true, from: legacyPath, to: newDbPath };
|
|
19698
|
+
}
|
|
19699
|
+
|
|
19538
19700
|
// src/agents/compose.ts
|
|
19539
19701
|
var BIND_MOUNT_EXACT_SOURCE_DENY = new Set(["/var/run/docker.sock"]);
|
|
19540
19702
|
|
|
19541
19703
|
// src/vault/broker/server.ts
|
|
19542
|
-
import { dirname as
|
|
19704
|
+
import { dirname as dirname6, resolve as resolve9, basename as basename4 } from "node:path";
|
|
19543
19705
|
import * as os3 from "node:os";
|
|
19544
|
-
import * as
|
|
19706
|
+
import * as path4 from "node:path";
|
|
19545
19707
|
|
|
19546
19708
|
// src/vault/migrate-layout.ts
|
|
19547
19709
|
import {
|
|
19548
19710
|
copyFileSync as copyFileSync2,
|
|
19549
|
-
chmodSync,
|
|
19550
|
-
existsSync as
|
|
19711
|
+
chmodSync as chmodSync2,
|
|
19712
|
+
existsSync as existsSync8,
|
|
19551
19713
|
fsyncSync as fsyncSync4,
|
|
19552
19714
|
lstatSync as lstatSync2,
|
|
19553
|
-
mkdirSync as
|
|
19715
|
+
mkdirSync as mkdirSync4,
|
|
19554
19716
|
openSync as openSync4,
|
|
19555
19717
|
closeSync as closeSync4,
|
|
19556
19718
|
readFileSync as readFileSync7,
|
|
19557
|
-
renameSync as
|
|
19719
|
+
renameSync as renameSync4,
|
|
19558
19720
|
statSync as statSync4,
|
|
19559
19721
|
symlinkSync,
|
|
19560
19722
|
unlinkSync as unlinkSync3
|
|
19561
19723
|
} from "node:fs";
|
|
19562
19724
|
import { createHash as createHash2 } from "node:crypto";
|
|
19563
|
-
import { basename as basename3, dirname as
|
|
19725
|
+
import { basename as basename3, dirname as dirname3, join as join6 } from "node:path";
|
|
19564
19726
|
function vaultLayoutPaths(home2) {
|
|
19565
|
-
const switchroomRoot =
|
|
19727
|
+
const switchroomRoot = join6(home2, ".switchroom");
|
|
19566
19728
|
return {
|
|
19567
|
-
oldPath:
|
|
19568
|
-
newPath:
|
|
19569
|
-
parent:
|
|
19729
|
+
oldPath: join6(switchroomRoot, "vault.enc"),
|
|
19730
|
+
newPath: join6(switchroomRoot, "vault", "vault.enc"),
|
|
19731
|
+
parent: join6(switchroomRoot, "vault"),
|
|
19570
19732
|
switchroomRoot
|
|
19571
19733
|
};
|
|
19572
19734
|
}
|
|
@@ -19575,11 +19737,11 @@ function inspectVaultLayout(home2) {
|
|
|
19575
19737
|
}
|
|
19576
19738
|
function runMigration(home2, opts) {
|
|
19577
19739
|
const { oldPath, newPath, parent, switchroomRoot } = vaultLayoutPaths(home2);
|
|
19578
|
-
const lockTarget =
|
|
19740
|
+
const lockTarget = existsSync8(newPath) ? newPath : existsSync8(oldPath) ? oldPath : null;
|
|
19579
19741
|
const release = !opts.dryRun && lockTarget !== null ? acquireVaultLock(lockTarget) : null;
|
|
19580
19742
|
try {
|
|
19581
19743
|
const oldStat = lstatSyncOrNull(oldPath);
|
|
19582
|
-
const newExists =
|
|
19744
|
+
const newExists = existsSync8(newPath);
|
|
19583
19745
|
if (oldStat === null && !newExists) {
|
|
19584
19746
|
return { kind: "no-vault" };
|
|
19585
19747
|
}
|
|
@@ -19615,12 +19777,12 @@ function runMigration(home2, opts) {
|
|
|
19615
19777
|
if (oldStat?.isFile() && !newExists) {
|
|
19616
19778
|
if (opts.dryRun)
|
|
19617
19779
|
return { kind: "migrated" };
|
|
19618
|
-
|
|
19780
|
+
mkdirSync4(parent, { recursive: true, mode: 448 });
|
|
19619
19781
|
const tempNew = `${newPath}.tmp`;
|
|
19620
19782
|
copyFileSync2(oldPath, tempNew);
|
|
19621
|
-
|
|
19783
|
+
chmodSync2(tempNew, 384);
|
|
19622
19784
|
fsyncFile(tempNew);
|
|
19623
|
-
|
|
19785
|
+
renameSync4(tempNew, newPath);
|
|
19624
19786
|
fsyncDir(parent);
|
|
19625
19787
|
atomicReplaceWithSymlink(oldPath, "vault/vault.enc");
|
|
19626
19788
|
fsyncDir(switchroomRoot);
|
|
@@ -19635,37 +19797,37 @@ function runMigration(home2, opts) {
|
|
|
19635
19797
|
}
|
|
19636
19798
|
}
|
|
19637
19799
|
}
|
|
19638
|
-
function lstatSyncOrNull(
|
|
19800
|
+
function lstatSyncOrNull(path2) {
|
|
19639
19801
|
try {
|
|
19640
|
-
return lstatSync2(
|
|
19802
|
+
return lstatSync2(path2);
|
|
19641
19803
|
} catch {
|
|
19642
19804
|
return null;
|
|
19643
19805
|
}
|
|
19644
19806
|
}
|
|
19645
|
-
function sha256File(
|
|
19646
|
-
const data = readFileSync7(
|
|
19807
|
+
function sha256File(path2) {
|
|
19808
|
+
const data = readFileSync7(path2);
|
|
19647
19809
|
return createHash2("sha256").update(data).digest("hex");
|
|
19648
19810
|
}
|
|
19649
19811
|
function atomicReplaceWithSymlink(target, linkTarget) {
|
|
19650
|
-
const tmp =
|
|
19651
|
-
if (
|
|
19812
|
+
const tmp = join6(dirname3(target), `.${basename3(target)}.symlink-tmp`);
|
|
19813
|
+
if (existsSync8(tmp)) {
|
|
19652
19814
|
try {
|
|
19653
19815
|
unlinkSync3(tmp);
|
|
19654
19816
|
} catch {}
|
|
19655
19817
|
}
|
|
19656
19818
|
symlinkSync(linkTarget, tmp);
|
|
19657
|
-
|
|
19819
|
+
renameSync4(tmp, target);
|
|
19658
19820
|
}
|
|
19659
|
-
function fsyncFile(
|
|
19660
|
-
const fd = openSync4(
|
|
19821
|
+
function fsyncFile(path2) {
|
|
19822
|
+
const fd = openSync4(path2, "r+");
|
|
19661
19823
|
try {
|
|
19662
19824
|
fsyncSync4(fd);
|
|
19663
19825
|
} finally {
|
|
19664
19826
|
closeSync4(fd);
|
|
19665
19827
|
}
|
|
19666
19828
|
}
|
|
19667
|
-
function fsyncDir(
|
|
19668
|
-
const fd = openSync4(
|
|
19829
|
+
function fsyncDir(path2) {
|
|
19830
|
+
const fd = openSync4(path2, "r");
|
|
19669
19831
|
try {
|
|
19670
19832
|
fsyncSync4(fd);
|
|
19671
19833
|
} finally {
|
|
@@ -19679,14 +19841,14 @@ init_loader();
|
|
|
19679
19841
|
// src/vault/auto-unlock.ts
|
|
19680
19842
|
import { createHmac, randomBytes as randomBytes2, createCipheriv as createCipheriv2, createDecipheriv as createDecipheriv2 } from "node:crypto";
|
|
19681
19843
|
import {
|
|
19682
|
-
chmodSync as
|
|
19844
|
+
chmodSync as chmodSync3,
|
|
19683
19845
|
closeSync as closeSync5,
|
|
19684
|
-
existsSync as
|
|
19846
|
+
existsSync as existsSync9,
|
|
19685
19847
|
fsyncSync as fsyncSync5,
|
|
19686
|
-
mkdirSync as
|
|
19848
|
+
mkdirSync as mkdirSync5,
|
|
19687
19849
|
openSync as openSync5,
|
|
19688
19850
|
readFileSync as readFileSync8,
|
|
19689
|
-
renameSync as
|
|
19851
|
+
renameSync as renameSync5,
|
|
19690
19852
|
unlinkSync as unlinkSync4,
|
|
19691
19853
|
writeSync as writeSync4
|
|
19692
19854
|
} from "node:fs";
|
|
@@ -19722,9 +19884,9 @@ function readMachineId() {
|
|
|
19722
19884
|
if (override && override.length > 0)
|
|
19723
19885
|
return override;
|
|
19724
19886
|
}
|
|
19725
|
-
for (const
|
|
19887
|
+
for (const path2 of [MACHINE_ID_PRIMARY, MACHINE_ID_FALLBACK]) {
|
|
19726
19888
|
try {
|
|
19727
|
-
const id = readFileSync8(
|
|
19889
|
+
const id = readFileSync8(path2, "utf8").trim();
|
|
19728
19890
|
if (id.length > 0)
|
|
19729
19891
|
return id;
|
|
19730
19892
|
} catch {}
|
|
@@ -19761,7 +19923,7 @@ function decryptAutoUnlock(blob, machineId) {
|
|
|
19761
19923
|
}
|
|
19762
19924
|
}
|
|
19763
19925
|
function readAutoUnlockFile(filePath) {
|
|
19764
|
-
if (!
|
|
19926
|
+
if (!existsSync9(filePath)) {
|
|
19765
19927
|
throw new AutoUnlockDecryptError("io");
|
|
19766
19928
|
}
|
|
19767
19929
|
let blob;
|
|
@@ -19775,9 +19937,9 @@ function readAutoUnlockFile(filePath) {
|
|
|
19775
19937
|
var DEFAULT_AUTO_UNLOCK_PATH = "~/.switchroom/vault-auto-unlock";
|
|
19776
19938
|
|
|
19777
19939
|
// src/vault/broker/audit-log.ts
|
|
19778
|
-
import * as
|
|
19779
|
-
import * as
|
|
19780
|
-
import * as
|
|
19940
|
+
import * as fs2 from "node:fs";
|
|
19941
|
+
import * as os2 from "node:os";
|
|
19942
|
+
import * as path2 from "node:path";
|
|
19781
19943
|
|
|
19782
19944
|
// src/util/audit-hashchain.ts
|
|
19783
19945
|
import { createHash as createHash4 } from "node:crypto";
|
|
@@ -19799,10 +19961,10 @@ function chainRow(state, entry) {
|
|
|
19799
19961
|
next: { seq, lastHash: hash }
|
|
19800
19962
|
};
|
|
19801
19963
|
}
|
|
19802
|
-
function readTail(
|
|
19964
|
+
function readTail(path2, tailBytes = 64 * 1024) {
|
|
19803
19965
|
let fd;
|
|
19804
19966
|
try {
|
|
19805
|
-
fd = openSync6(
|
|
19967
|
+
fd = openSync6(path2, "r");
|
|
19806
19968
|
} catch {
|
|
19807
19969
|
return null;
|
|
19808
19970
|
}
|
|
@@ -19822,8 +19984,8 @@ function readTail(path, tailBytes = 64 * 1024) {
|
|
|
19822
19984
|
} catch {}
|
|
19823
19985
|
}
|
|
19824
19986
|
}
|
|
19825
|
-
function seedChain(
|
|
19826
|
-
const tail = readTail(
|
|
19987
|
+
function seedChain(path2) {
|
|
19988
|
+
const tail = readTail(path2);
|
|
19827
19989
|
if (tail === null || tail === "") {
|
|
19828
19990
|
return { seq: 0, lastHash: CHAIN_GENESIS };
|
|
19829
19991
|
}
|
|
@@ -19846,8 +20008,8 @@ function seedChain(path) {
|
|
|
19846
20008
|
|
|
19847
20009
|
// src/vault/broker/test-isolation-guard.ts
|
|
19848
20010
|
import { mkdtempSync } from "node:fs";
|
|
19849
|
-
import { homedir as
|
|
19850
|
-
import { join as
|
|
20011
|
+
import { homedir as homedir5, tmpdir } from "node:os";
|
|
20012
|
+
import { join as join7, resolve as resolve8, sep } from "node:path";
|
|
19851
20013
|
function isTestRuntime() {
|
|
19852
20014
|
return process.env.VITEST !== undefined;
|
|
19853
20015
|
}
|
|
@@ -19855,11 +20017,11 @@ function escapeHatchSet() {
|
|
|
19855
20017
|
return process.env.SWITCHROOM_ALLOW_PROD_VAULT_IN_TEST === "1";
|
|
19856
20018
|
}
|
|
19857
20019
|
function realSwitchroomHome() {
|
|
19858
|
-
return
|
|
20020
|
+
return resolve8(homedir5(), ".switchroom");
|
|
19859
20021
|
}
|
|
19860
20022
|
function isUnderRealSwitchroomHome(p) {
|
|
19861
20023
|
const home2 = realSwitchroomHome();
|
|
19862
|
-
const r =
|
|
20024
|
+
const r = resolve8(p);
|
|
19863
20025
|
return r === home2 || r.startsWith(home2 + sep);
|
|
19864
20026
|
}
|
|
19865
20027
|
var warnedVault = false;
|
|
@@ -19868,7 +20030,7 @@ function safeVaultPath(requestedPath) {
|
|
|
19868
20030
|
return requestedPath;
|
|
19869
20031
|
if (!isUnderRealSwitchroomHome(requestedPath))
|
|
19870
20032
|
return requestedPath;
|
|
19871
|
-
const redirected =
|
|
20033
|
+
const redirected = join7(mkdtempSync(join7(tmpdir(), "sw-test-vault-")), "vault.enc");
|
|
19872
20034
|
if (!warnedVault) {
|
|
19873
20035
|
warnedVault = true;
|
|
19874
20036
|
process.stderr.write(`[test-isolation guard] vault path redirected away from the ` + `production tree to an isolated tmpdir — a vault/broker test ` + `resolved its vault path inside ~/.switchroom. See CLAUDE.md ` + `"Vault & shared-state test discipline".
|
|
@@ -19884,7 +20046,7 @@ function safeAuditLogPath(requestedPath) {
|
|
|
19884
20046
|
if (!isUnderRealSwitchroomHome(requestedPath))
|
|
19885
20047
|
return requestedPath;
|
|
19886
20048
|
if (!cachedTmpAuditLog) {
|
|
19887
|
-
cachedTmpAuditLog =
|
|
20049
|
+
cachedTmpAuditLog = join7(mkdtempSync(join7(tmpdir(), "sw-test-audit-")), "vault-audit.log");
|
|
19888
20050
|
}
|
|
19889
20051
|
if (!warnedAudit) {
|
|
19890
20052
|
warnedAudit = true;
|
|
@@ -19900,7 +20062,7 @@ function failOpenStatePath(logPath) {
|
|
|
19900
20062
|
}
|
|
19901
20063
|
function readFailOpenState(statePath) {
|
|
19902
20064
|
try {
|
|
19903
|
-
const raw =
|
|
20065
|
+
const raw = fs2.readFileSync(statePath, "utf8");
|
|
19904
20066
|
const parsed = JSON.parse(raw);
|
|
19905
20067
|
const count = Number(parsed.failOpenCount);
|
|
19906
20068
|
return {
|
|
@@ -19923,9 +20085,9 @@ function resolveRotation(opts) {
|
|
|
19923
20085
|
}
|
|
19924
20086
|
function rotateAuditLog(logPath, maxFiles) {
|
|
19925
20087
|
const oldest = `${logPath}.${maxFiles}`;
|
|
19926
|
-
if (
|
|
20088
|
+
if (fs2.existsSync(oldest)) {
|
|
19927
20089
|
try {
|
|
19928
|
-
|
|
20090
|
+
fs2.unlinkSync(oldest);
|
|
19929
20091
|
} catch (err) {
|
|
19930
20092
|
process.stderr.write(`[vault-audit] ERROR: could not drop oldest rotation ${oldest}: ${err.message}
|
|
19931
20093
|
`);
|
|
@@ -19934,10 +20096,10 @@ function rotateAuditLog(logPath, maxFiles) {
|
|
|
19934
20096
|
for (let n = maxFiles - 1;n >= 1; n--) {
|
|
19935
20097
|
const from = `${logPath}.${n}`;
|
|
19936
20098
|
const to = `${logPath}.${n + 1}`;
|
|
19937
|
-
if (!
|
|
20099
|
+
if (!fs2.existsSync(from))
|
|
19938
20100
|
continue;
|
|
19939
20101
|
try {
|
|
19940
|
-
|
|
20102
|
+
fs2.renameSync(from, to);
|
|
19941
20103
|
} catch (err) {
|
|
19942
20104
|
process.stderr.write(`[vault-audit] ERROR: could not rotate ${from} → ${to}: ${err.message}
|
|
19943
20105
|
`);
|
|
@@ -19946,18 +20108,18 @@ function rotateAuditLog(logPath, maxFiles) {
|
|
|
19946
20108
|
}
|
|
19947
20109
|
const snapshotPath = `${logPath}.1`;
|
|
19948
20110
|
try {
|
|
19949
|
-
|
|
20111
|
+
fs2.copyFileSync(logPath, snapshotPath);
|
|
19950
20112
|
} catch (err) {
|
|
19951
20113
|
process.stderr.write(`[vault-audit] ERROR: could not snapshot active audit log ${logPath} → ${snapshotPath}: ${err.message}
|
|
19952
20114
|
`);
|
|
19953
20115
|
return;
|
|
19954
20116
|
}
|
|
19955
20117
|
try {
|
|
19956
|
-
const fd =
|
|
20118
|
+
const fd = fs2.openSync(snapshotPath, "r");
|
|
19957
20119
|
try {
|
|
19958
|
-
|
|
20120
|
+
fs2.fsyncSync(fd);
|
|
19959
20121
|
} finally {
|
|
19960
|
-
|
|
20122
|
+
fs2.closeSync(fd);
|
|
19961
20123
|
}
|
|
19962
20124
|
} catch (err) {
|
|
19963
20125
|
process.stderr.write(`[vault-audit] ERROR: could not fsync audit snapshot ${snapshotPath}; leaving active log intact to avoid data loss: ${err.message}
|
|
@@ -19965,22 +20127,22 @@ function rotateAuditLog(logPath, maxFiles) {
|
|
|
19965
20127
|
return;
|
|
19966
20128
|
}
|
|
19967
20129
|
try {
|
|
19968
|
-
const dirFd =
|
|
20130
|
+
const dirFd = fs2.openSync(path2.dirname(snapshotPath), "r");
|
|
19969
20131
|
try {
|
|
19970
|
-
|
|
20132
|
+
fs2.fsyncSync(dirFd);
|
|
19971
20133
|
} finally {
|
|
19972
|
-
|
|
20134
|
+
fs2.closeSync(dirFd);
|
|
19973
20135
|
}
|
|
19974
20136
|
} catch {}
|
|
19975
20137
|
try {
|
|
19976
|
-
|
|
20138
|
+
fs2.truncateSync(logPath, 0);
|
|
19977
20139
|
} catch (err) {
|
|
19978
20140
|
process.stderr.write(`[vault-audit] ERROR: could not truncate active audit log ${logPath}: ${err.message}
|
|
19979
20141
|
`);
|
|
19980
20142
|
}
|
|
19981
20143
|
}
|
|
19982
20144
|
function defaultAuditLogPath() {
|
|
19983
|
-
return
|
|
20145
|
+
return path2.join(os2.homedir(), ".switchroom", "vault-audit.log");
|
|
19984
20146
|
}
|
|
19985
20147
|
function callerFromPeer(peer) {
|
|
19986
20148
|
if (peer.systemdUnit !== null && peer.systemdUnit.length > 0) {
|
|
@@ -20002,7 +20164,7 @@ function createAuditLogger(opts = {}) {
|
|
|
20002
20164
|
lastError: String(err.message).slice(0, 300)
|
|
20003
20165
|
};
|
|
20004
20166
|
try {
|
|
20005
|
-
|
|
20167
|
+
fs2.writeFileSync(statePath, `${JSON.stringify(state)}
|
|
20006
20168
|
`, { mode: 384 });
|
|
20007
20169
|
} catch (persistErr) {
|
|
20008
20170
|
process.stderr.write(`[vault-audit] ERROR: could not persist fail-open counter ${statePath}: ${persistErr.message}
|
|
@@ -20016,7 +20178,7 @@ function createAuditLogger(opts = {}) {
|
|
|
20016
20178
|
write(entry) {
|
|
20017
20179
|
if (rotation.maxBytes > 0) {
|
|
20018
20180
|
try {
|
|
20019
|
-
const size =
|
|
20181
|
+
const size = fs2.statSync(logPath).size;
|
|
20020
20182
|
if (size >= rotation.maxBytes) {
|
|
20021
20183
|
rotateAuditLog(logPath, rotation.maxFiles);
|
|
20022
20184
|
}
|
|
@@ -20025,7 +20187,7 @@ function createAuditLogger(opts = {}) {
|
|
|
20025
20187
|
const { line, next } = chainRow(chain, entry);
|
|
20026
20188
|
let fd;
|
|
20027
20189
|
try {
|
|
20028
|
-
fd =
|
|
20190
|
+
fd = fs2.openSync(logPath, "a", 384);
|
|
20029
20191
|
} catch (err) {
|
|
20030
20192
|
process.stderr.write(`[vault-audit] ERROR: could not open audit log ${logPath}: ${err.message}
|
|
20031
20193
|
`);
|
|
@@ -20034,7 +20196,7 @@ function createAuditLogger(opts = {}) {
|
|
|
20034
20196
|
}
|
|
20035
20197
|
let durable = false;
|
|
20036
20198
|
try {
|
|
20037
|
-
|
|
20199
|
+
fs2.writeSync(fd, line);
|
|
20038
20200
|
chain = next;
|
|
20039
20201
|
durable = true;
|
|
20040
20202
|
} catch (err) {
|
|
@@ -20043,7 +20205,7 @@ function createAuditLogger(opts = {}) {
|
|
|
20043
20205
|
recordFailOpen(err);
|
|
20044
20206
|
} finally {
|
|
20045
20207
|
try {
|
|
20046
|
-
|
|
20208
|
+
fs2.closeSync(fd);
|
|
20047
20209
|
} catch (closeErr) {
|
|
20048
20210
|
process.stderr.write(`[vault-audit] ERROR: could not close audit log fd: ${closeErr.message}
|
|
20049
20211
|
`);
|
|
@@ -20112,13 +20274,13 @@ function genSalt(rounds, seed_length, callback) {
|
|
|
20112
20274
|
throw Error("Illegal callback: " + typeof callback);
|
|
20113
20275
|
_async(callback);
|
|
20114
20276
|
} else
|
|
20115
|
-
return new Promise(function(
|
|
20277
|
+
return new Promise(function(resolve9, reject) {
|
|
20116
20278
|
_async(function(err, res) {
|
|
20117
20279
|
if (err) {
|
|
20118
20280
|
reject(err);
|
|
20119
20281
|
return;
|
|
20120
20282
|
}
|
|
20121
|
-
|
|
20283
|
+
resolve9(res);
|
|
20122
20284
|
});
|
|
20123
20285
|
});
|
|
20124
20286
|
}
|
|
@@ -20138,13 +20300,13 @@ function hash(password, salt, callback, progressCallback) {
|
|
|
20138
20300
|
throw Error("Illegal callback: " + typeof callback);
|
|
20139
20301
|
_async(callback);
|
|
20140
20302
|
} else
|
|
20141
|
-
return new Promise(function(
|
|
20303
|
+
return new Promise(function(resolve9, reject) {
|
|
20142
20304
|
_async(function(err, res) {
|
|
20143
20305
|
if (err) {
|
|
20144
20306
|
reject(err);
|
|
20145
20307
|
return;
|
|
20146
20308
|
}
|
|
20147
|
-
|
|
20309
|
+
resolve9(res);
|
|
20148
20310
|
});
|
|
20149
20311
|
});
|
|
20150
20312
|
}
|
|
@@ -20177,13 +20339,13 @@ function compare(password, hashValue, callback, progressCallback) {
|
|
|
20177
20339
|
throw Error("Illegal callback: " + typeof callback);
|
|
20178
20340
|
_async(callback);
|
|
20179
20341
|
} else
|
|
20180
|
-
return new Promise(function(
|
|
20342
|
+
return new Promise(function(resolve9, reject) {
|
|
20181
20343
|
_async(function(err, res) {
|
|
20182
20344
|
if (err) {
|
|
20183
20345
|
reject(err);
|
|
20184
20346
|
return;
|
|
20185
20347
|
}
|
|
20186
|
-
|
|
20348
|
+
resolve9(res);
|
|
20187
20349
|
});
|
|
20188
20350
|
});
|
|
20189
20351
|
}
|
|
@@ -21777,6 +21939,11 @@ function rowToGrant(row) {
|
|
|
21777
21939
|
description: row.description ?? null
|
|
21778
21940
|
};
|
|
21779
21941
|
}
|
|
21942
|
+
function checkpointGrantsWal(db) {
|
|
21943
|
+
try {
|
|
21944
|
+
db.run("PRAGMA wal_checkpoint(TRUNCATE)");
|
|
21945
|
+
} catch {}
|
|
21946
|
+
}
|
|
21780
21947
|
function keyMatchesPatterns(patterns, key) {
|
|
21781
21948
|
for (const pat of patterns) {
|
|
21782
21949
|
if (pat.endsWith("*")) {
|
|
@@ -21807,6 +21974,7 @@ async function mintGrant(db, agent_slug, key_allow, ttl_seconds, description, wr
|
|
|
21807
21974
|
now,
|
|
21808
21975
|
description ?? null
|
|
21809
21976
|
]);
|
|
21977
|
+
checkpointGrantsWal(db);
|
|
21810
21978
|
return { token, id, expires_at };
|
|
21811
21979
|
}
|
|
21812
21980
|
async function validateGrant(db, token, key) {
|
|
@@ -21885,6 +22053,7 @@ async function validateGrantForWrite(db, token, key) {
|
|
|
21885
22053
|
function revokeGrant(db, id) {
|
|
21886
22054
|
const now = Math.floor(Date.now() / 1000);
|
|
21887
22055
|
const result = db.run(`UPDATE vault_grants SET revoked_at = ? WHERE id = ? AND revoked_at IS NULL`, [now, id]);
|
|
22056
|
+
checkpointGrantsWal(db);
|
|
21888
22057
|
return (result.changes ?? 0) > 0;
|
|
21889
22058
|
}
|
|
21890
22059
|
function listGrants(db, agent_slug) {
|
|
@@ -21927,9 +22096,8 @@ function adminOnlyKeysBeingAdded(requestedKeys, existingKeys, patterns) {
|
|
|
21927
22096
|
}
|
|
21928
22097
|
|
|
21929
22098
|
// src/vault/grants-db.ts
|
|
21930
|
-
import * as
|
|
21931
|
-
import * as
|
|
21932
|
-
import * as fs2 from "node:fs";
|
|
22099
|
+
import * as path3 from "node:path";
|
|
22100
|
+
import * as fs3 from "node:fs";
|
|
21933
22101
|
import { Database } from "bun:sqlite";
|
|
21934
22102
|
|
|
21935
22103
|
// src/vault/approvals/schema.ts
|
|
@@ -22007,8 +22175,8 @@ function migrateApprovalSchema(db) {
|
|
|
22007
22175
|
var DEFAULT_MAX_TTL_LIFETIME_MS = 7 * 24 * 60 * 60 * 1000;
|
|
22008
22176
|
|
|
22009
22177
|
// src/vault/grants-db.ts
|
|
22010
|
-
var DEFAULT_GRANTS_DB_PATH =
|
|
22011
|
-
var
|
|
22178
|
+
var DEFAULT_GRANTS_DB_PATH = getGrantsDbPath();
|
|
22179
|
+
var WAL_SIDECAR_SUFFIXES2 = ["-wal", "-shm"];
|
|
22012
22180
|
function isGrantsDbCorruption(err) {
|
|
22013
22181
|
if (!err || typeof err !== "object")
|
|
22014
22182
|
return false;
|
|
@@ -22022,12 +22190,12 @@ function isGrantsDbCorruption(err) {
|
|
|
22022
22190
|
function quarantineGrantsDb(dbPath) {
|
|
22023
22191
|
const stamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
22024
22192
|
const quarantinePath = `${dbPath}.corrupt-${stamp}`;
|
|
22025
|
-
|
|
22026
|
-
for (const suffix of
|
|
22193
|
+
fs3.renameSync(dbPath, quarantinePath);
|
|
22194
|
+
for (const suffix of WAL_SIDECAR_SUFFIXES2) {
|
|
22027
22195
|
const sidecar = `${dbPath}${suffix}`;
|
|
22028
|
-
if (
|
|
22196
|
+
if (fs3.existsSync(sidecar)) {
|
|
22029
22197
|
try {
|
|
22030
|
-
|
|
22198
|
+
fs3.renameSync(sidecar, `${quarantinePath}${suffix}`);
|
|
22031
22199
|
} catch {}
|
|
22032
22200
|
}
|
|
22033
22201
|
}
|
|
@@ -22037,7 +22205,7 @@ function openAndMigrate(dbPath) {
|
|
|
22037
22205
|
const db = new Database(dbPath, { create: true });
|
|
22038
22206
|
try {
|
|
22039
22207
|
try {
|
|
22040
|
-
|
|
22208
|
+
fs3.chmodSync(dbPath, 384);
|
|
22041
22209
|
} catch {}
|
|
22042
22210
|
db.run("PRAGMA journal_mode=WAL");
|
|
22043
22211
|
db.run("PRAGMA busy_timeout=5000");
|
|
@@ -22053,8 +22221,9 @@ function openAndMigrate(dbPath) {
|
|
|
22053
22221
|
return db;
|
|
22054
22222
|
}
|
|
22055
22223
|
function openGrantsDb(dbPath = DEFAULT_GRANTS_DB_PATH, opener = openAndMigrate) {
|
|
22056
|
-
const dir =
|
|
22057
|
-
|
|
22224
|
+
const dir = path3.dirname(dbPath);
|
|
22225
|
+
fs3.mkdirSync(dir, { recursive: true });
|
|
22226
|
+
migrateLegacyGrantsDbLocation(dbPath);
|
|
22058
22227
|
try {
|
|
22059
22228
|
return opener(dbPath);
|
|
22060
22229
|
} catch (err) {
|
|
@@ -22405,7 +22574,7 @@ class VaultBroker {
|
|
|
22405
22574
|
if (process.platform !== "linux" && process.env.SWITCHROOM_BROKER_ALLOW_NON_LINUX !== "1") {
|
|
22406
22575
|
throw new Error(`vault-broker is Linux-only (running on ${process.platform}). ` + `The broker's ACL relies on cgroup-based systemd unit identification, ` + `which is not available on this platform. ` + `Use 'switchroom vault get --no-broker' for direct vault access. ` + `If you need to run the broker for development on this platform, ` + `set SWITCHROOM_BROKER_ALLOW_NON_LINUX=1 — but understand that the ` + `broker will accept any same-user caller without per-cron ACL enforcement.`);
|
|
22407
22576
|
}
|
|
22408
|
-
this.socketPath =
|
|
22577
|
+
this.socketPath = resolve9(socketPath);
|
|
22409
22578
|
this.unlockSocketPath = unlockSocketFor(this.socketPath);
|
|
22410
22579
|
this.startedAt = Date.now();
|
|
22411
22580
|
if (this.testOpts._testConfig) {
|
|
@@ -22415,7 +22584,7 @@ class VaultBroker {
|
|
|
22415
22584
|
this.config = loadConfig2(configPath);
|
|
22416
22585
|
}
|
|
22417
22586
|
if (vaultPath) {
|
|
22418
|
-
this.vaultPath =
|
|
22587
|
+
this.vaultPath = resolve9(vaultPath);
|
|
22419
22588
|
} else {
|
|
22420
22589
|
this.vaultPath = resolvePath(this.config.vault?.path ?? "~/.switchroom/vault.enc");
|
|
22421
22590
|
}
|
|
@@ -22427,13 +22596,13 @@ class VaultBroker {
|
|
|
22427
22596
|
this.passphrase = this.testOpts._testPassphrase;
|
|
22428
22597
|
}
|
|
22429
22598
|
process.umask(63);
|
|
22430
|
-
const parentDir =
|
|
22431
|
-
|
|
22599
|
+
const parentDir = dirname6(this.socketPath);
|
|
22600
|
+
mkdirSync7(parentDir, { recursive: true, mode: 448 });
|
|
22432
22601
|
try {
|
|
22433
|
-
|
|
22602
|
+
chmodSync5(parentDir, 448);
|
|
22434
22603
|
} catch {}
|
|
22435
22604
|
for (const p of [this.socketPath, this.unlockSocketPath]) {
|
|
22436
|
-
if (
|
|
22605
|
+
if (existsSync12(p)) {
|
|
22437
22606
|
try {
|
|
22438
22607
|
unlinkSync6(p);
|
|
22439
22608
|
} catch {}
|
|
@@ -22469,7 +22638,7 @@ class VaultBroker {
|
|
|
22469
22638
|
try {
|
|
22470
22639
|
if (ready) {
|
|
22471
22640
|
writeFileSync3(p, "", { mode: 384 });
|
|
22472
|
-
} else if (
|
|
22641
|
+
} else if (existsSync12(p)) {
|
|
22473
22642
|
unlinkSync6(p);
|
|
22474
22643
|
}
|
|
22475
22644
|
} catch {}
|
|
@@ -22502,7 +22671,7 @@ class VaultBroker {
|
|
|
22502
22671
|
try {
|
|
22503
22672
|
entry.server.close();
|
|
22504
22673
|
} catch {}
|
|
22505
|
-
if (
|
|
22674
|
+
if (existsSync12(sockPath)) {
|
|
22506
22675
|
try {
|
|
22507
22676
|
unlinkSync6(sockPath);
|
|
22508
22677
|
} catch {}
|
|
@@ -22510,7 +22679,7 @@ class VaultBroker {
|
|
|
22510
22679
|
}
|
|
22511
22680
|
this.agentServers.clear();
|
|
22512
22681
|
for (const p of [this.socketPath, this.unlockSocketPath]) {
|
|
22513
|
-
if (p &&
|
|
22682
|
+
if (p && existsSync12(p)) {
|
|
22514
22683
|
try {
|
|
22515
22684
|
unlinkSync6(p);
|
|
22516
22685
|
} catch {}
|
|
@@ -22518,7 +22687,7 @@ class VaultBroker {
|
|
|
22518
22687
|
}
|
|
22519
22688
|
try {
|
|
22520
22689
|
const pidPath = resolvePath(PID_FILE_DEFAULT);
|
|
22521
|
-
if (
|
|
22690
|
+
if (existsSync12(pidPath))
|
|
22522
22691
|
unlinkSync6(pidPath);
|
|
22523
22692
|
} catch {}
|
|
22524
22693
|
}
|
|
@@ -22536,7 +22705,7 @@ class VaultBroker {
|
|
|
22536
22705
|
return this.config;
|
|
22537
22706
|
}
|
|
22538
22707
|
bindAgentSocket(socketPath) {
|
|
22539
|
-
const abs =
|
|
22708
|
+
const abs = resolve9(socketPath);
|
|
22540
22709
|
const agentName = socketPathToAgent(abs);
|
|
22541
22710
|
if (agentName === null) {
|
|
22542
22711
|
return Promise.reject(new Error(`bindAgentSocket: socket path '${abs}' does not match the canonical ` + `/run/switchroom/broker/<agent>.sock shape — refusing to bind without ` + `a verifiable agent identity`));
|
|
@@ -22544,16 +22713,16 @@ class VaultBroker {
|
|
|
22544
22713
|
return new Promise((resolveP, rejectP) => {
|
|
22545
22714
|
if (abs.endsWith("/sock")) {
|
|
22546
22715
|
const dir = abs.slice(0, -"/sock".length);
|
|
22547
|
-
if (
|
|
22716
|
+
if (existsSync12(dir)) {
|
|
22548
22717
|
try {
|
|
22549
22718
|
chownSync(dir, 0, 0);
|
|
22550
22719
|
} catch {}
|
|
22551
22720
|
try {
|
|
22552
|
-
|
|
22721
|
+
chmodSync5(dir, 448);
|
|
22553
22722
|
} catch {}
|
|
22554
22723
|
}
|
|
22555
22724
|
}
|
|
22556
|
-
if (
|
|
22725
|
+
if (existsSync12(abs)) {
|
|
22557
22726
|
try {
|
|
22558
22727
|
unlinkSync6(abs);
|
|
22559
22728
|
} catch (err) {
|
|
@@ -22568,7 +22737,7 @@ class VaultBroker {
|
|
|
22568
22737
|
server.on("error", (err) => rejectP(err));
|
|
22569
22738
|
server.listen(abs, () => {
|
|
22570
22739
|
try {
|
|
22571
|
-
|
|
22740
|
+
chmodSync5(abs, 432);
|
|
22572
22741
|
} catch {}
|
|
22573
22742
|
let agentUid = null;
|
|
22574
22743
|
try {
|
|
@@ -22585,7 +22754,7 @@ class VaultBroker {
|
|
|
22585
22754
|
} catch {}
|
|
22586
22755
|
this.agentServers.set(abs, { server, agentName });
|
|
22587
22756
|
const unlockAbs = unlockSocketFor(abs);
|
|
22588
|
-
if (
|
|
22757
|
+
if (existsSync12(unlockAbs)) {
|
|
22589
22758
|
try {
|
|
22590
22759
|
unlinkSync6(unlockAbs);
|
|
22591
22760
|
} catch {}
|
|
@@ -22602,7 +22771,7 @@ class VaultBroker {
|
|
|
22602
22771
|
});
|
|
22603
22772
|
unlockServer.listen(unlockAbs, () => {
|
|
22604
22773
|
try {
|
|
22605
|
-
|
|
22774
|
+
chmodSync5(unlockAbs, 432);
|
|
22606
22775
|
} catch {}
|
|
22607
22776
|
if (agentUid !== null) {
|
|
22608
22777
|
try {
|
|
@@ -22616,7 +22785,7 @@ class VaultBroker {
|
|
|
22616
22785
|
});
|
|
22617
22786
|
}
|
|
22618
22787
|
_bindDataSocket() {
|
|
22619
|
-
return new Promise((
|
|
22788
|
+
return new Promise((resolve10, reject) => {
|
|
22620
22789
|
const server = net.createServer((socket) => {
|
|
22621
22790
|
this._handleDataConnection(socket);
|
|
22622
22791
|
});
|
|
@@ -22625,10 +22794,10 @@ class VaultBroker {
|
|
|
22625
22794
|
});
|
|
22626
22795
|
server.listen(this.socketPath, () => {
|
|
22627
22796
|
try {
|
|
22628
|
-
|
|
22797
|
+
chmodSync5(this.socketPath, 384);
|
|
22629
22798
|
} catch {}
|
|
22630
22799
|
this.server = server;
|
|
22631
|
-
|
|
22800
|
+
resolve10();
|
|
22632
22801
|
});
|
|
22633
22802
|
});
|
|
22634
22803
|
}
|
|
@@ -22640,12 +22809,12 @@ class VaultBroker {
|
|
|
22640
22809
|
if (!Number.isFinite(uid) || uid <= 0)
|
|
22641
22810
|
return;
|
|
22642
22811
|
try {
|
|
22643
|
-
if (
|
|
22812
|
+
if (existsSync12(this.vaultPath))
|
|
22644
22813
|
chownSync(this.vaultPath, uid, uid);
|
|
22645
22814
|
} catch {}
|
|
22646
22815
|
}
|
|
22647
22816
|
bindOperatorListener(socketPath, operatorUid) {
|
|
22648
|
-
const abs =
|
|
22817
|
+
const abs = resolve9(socketPath);
|
|
22649
22818
|
const identity = socketPathToIdentity(abs);
|
|
22650
22819
|
if (identity?.kind !== "operator") {
|
|
22651
22820
|
return Promise.reject(new Error(`bindOperatorListener: socket path '${abs}' does not match the canonical ` + `/run/switchroom/broker/operator/sock shape — refusing to bind`));
|
|
@@ -22653,17 +22822,17 @@ class VaultBroker {
|
|
|
22653
22822
|
const unlockAbs = unlockSocketFor(abs);
|
|
22654
22823
|
if (abs.endsWith("/sock")) {
|
|
22655
22824
|
const dir = abs.slice(0, -"/sock".length);
|
|
22656
|
-
if (
|
|
22825
|
+
if (existsSync12(dir)) {
|
|
22657
22826
|
try {
|
|
22658
22827
|
chownSync(dir, 0, 0);
|
|
22659
22828
|
} catch {}
|
|
22660
22829
|
try {
|
|
22661
|
-
|
|
22830
|
+
chmodSync5(dir, 448);
|
|
22662
22831
|
} catch {}
|
|
22663
22832
|
}
|
|
22664
22833
|
}
|
|
22665
22834
|
for (const p of [abs, unlockAbs]) {
|
|
22666
|
-
if (
|
|
22835
|
+
if (existsSync12(p)) {
|
|
22667
22836
|
try {
|
|
22668
22837
|
unlinkSync6(p);
|
|
22669
22838
|
} catch {}
|
|
@@ -22676,7 +22845,7 @@ class VaultBroker {
|
|
|
22676
22845
|
dataServer.on("error", (err) => rejectP(err));
|
|
22677
22846
|
dataServer.listen(abs, () => {
|
|
22678
22847
|
try {
|
|
22679
|
-
|
|
22848
|
+
chmodSync5(abs, 384);
|
|
22680
22849
|
} catch {}
|
|
22681
22850
|
try {
|
|
22682
22851
|
chownSync(abs, operatorUid, operatorUid);
|
|
@@ -22687,7 +22856,7 @@ class VaultBroker {
|
|
|
22687
22856
|
unlockServer.on("error", (err) => rejectP(err));
|
|
22688
22857
|
unlockServer.listen(unlockAbs, () => {
|
|
22689
22858
|
try {
|
|
22690
|
-
|
|
22859
|
+
chmodSync5(unlockAbs, 384);
|
|
22691
22860
|
} catch {}
|
|
22692
22861
|
try {
|
|
22693
22862
|
chownSync(unlockAbs, operatorUid, operatorUid);
|
|
@@ -22698,7 +22867,7 @@ class VaultBroker {
|
|
|
22698
22867
|
chownSync(dir, operatorUid, operatorUid);
|
|
22699
22868
|
} catch {}
|
|
22700
22869
|
try {
|
|
22701
|
-
|
|
22870
|
+
chmodSync5(dir, 448);
|
|
22702
22871
|
} catch {}
|
|
22703
22872
|
}
|
|
22704
22873
|
this.agentServers.set(abs, { server: dataServer, agentName: "__operator__" });
|
|
@@ -22709,7 +22878,7 @@ class VaultBroker {
|
|
|
22709
22878
|
});
|
|
22710
22879
|
}
|
|
22711
22880
|
_bindUnlockSocket() {
|
|
22712
|
-
return new Promise((
|
|
22881
|
+
return new Promise((resolve10, reject) => {
|
|
22713
22882
|
const server = net.createServer((socket) => {
|
|
22714
22883
|
this._handleUnlockConnection(socket);
|
|
22715
22884
|
});
|
|
@@ -22718,10 +22887,10 @@ class VaultBroker {
|
|
|
22718
22887
|
});
|
|
22719
22888
|
server.listen(this.unlockSocketPath, () => {
|
|
22720
22889
|
try {
|
|
22721
|
-
|
|
22890
|
+
chmodSync5(this.unlockSocketPath, 384);
|
|
22722
22891
|
} catch {}
|
|
22723
22892
|
this.unlockServer = server;
|
|
22724
|
-
|
|
22893
|
+
resolve10();
|
|
22725
22894
|
});
|
|
22726
22895
|
});
|
|
22727
22896
|
}
|
|
@@ -23560,13 +23729,13 @@ class VaultBroker {
|
|
|
23560
23729
|
return;
|
|
23561
23730
|
}
|
|
23562
23731
|
try {
|
|
23563
|
-
const agentsDir = this.config ? resolveAgentsDir(this.config) :
|
|
23564
|
-
const tokenDir =
|
|
23565
|
-
|
|
23566
|
-
const tokenPath =
|
|
23732
|
+
const agentsDir = this.config ? resolveAgentsDir(this.config) : path4.join(os3.homedir(), ".switchroom", "agents");
|
|
23733
|
+
const tokenDir = path4.join(agentsDir, agent);
|
|
23734
|
+
mkdirSync7(tokenDir, { recursive: true });
|
|
23735
|
+
const tokenPath = path4.join(tokenDir, ".vault-token");
|
|
23567
23736
|
const tmpPath = `${tokenPath}.tmp.${process.pid}`;
|
|
23568
23737
|
writeFileSync3(tmpPath, mintResult.token, { mode: 384 });
|
|
23569
|
-
|
|
23738
|
+
renameSync8(tmpPath, tokenPath);
|
|
23570
23739
|
try {
|
|
23571
23740
|
const uid = allocateAgentUid(agent);
|
|
23572
23741
|
chownSync(tokenPath, uid, uid);
|
|
@@ -23625,9 +23794,9 @@ class VaultBroker {
|
|
|
23625
23794
|
try {
|
|
23626
23795
|
const row = this.grantsDb.query("SELECT agent_slug FROM vault_grants WHERE id = ?").get(id);
|
|
23627
23796
|
if (row && AgentNameSchema.safeParse(row.agent_slug).success) {
|
|
23628
|
-
const agentsDir = this.config ? resolveAgentsDir(this.config) :
|
|
23629
|
-
const tokenPath =
|
|
23630
|
-
if (
|
|
23797
|
+
const agentsDir = this.config ? resolveAgentsDir(this.config) : path4.join(os3.homedir(), ".switchroom", "agents");
|
|
23798
|
+
const tokenPath = path4.join(agentsDir, row.agent_slug, ".vault-token");
|
|
23799
|
+
if (existsSync12(tokenPath)) {
|
|
23631
23800
|
try {
|
|
23632
23801
|
unlinkSync6(tokenPath);
|
|
23633
23802
|
} catch {}
|
|
@@ -23865,7 +24034,7 @@ class VaultBroker {
|
|
|
23865
24034
|
const envPath = process.env.SWITCHROOM_VAULT_BROKER_AUTO_UNLOCK_PATH;
|
|
23866
24035
|
const configuredPath = (envPath && envPath.length > 0 ? envPath : undefined) ?? this.config?.vault?.broker?.autoUnlockCredentialPath ?? DEFAULT_AUTO_UNLOCK_PATH;
|
|
23867
24036
|
const filePath = resolvePath(configuredPath);
|
|
23868
|
-
if (!
|
|
24037
|
+
if (!existsSync12(filePath))
|
|
23869
24038
|
return false;
|
|
23870
24039
|
try {
|
|
23871
24040
|
if (statSync6(filePath).size === 0)
|
|
@@ -23946,15 +24115,15 @@ class VaultBroker {
|
|
|
23946
24115
|
}
|
|
23947
24116
|
}
|
|
23948
24117
|
function detectVaultLayoutDrift(vaultPath) {
|
|
23949
|
-
const dir =
|
|
24118
|
+
const dir = dirname6(vaultPath);
|
|
23950
24119
|
if (basename4(dir) !== "vault")
|
|
23951
24120
|
return;
|
|
23952
24121
|
if (basename4(vaultPath) !== "vault.enc")
|
|
23953
24122
|
return;
|
|
23954
|
-
const switchroomDir =
|
|
24123
|
+
const switchroomDir = dirname6(dir);
|
|
23955
24124
|
if (basename4(switchroomDir) !== ".switchroom")
|
|
23956
24125
|
return;
|
|
23957
|
-
const home2 =
|
|
24126
|
+
const home2 = dirname6(switchroomDir);
|
|
23958
24127
|
const result = inspectVaultLayout(home2);
|
|
23959
24128
|
if (result.kind === "divergent") {
|
|
23960
24129
|
throw new VaultError(`Vault layout divergence detected at boot: ${result.details.oldPath} and ${result.details.newPath} are both regular files with different content. An older switchroom CLI may have written to the legacy path after migration ran. Run \`switchroom apply\` from the host to surface the recovery recipe (state E refusal with literal \`mv\` commands). See docs/operators/state-e-recovery.md.`);
|
|
@@ -23980,7 +24149,7 @@ async function main() {
|
|
|
23980
24149
|
const vaultPath = process.env.SWITCHROOM_VAULT_PATH;
|
|
23981
24150
|
let perAgentTargets = [];
|
|
23982
24151
|
try {
|
|
23983
|
-
if (
|
|
24152
|
+
if (existsSync12(perAgentDir)) {
|
|
23984
24153
|
const entries = readdirSync4(perAgentDir, { withFileTypes: true });
|
|
23985
24154
|
const flat = [];
|
|
23986
24155
|
const subdirs = [];
|
|
@@ -23988,11 +24157,11 @@ async function main() {
|
|
|
23988
24157
|
if (e.name.startsWith("."))
|
|
23989
24158
|
continue;
|
|
23990
24159
|
if ((e.isFile() || e.isSocket()) && e.name.endsWith(".sock")) {
|
|
23991
|
-
flat.push(
|
|
24160
|
+
flat.push(resolve9(perAgentDir, e.name));
|
|
23992
24161
|
continue;
|
|
23993
24162
|
}
|
|
23994
24163
|
if (e.isDirectory()) {
|
|
23995
|
-
const candidate =
|
|
24164
|
+
const candidate = resolve9(perAgentDir, e.name, "sock");
|
|
23996
24165
|
if (socketPathToAgent(candidate) !== null) {
|
|
23997
24166
|
subdirs.push(candidate);
|
|
23998
24167
|
}
|
|
@@ -24035,7 +24204,7 @@ async function main() {
|
|
|
24035
24204
|
}
|
|
24036
24205
|
const operatorUidStr = process.env.SWITCHROOM_BROKER_OPERATOR_UID;
|
|
24037
24206
|
const operatorDir = "/run/switchroom/broker/operator";
|
|
24038
|
-
if (operatorUidStr !== undefined &&
|
|
24207
|
+
if (operatorUidStr !== undefined && existsSync12(operatorDir)) {
|
|
24039
24208
|
const operatorUid = parseInt(operatorUidStr, 10);
|
|
24040
24209
|
if (!Number.isFinite(operatorUid) || operatorUid <= 0) {
|
|
24041
24210
|
process.stderr.write(`[vault-broker] SWITCHROOM_BROKER_OPERATOR_UID='${operatorUidStr}' is not a positive integer; skipping operator listener
|