switchroom 0.18.10 → 0.18.11
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/dist/cli/switchroom.js +188 -81
- package/dist/cli/ui/apple-touch-icon.png +0 -0
- package/dist/cli/ui/favicon-32.png +0 -0
- package/dist/cli/ui/favicon.ico +0 -0
- package/dist/host-control/main.js +758 -256
- package/dist/vault/approvals/kernel-server.js +1 -0
- package/dist/vault/broker/server.js +1 -0
- package/package.json +1 -1
- package/telegram-plugin/dist/gateway/gateway.js +5 -4
package/dist/cli/switchroom.js
CHANGED
|
@@ -17428,6 +17428,40 @@ function registerAgentConfigCommands(program2) {
|
|
|
17428
17428
|
process.exit(1);
|
|
17429
17429
|
}
|
|
17430
17430
|
}));
|
|
17431
|
+
config.command("check").description("Validate a switchroom.yaml (YAML parse incl. duplicate keys, schema, cross-field checks) without starting anything").option("--file <path>", "YAML file to validate (defaults to the discovered switchroom.yaml)").action(async (opts) => {
|
|
17432
|
+
let path;
|
|
17433
|
+
try {
|
|
17434
|
+
path = opts.file ?? findConfigFile();
|
|
17435
|
+
} catch (err) {
|
|
17436
|
+
if (!(err instanceof ConfigError))
|
|
17437
|
+
throw err;
|
|
17438
|
+
process.stderr.write(`INVALID: switchroom.yaml not found
|
|
17439
|
+
${err.message}
|
|
17440
|
+
`);
|
|
17441
|
+
for (const d of err.details ?? []) {
|
|
17442
|
+
process.stderr.write(`${d}
|
|
17443
|
+
`);
|
|
17444
|
+
}
|
|
17445
|
+
process.exit(1);
|
|
17446
|
+
}
|
|
17447
|
+
try {
|
|
17448
|
+
loadConfig(path);
|
|
17449
|
+
} catch (err) {
|
|
17450
|
+
if (err instanceof ConfigError) {
|
|
17451
|
+
process.stderr.write(`INVALID: ${path}
|
|
17452
|
+
${err.message}
|
|
17453
|
+
`);
|
|
17454
|
+
for (const d of err.details ?? []) {
|
|
17455
|
+
process.stderr.write(`${d}
|
|
17456
|
+
`);
|
|
17457
|
+
}
|
|
17458
|
+
process.exit(1);
|
|
17459
|
+
}
|
|
17460
|
+
throw err;
|
|
17461
|
+
}
|
|
17462
|
+
process.stdout.write(`OK: ${path} is a valid switchroom config
|
|
17463
|
+
`);
|
|
17464
|
+
});
|
|
17431
17465
|
config.command("whoami").description("Emit what this agent is allowed to do (tools, MCP, vault key-names, powers) as JSON \u2014 its own sandbox, computed from live enforcement").option("--agent <name>", "Target agent (defaults to $SWITCHROOM_AGENT_NAME)").action(withConfigError(async (opts) => {
|
|
17432
17466
|
let agent;
|
|
17433
17467
|
try {
|
|
@@ -17588,6 +17622,7 @@ function registerAgentConfigCommands(program2) {
|
|
|
17588
17622
|
var AUDIT_ROOT;
|
|
17589
17623
|
var init_agent_config = __esm(() => {
|
|
17590
17624
|
init_helpers();
|
|
17625
|
+
init_loader();
|
|
17591
17626
|
init_merge();
|
|
17592
17627
|
init_acl();
|
|
17593
17628
|
init_cron_introspect();
|
|
@@ -63399,9 +63434,9 @@ __export(exports_server, {
|
|
|
63399
63434
|
dispatchTool: () => dispatchTool,
|
|
63400
63435
|
TOOLS: () => TOOLS
|
|
63401
63436
|
});
|
|
63402
|
-
import { spawnSync as
|
|
63437
|
+
import { spawnSync as spawnSync18 } from "node:child_process";
|
|
63403
63438
|
function execCli(args, stdin) {
|
|
63404
|
-
const r =
|
|
63439
|
+
const r = spawnSync18(CLI_BIN, args, {
|
|
63405
63440
|
encoding: "utf-8",
|
|
63406
63441
|
env: process.env,
|
|
63407
63442
|
timeout: 15000,
|
|
@@ -63905,6 +63940,7 @@ __export(exports_server2, {
|
|
|
63905
63940
|
resolveAuditLogPath: () => resolveAuditLogPath,
|
|
63906
63941
|
getLastUpdateApplyStatus: () => getLastUpdateApplyStatus,
|
|
63907
63942
|
dispatchTool: () => dispatchTool2,
|
|
63943
|
+
UPDATE_APPLY_SEMVER_PIN_ADVISORY: () => UPDATE_APPLY_SEMVER_PIN_ADVISORY,
|
|
63908
63944
|
TOOLS: () => TOOLS2
|
|
63909
63945
|
});
|
|
63910
63946
|
import { randomBytes as randomBytes16 } from "node:crypto";
|
|
@@ -63919,7 +63955,7 @@ function wireTimeoutForOp(op) {
|
|
|
63919
63955
|
return WIRE_TIMEOUT_MS_BY_OP[op] ?? DEFAULT_WIRE_TIMEOUT_MS;
|
|
63920
63956
|
}
|
|
63921
63957
|
async function dispatchTool2(name, args) {
|
|
63922
|
-
if (name === "get_status") {
|
|
63958
|
+
if (name === "get_status" && args.request_id === undefined) {
|
|
63923
63959
|
return getLastUpdateApplyStatus();
|
|
63924
63960
|
}
|
|
63925
63961
|
if (!SELF_AGENT) {
|
|
@@ -63930,6 +63966,7 @@ async function dispatchTool2(name, args) {
|
|
|
63930
63966
|
return errorText2(`hostd MCP: socket not bound at ${sockPath}. The host-control ` + `daemon is either not installed (run \`switchroom hostd install\`) ` + `or this agent isn't admin-flagged in switchroom.yaml. RFC C ` + `bind-mounts the per-agent socket only when host_control.enabled ` + `is true AND the agent has admin: true.`);
|
|
63931
63967
|
}
|
|
63932
63968
|
let req;
|
|
63969
|
+
let advisory;
|
|
63933
63970
|
switch (name) {
|
|
63934
63971
|
case "agent_restart": {
|
|
63935
63972
|
if (!args.name)
|
|
@@ -64022,6 +64059,9 @@ async function dispatchTool2(name, args) {
|
|
|
64022
64059
|
if (args.pin && !/^(sha-[0-9a-f]{7,40}|v\d+\.\d+\.\d+)$/.test(args.pin)) {
|
|
64023
64060
|
return errorText2(`update_apply: pin "${args.pin}" is invalid. Expected sha-<7-40 hex> or v<semver>.`);
|
|
64024
64061
|
}
|
|
64062
|
+
if (args.pin && ROLLOUT_SEMVER_PIN_RE.test(args.pin)) {
|
|
64063
|
+
advisory = UPDATE_APPLY_SEMVER_PIN_ADVISORY;
|
|
64064
|
+
}
|
|
64025
64065
|
req = {
|
|
64026
64066
|
v: 1,
|
|
64027
64067
|
op: "update_apply",
|
|
@@ -64060,6 +64100,18 @@ async function dispatchTool2(name, args) {
|
|
|
64060
64100
|
};
|
|
64061
64101
|
break;
|
|
64062
64102
|
}
|
|
64103
|
+
case "get_status": {
|
|
64104
|
+
if (typeof args.request_id !== "string" || !args.request_id) {
|
|
64105
|
+
return errorText2("get_status: request_id must be a non-empty string");
|
|
64106
|
+
}
|
|
64107
|
+
req = {
|
|
64108
|
+
v: 1,
|
|
64109
|
+
op: "get_status",
|
|
64110
|
+
request_id: makeRequestId("mcp-get-status"),
|
|
64111
|
+
args: { target_request_id: args.request_id }
|
|
64112
|
+
};
|
|
64113
|
+
break;
|
|
64114
|
+
}
|
|
64063
64115
|
case "config_propose_edit": {
|
|
64064
64116
|
if (!args.unified_diff || typeof args.unified_diff !== "string") {
|
|
64065
64117
|
return errorText2("config_propose_edit: unified_diff is required (non-empty string).");
|
|
@@ -64095,7 +64147,11 @@ async function dispatchTool2(name, args) {
|
|
|
64095
64147
|
return errorText2(`hostd wire error (request_id=${req.request_id}): ` + `${err2.message}`);
|
|
64096
64148
|
}
|
|
64097
64149
|
if (resp.result === "started" || resp.result === "completed") {
|
|
64098
|
-
|
|
64150
|
+
const out = jsonText2(resp);
|
|
64151
|
+
if (advisory) {
|
|
64152
|
+
out.content.push({ type: "text", text: advisory });
|
|
64153
|
+
}
|
|
64154
|
+
return out;
|
|
64099
64155
|
}
|
|
64100
64156
|
const content = [
|
|
64101
64157
|
{ type: "text", text: JSON.stringify(resp) }
|
|
@@ -64167,7 +64223,7 @@ async function runHostdMcpServer() {
|
|
|
64167
64223
|
const transport = new StdioServerTransport;
|
|
64168
64224
|
await server.connect(transport);
|
|
64169
64225
|
}
|
|
64170
|
-
var SELF_AGENT, DEFAULT_WIRE_TIMEOUT_MS = 1e4, WIRE_TIMEOUT_MS_BY_OP, ROLLOUT_SEMVER_PIN_RE, TOOLS2;
|
|
64226
|
+
var SELF_AGENT, DEFAULT_WIRE_TIMEOUT_MS = 1e4, WIRE_TIMEOUT_MS_BY_OP, ROLLOUT_SEMVER_PIN_RE, UPDATE_APPLY_SEMVER_PIN_ADVISORY, TOOLS2;
|
|
64171
64227
|
var init_server4 = __esm(() => {
|
|
64172
64228
|
init_server2();
|
|
64173
64229
|
init_stdio2();
|
|
@@ -64179,6 +64235,7 @@ var init_server4 = __esm(() => {
|
|
|
64179
64235
|
config_propose_edit: 61 * 60 * 1000
|
|
64180
64236
|
};
|
|
64181
64237
|
ROLLOUT_SEMVER_PIN_RE = /^v\d+\.\d+\.\d+$/;
|
|
64238
|
+
UPDATE_APPLY_SEMVER_PIN_ADVISORY = "\u26a0\ufe0f advisory: update_apply with a semver pin is the BLUNT path \u2014 all " + "containers recreate at once, with no canary, no per-agent version " + "assert, and no stop-on-mismatch. For fleet version rolls prefer the " + "`rollout` tool (staggered, canary-gated, version-asserted; also " + "refreshes the web + hindsight singletons). This request was still " + "dispatched \u2014 this is a steer, not a refusal.";
|
|
64182
64239
|
TOOLS2 = [
|
|
64183
64240
|
{
|
|
64184
64241
|
name: "agent_restart",
|
|
@@ -64302,7 +64359,7 @@ var init_server4 = __esm(() => {
|
|
|
64302
64359
|
},
|
|
64303
64360
|
{
|
|
64304
64361
|
name: "update_apply",
|
|
64305
|
-
description: "Execute a fleet-wide update: pull images, regenerate " + "scaffolds, recreate containers. Admin-only at the wire layer. " + "Returns `started` once dispatched \u2014 the actual work runs " + "async on the host and the caller's own agent container will " + "be recreated as part of the cycle. This is operator-gated \u2014 every " + "call surfaces a Telegram approval card. ALWAYS pass a one-line " + "`reason` explaining why you're applying this update; it renders on " + "the card's `why:` line so the operator can decide in context.",
|
|
64362
|
+
description: "Execute a fleet-wide update: pull images, regenerate " + "scaffolds, recreate containers. This is the BLUNT all-at-once " + "path \u2014 no canary, no per-agent version assert, no stop-on-" + "mismatch. For rolling the fleet to a specific VERSION (a semver " + "pin), prefer the `rollout` tool instead: staggered, canary-gated, " + "version-asserted, and it also refreshes the web + hindsight " + "singletons. Calling update_apply with a semver pin still works " + "(the operator may want the blunt path deliberately) but the " + "result includes an advisory warning. Admin-only at the wire layer. " + "Returns `started` once dispatched \u2014 the actual work runs " + "async on the host and the caller's own agent container will " + "be recreated as part of the cycle. This is operator-gated \u2014 every " + "call surfaces a Telegram approval card. ALWAYS pass a one-line " + "`reason` explaining why you're applying this update; it renders on " + "the card's `why:` line so the operator can decide in context.",
|
|
64306
64363
|
inputSchema: {
|
|
64307
64364
|
type: "object",
|
|
64308
64365
|
properties: {
|
|
@@ -64334,7 +64391,7 @@ var init_server4 = __esm(() => {
|
|
|
64334
64391
|
},
|
|
64335
64392
|
{
|
|
64336
64393
|
name: "rollout",
|
|
64337
|
-
description: "SAFELY roll the fleet to a pinned SEMVER version, staggered and " + "canary-gated (#2487). Unlike update_apply (a blunt all-at-once " + "recreate), this restarts agents one at a time canary-first, asserts " + "each agent's in-container `switchroom --version` matches the target, " + "and STOPS on the first mismatch \u2014 so a bad build fails on the canary " + "before touching the rest of the fleet. The durable release.pin is " + "persisted only AFTER the canary confirms (a failed canary never " + "strands a bad pin). `pin` MUST be a tagged semver (vX.Y.Z) \u2014 sha " + "pins are rejected because the version assert needs a semver to " + "compare against. When the target pin is NEWER than hostd's own CLI " + "(every freshly tagged release), hostd SELF-BUMPS first (#2645): its " + "container restarts on the target image (~30-60s blip on this MCP " + "socket), then the roll resumes AUTOMATICALLY under the SAME " + "request_id \u2014 do NOT re-issue the rollout during the blip; poll " + "get_status
|
|
64394
|
+
description: "SAFELY roll the fleet to a pinned SEMVER version, staggered and " + "canary-gated (#2487). Unlike update_apply (a blunt all-at-once " + "recreate), this restarts agents one at a time canary-first, asserts " + "each agent's in-container `switchroom --version` matches the target, " + "and STOPS on the first mismatch \u2014 so a bad build fails on the canary " + "before touching the rest of the fleet. The durable release.pin is " + "persisted only AFTER the canary confirms (a failed canary never " + "strands a bad pin). `pin` MUST be a tagged semver (vX.Y.Z) \u2014 sha " + "pins are rejected because the version assert needs a semver to " + "compare against. When the target pin is NEWER than hostd's own CLI " + "(every freshly tagged release), hostd SELF-BUMPS first (#2645): its " + "container restarts on the target image (~30-60s blip on this MCP " + "socket), then the roll resumes AUTOMATICALLY under the SAME " + "request_id \u2014 do NOT re-issue the rollout during the blip; poll " + "get_status (pass the returned request_id as its `request_id` " + "argument) once the socket is back. " + "The web + hindsight singletons are refreshed in-plan on this path " + "(only the hostd template regen + host operator CLI stay host-side; " + "the terminal warnings name exactly what is still on the prior " + "version and the commands to finish). By default downgrade pins " + "are rejected \u2014 pass `allow_downgrade: true` for the operator-approved " + "rollback path to a known-good earlier tag; all other safety rails " + "(canary order, version-assert, stop-on-mismatch) apply unchanged. " + "Admin-only at the wire layer AND deliberately NOT pre-approved \u2014 every " + "call surfaces a Telegram approval card for the operator to tap. " + "ALWAYS pass a one-line `reason` explaining why you're rolling the " + "fleet to this pin; it renders on the card's `why:` line so the " + "operator can decide in context. " + "Returns `started`; poll get_status with `request_id` for the " + "structured outcome (which agents rolled / where it stopped).",
|
|
64338
64395
|
inputSchema: {
|
|
64339
64396
|
type: "object",
|
|
64340
64397
|
required: ["pin"],
|
|
@@ -64357,11 +64414,11 @@ var init_server4 = __esm(() => {
|
|
|
64357
64414
|
},
|
|
64358
64415
|
skip_web: {
|
|
64359
64416
|
type: "boolean",
|
|
64360
|
-
description: "Skip the web
|
|
64417
|
+
description: "Skip the web refresh step (leaves switchroom-web on the prior " + "version \u2014 the terminal warning names the host-side command to " + "finish). The hostd self-refresh is deferred on this path " + "regardless of this flag."
|
|
64361
64418
|
},
|
|
64362
64419
|
allow_downgrade: {
|
|
64363
64420
|
type: "boolean",
|
|
64364
|
-
description: "Operator-approved rollback to a known-good earlier tag (#2487 PR2). " + "When true, the downgrade guard is relaxed so `pin` may be older " + "than the current release.pin. All other safety rails (canary order, " + "version-assert, stop-on-mismatch, persist-after-canary, " + "hostd
|
|
64421
|
+
description: "Operator-approved rollback to a known-good earlier tag (#2487 PR2). " + "When true, the downgrade guard is relaxed so `pin` may be older " + "than the current release.pin. All other safety rails (canary order, " + "version-assert, stop-on-mismatch, persist-after-canary, " + "hostd deferral) apply unchanged. Still gated by the operator " + "approval card \u2014 not pre-approved."
|
|
64365
64422
|
}
|
|
64366
64423
|
}
|
|
64367
64424
|
}
|
|
@@ -64394,10 +64451,17 @@ var init_server4 = __esm(() => {
|
|
|
64394
64451
|
},
|
|
64395
64452
|
{
|
|
64396
64453
|
name: "get_status",
|
|
64397
|
-
description: "
|
|
64454
|
+
description: "Look up the status of a prior async fleet mutation. With " + "`request_id` (the id echoed by rollout / update_apply / " + "agent_restart etc.), asks the host-control daemon for that " + "specific request's live status \u2014 for an in-flight `rollout` " + "this is the ONLY way to see progress: the response payload " + "carries the current phase, n/m counters, rolled[] agents, " + "failedStep/failedAgent and pin, and it keeps working across a " + "hostd self-bump/restart via the durable audit-log fallback. " + "This is the tool the `rollout` description tells you to poll " + "with the returned request_id. Without `request_id`, falls back " + "to reading the most recent terminal `update_apply` audit row " + "(channel, pin, resolved_sha, install_context, result, " + "exit_code, stderr_tail) \u2014 use that no-arg form only to report " + "the last blunt update on demand; it CANNOT see rollouts or " + "in-flight work. Read-only; returns JSON.",
|
|
64398
64455
|
inputSchema: {
|
|
64399
64456
|
type: "object",
|
|
64400
|
-
properties: {
|
|
64457
|
+
properties: {
|
|
64458
|
+
request_id: {
|
|
64459
|
+
type: "string",
|
|
64460
|
+
minLength: 1,
|
|
64461
|
+
maxLength: 128,
|
|
64462
|
+
description: "The request_id returned by a prior mutating verb " + "(e.g. `mcp-rollout-\u2026`). When set, the daemon returns " + "that request's rich per-request status (phase, n/m, " + "rolled agents, outcome). Omit to read the last " + "terminal update_apply audit row instead."
|
|
64463
|
+
}
|
|
64464
|
+
},
|
|
64401
64465
|
additionalProperties: false
|
|
64402
64466
|
}
|
|
64403
64467
|
}
|
|
@@ -64913,8 +64977,8 @@ import { existsSync, readFileSync } from "node:fs";
|
|
|
64913
64977
|
import { dirname, join } from "node:path";
|
|
64914
64978
|
|
|
64915
64979
|
// src/build-info.ts
|
|
64916
|
-
var VERSION = "0.18.
|
|
64917
|
-
var COMMIT_SHA = "
|
|
64980
|
+
var VERSION = "0.18.11";
|
|
64981
|
+
var COMMIT_SHA = "8fbd9574";
|
|
64918
64982
|
|
|
64919
64983
|
// src/cli/resolve-version.ts
|
|
64920
64984
|
function readPackageVersion() {
|
|
@@ -85054,13 +85118,52 @@ function registerUpdateCommand(program3) {
|
|
|
85054
85118
|
|
|
85055
85119
|
// src/cli/rollout.ts
|
|
85056
85120
|
init_helpers();
|
|
85057
|
-
import { spawnSync as
|
|
85121
|
+
import { spawnSync as spawnSync14 } from "node:child_process";
|
|
85058
85122
|
import { readFileSync as readFileSync58, chownSync as chownSync6, statSync as statSync30 } from "node:fs";
|
|
85059
85123
|
import { homedir as homedir40 } from "node:os";
|
|
85060
85124
|
init_operator_uid();
|
|
85061
85125
|
init_atomic();
|
|
85062
85126
|
init_audit_reader();
|
|
85063
85127
|
init_hindsight();
|
|
85128
|
+
|
|
85129
|
+
// src/cli/deploy-version-guard.ts
|
|
85130
|
+
import { spawnSync as spawnSync13 } from "node:child_process";
|
|
85131
|
+
var defaultRunner3 = (args) => {
|
|
85132
|
+
const r = spawnSync13("docker", args, { encoding: "utf8" });
|
|
85133
|
+
return { ok: r.status === 0, stdout: r.stdout ?? "", stderr: r.stderr ?? "" };
|
|
85134
|
+
};
|
|
85135
|
+
function deployedImageTag(container, run = defaultRunner3) {
|
|
85136
|
+
const r = run(["inspect", "-f", "{{.Config.Image}}", container]);
|
|
85137
|
+
if (!r.ok)
|
|
85138
|
+
return null;
|
|
85139
|
+
const ref = r.stdout.trim();
|
|
85140
|
+
if (!ref)
|
|
85141
|
+
return null;
|
|
85142
|
+
const noDigest = ref.split("@")[0];
|
|
85143
|
+
const colon = noDigest.lastIndexOf(":");
|
|
85144
|
+
if (colon < 0)
|
|
85145
|
+
return null;
|
|
85146
|
+
const tag = noDigest.slice(colon + 1);
|
|
85147
|
+
return tag && !tag.includes("/") ? tag : null;
|
|
85148
|
+
}
|
|
85149
|
+
function checkDowngrade(opts) {
|
|
85150
|
+
const running = deployedImageTag(opts.container, opts.run);
|
|
85151
|
+
if (opts.allowDowngrade)
|
|
85152
|
+
return { skip: false, running };
|
|
85153
|
+
const cmp = compareReleaseTags(opts.targetTag, running);
|
|
85154
|
+
if (cmp !== null && cmp < 0) {
|
|
85155
|
+
return {
|
|
85156
|
+
skip: true,
|
|
85157
|
+
running,
|
|
85158
|
+
message: `Skipping ${opts.container} deploy \u2014 it is already on ${running}, newer than the ` + `requested ${opts.targetTag}.
|
|
85159
|
+
` + ` Refusing to downgrade: this guards against a concurrent rollout/update ` + `reverting a newer build.
|
|
85160
|
+
` + ` To force the downgrade, re-run with --allow-downgrade.`
|
|
85161
|
+
};
|
|
85162
|
+
}
|
|
85163
|
+
return { skip: false, running };
|
|
85164
|
+
}
|
|
85165
|
+
|
|
85166
|
+
// src/cli/rollout.ts
|
|
85064
85167
|
function normalizeVersion(v) {
|
|
85065
85168
|
return v.trim().replace(/^v/, "");
|
|
85066
85169
|
}
|
|
@@ -85089,6 +85192,9 @@ function planRollout(agents, opts = {}) {
|
|
|
85089
85192
|
} else if (opts.pinToPersist) {
|
|
85090
85193
|
steps.push({ kind: "persist-pin", pin: opts.pinToPersist });
|
|
85091
85194
|
}
|
|
85195
|
+
if (!opts.skipWeb) {
|
|
85196
|
+
steps.push({ kind: "refresh-web" });
|
|
85197
|
+
}
|
|
85092
85198
|
steps.push({ kind: "refresh-hindsight" });
|
|
85093
85199
|
steps.push({ kind: "sweep" });
|
|
85094
85200
|
return steps;
|
|
@@ -85240,15 +85346,24 @@ function executeRollout(steps, target, deps, execOpts = {}) {
|
|
|
85240
85346
|
break;
|
|
85241
85347
|
}
|
|
85242
85348
|
case "refresh-web": {
|
|
85243
|
-
|
|
85244
|
-
|
|
85245
|
-
|
|
85246
|
-
|
|
85349
|
+
const downgradeArgs = execOpts.allowDowngrade ? ["--allow-downgrade"] : [];
|
|
85350
|
+
deps.log(`ROLL_STEP refresh-web \u2014 webd install --tag ${target}` + (execOpts.allowDowngrade ? " --allow-downgrade" : ""));
|
|
85351
|
+
emit({ phase: "web-refresh", target });
|
|
85352
|
+
const r = deps.run(["webd", "install", "--tag", target, ...downgradeArgs]);
|
|
85353
|
+
if (r.status !== 0) {
|
|
85354
|
+
warnings.push(`web refresh FAILED (non-fatal \u2014 agents already rolled) so ` + `switchroom-web is still on the PRIOR version. Finish it ` + `host-side: \`switchroom webd install --tag ${target}\`.`);
|
|
85355
|
+
break;
|
|
85356
|
+
}
|
|
85357
|
+
const webTag = deps.webImageTag?.();
|
|
85358
|
+
if (webTag !== undefined && webTag !== null && isVersionAssertable(webTag) && normalizeVersion(webTag) !== targetNorm) {
|
|
85359
|
+
warnings.push(`web refresh completed but switchroom-web is running ${webTag}, ` + `NOT the roll target ${target} (most likely its downgrade guard ` + `skipped the install). Finish it host-side: \`switchroom webd ` + `install --tag ${target} --allow-downgrade\`.`);
|
|
85360
|
+
}
|
|
85247
85361
|
break;
|
|
85248
85362
|
}
|
|
85249
85363
|
case "refresh-hostd": {
|
|
85250
|
-
|
|
85251
|
-
|
|
85364
|
+
const downgradeArgs = execOpts.allowDowngrade ? ["--allow-downgrade"] : [];
|
|
85365
|
+
deps.log(`ROLL_STEP refresh-hostd \u2014 hostd install --tag ${target}` + (execOpts.allowDowngrade ? " --allow-downgrade" : ""));
|
|
85366
|
+
const r = deps.run(["hostd", "install", "--tag", target, ...downgradeArgs]);
|
|
85252
85367
|
if (r.status !== 0)
|
|
85253
85368
|
warnings.push(`hostd refresh failed (non-fatal); agents already rolled`);
|
|
85254
85369
|
break;
|
|
@@ -85303,7 +85418,7 @@ function resolveRollbackTarget(auditLogPath) {
|
|
|
85303
85418
|
}
|
|
85304
85419
|
function registerRolloutCommand(program3) {
|
|
85305
85420
|
const hostdCtx = isHostdContext2();
|
|
85306
|
-
program3.command("rollout").description("Deploy a pinned version to the fleet, safely (staggered restart + " + "per-agent version assert + web/hostd refresh)." + (hostdCtx ? "" : " Run with sudo.")).option("--pin <version>", "Version to roll (e.g. v0.15.18). Defaults to release.pin from config.").option("--agents <list>", "Comma-separated subset of agents to roll (default: all configured).").option("--skip-web", "Skip the web
|
|
85421
|
+
program3.command("rollout").description("Deploy a pinned version to the fleet, safely (staggered restart + " + "per-agent version assert + web/hostd refresh)." + (hostdCtx ? "" : " Run with sudo.")).option("--pin <version>", "Version to roll (e.g. v0.15.18). Defaults to release.pin from config.").option("--agents <list>", "Comma-separated subset of agents to roll (default: all configured).").option("--skip-web", "Skip the web refresh (host-shell path: also the hostd + hindsight " + "refresh). On the hostd/agent path only the web refresh is skipped \u2014 " + "hindsight is still recreated and hostd stays deferred regardless.").option("--allow-downgrade", "Permit rolling to an older semver tag (operator-approved rollback path). " + "When set, the downgrade guard is relaxed so --pin may be older than the " + "current release.pin. All other safety rails apply unchanged.").option("--dry-run", "Print the plan and exit without changing anything.").action(async (opts) => {
|
|
85307
85422
|
const config = getConfig(program3);
|
|
85308
85423
|
let resolvedPin = opts.pin;
|
|
85309
85424
|
if (opts.allowDowngrade && !resolvedPin) {
|
|
@@ -85387,11 +85502,11 @@ function registerRolloutCommand(program3) {
|
|
|
85387
85502
|
const scriptPath = process.argv[1] ?? "switchroom";
|
|
85388
85503
|
const deps = {
|
|
85389
85504
|
run: (args) => {
|
|
85390
|
-
const r =
|
|
85505
|
+
const r = spawnSync14(process.execPath, [scriptPath, ...args], { stdio: "inherit" });
|
|
85391
85506
|
return { status: r.status ?? 1 };
|
|
85392
85507
|
},
|
|
85393
85508
|
probeVersion: (agent) => {
|
|
85394
|
-
const r =
|
|
85509
|
+
const r = spawnSync14("docker", ["exec", `switchroom-${agent}`, "sh", "-lc", "switchroom --version"], { encoding: "utf8" });
|
|
85395
85510
|
if (r.status !== 0)
|
|
85396
85511
|
return null;
|
|
85397
85512
|
return (r.stdout ?? "").trim().split(`
|
|
@@ -85401,6 +85516,7 @@ function registerRolloutCommand(program3) {
|
|
|
85401
85516
|
`),
|
|
85402
85517
|
emitPhase: (phase) => process.stdout.write(encodeRolloutPhaseLine(phase) + `
|
|
85403
85518
|
`),
|
|
85519
|
+
webImageTag: () => deployedImageTag("switchroom-web"),
|
|
85404
85520
|
persistPin: (pin) => {
|
|
85405
85521
|
const before = readFileSync58(configPath, "utf8");
|
|
85406
85522
|
const after = setReleasePinInConfig(before, pin);
|
|
@@ -85422,9 +85538,12 @@ function registerRolloutCommand(program3) {
|
|
|
85422
85538
|
};
|
|
85423
85539
|
process.stdout.write(`${opts.allowDowngrade ? "Rolling back" : "Rolling"} ${requested.length} agent(s) to ${target}\u2026
|
|
85424
85540
|
`);
|
|
85425
|
-
const result = executeRollout(steps, target, deps, {
|
|
85541
|
+
const result = executeRollout(steps, target, deps, {
|
|
85542
|
+
hostdContext: hostdCtx,
|
|
85543
|
+
allowDowngrade: opts.allowDowngrade
|
|
85544
|
+
});
|
|
85426
85545
|
if (hostdCtx) {
|
|
85427
|
-
result.warnings.push(`still on the PRIOR version after this roll:
|
|
85546
|
+
result.warnings.push(`still on the PRIOR version after this roll: the host operator ` + `CLI (\`sudo npm i -g switchroom@${normalizeVersion(target)}\`). ` + `hostd's compose was tag-bumped by the self-bump when one ran; a ` + `full hostd template regen (only needed when a release changes ` + `hostd's mounts/env) is \`switchroom hostd install --tag ${target}\` ` + `host-side. An agent-invoked rollout cannot recreate its own hostd ` + `container mid-roll without killing itself.` + (opts.skipWeb ? ` --skip-web was set, so switchroom-web is ALSO still on the ` + `prior version (host-side: \`switchroom webd install --tag ${target}\`).` : ""));
|
|
85428
85547
|
process.stdout.write(encodeRolloutPhaseLine({ phase: "hostd-web-deferred", target }) + `
|
|
85429
85548
|
`);
|
|
85430
85549
|
}
|
|
@@ -85450,7 +85569,7 @@ function registerRolloutCommand(program3) {
|
|
|
85450
85569
|
});
|
|
85451
85570
|
}
|
|
85452
85571
|
function registerRollbackCommand(program3) {
|
|
85453
|
-
program3.command("rollback").description("Roll the fleet back to the previous version (operator-approved downgrade). " + "Defaults to the version captured in the last completed rollout's prior_pin. " + "Equivalent to `rollout --allow-downgrade [--pin <version>]`.").option("--to <version>", "Explicit version to roll back to (e.g. v0.15.17). " + "Omit to use the last completed rollout's prior_pin.").option("--agents <list>", "Comma-separated subset of agents to roll back (default: all configured).").option("--skip-web", "Skip the web
|
|
85572
|
+
program3.command("rollback").description("Roll the fleet back to the previous version (operator-approved downgrade). " + "Defaults to the version captured in the last completed rollout's prior_pin. " + "Equivalent to `rollout --allow-downgrade [--pin <version>]`.").option("--to <version>", "Explicit version to roll back to (e.g. v0.15.17). " + "Omit to use the last completed rollout's prior_pin.").option("--agents <list>", "Comma-separated subset of agents to roll back (default: all configured).").option("--skip-web", "Skip the web refresh (host-shell path: also the hostd + hindsight " + "refresh). On the hostd/agent path only the web refresh is skipped \u2014 " + "hindsight is still recreated and hostd stays deferred regardless.").option("--dry-run", "Print the plan and exit without changing anything.").action(async (opts) => {
|
|
85454
85573
|
const rolloutArgs = ["--allow-downgrade"];
|
|
85455
85574
|
if (opts.to)
|
|
85456
85575
|
rolloutArgs.push("--pin", opts.to);
|
|
@@ -87013,7 +87132,7 @@ init_helpers();
|
|
|
87013
87132
|
init_loader();
|
|
87014
87133
|
import { existsSync as existsSync71 } from "node:fs";
|
|
87015
87134
|
import { resolve as resolve43, sep as sep3 } from "node:path";
|
|
87016
|
-
import { spawnSync as
|
|
87135
|
+
import { spawnSync as spawnSync15 } from "node:child_process";
|
|
87017
87136
|
|
|
87018
87137
|
// src/agents/workspace.ts
|
|
87019
87138
|
import { readFile as readFile2, stat } from "node:fs/promises";
|
|
@@ -87726,7 +87845,7 @@ function registerWorkspaceCommand(program3) {
|
|
|
87726
87845
|
process.exit(1);
|
|
87727
87846
|
}
|
|
87728
87847
|
const editor = process.env["EDITOR"] ?? process.env["VISUAL"] ?? "vi";
|
|
87729
|
-
const child =
|
|
87848
|
+
const child = spawnSync15(editor, [target], { stdio: "inherit" });
|
|
87730
87849
|
if (child.status !== 0 && child.status !== null) {
|
|
87731
87850
|
process.exit(child.status);
|
|
87732
87851
|
}
|
|
@@ -87793,7 +87912,7 @@ function registerWorkspaceCommand(program3) {
|
|
|
87793
87912
|
`);
|
|
87794
87913
|
return;
|
|
87795
87914
|
}
|
|
87796
|
-
const statusResult =
|
|
87915
|
+
const statusResult = spawnSync15("git", ["status", "--short"], {
|
|
87797
87916
|
cwd: dir,
|
|
87798
87917
|
encoding: "utf-8"
|
|
87799
87918
|
});
|
|
@@ -87808,7 +87927,7 @@ function registerWorkspaceCommand(program3) {
|
|
|
87808
87927
|
return;
|
|
87809
87928
|
}
|
|
87810
87929
|
const message = opts.message || `checkpoint: ${new Date().toISOString()}`;
|
|
87811
|
-
const addResult =
|
|
87930
|
+
const addResult = spawnSync15("git", ["add", "-A"], {
|
|
87812
87931
|
cwd: dir,
|
|
87813
87932
|
encoding: "utf-8"
|
|
87814
87933
|
});
|
|
@@ -87817,7 +87936,7 @@ function registerWorkspaceCommand(program3) {
|
|
|
87817
87936
|
`);
|
|
87818
87937
|
process.exit(1);
|
|
87819
87938
|
}
|
|
87820
|
-
const commitResult =
|
|
87939
|
+
const commitResult = spawnSync15("git", ["commit", "-m", message], {
|
|
87821
87940
|
cwd: dir,
|
|
87822
87941
|
encoding: "utf-8"
|
|
87823
87942
|
});
|
|
@@ -87826,7 +87945,7 @@ function registerWorkspaceCommand(program3) {
|
|
|
87826
87945
|
`);
|
|
87827
87946
|
process.exit(1);
|
|
87828
87947
|
}
|
|
87829
|
-
const shaResult =
|
|
87948
|
+
const shaResult = spawnSync15("git", ["rev-parse", "--short", "HEAD"], {
|
|
87830
87949
|
cwd: dir,
|
|
87831
87950
|
encoding: "utf-8"
|
|
87832
87951
|
});
|
|
@@ -87847,7 +87966,7 @@ function registerWorkspaceCommand(program3) {
|
|
|
87847
87966
|
`);
|
|
87848
87967
|
return;
|
|
87849
87968
|
}
|
|
87850
|
-
const child =
|
|
87969
|
+
const child = spawnSync15("git", ["status", "--short"], {
|
|
87851
87970
|
cwd: dir,
|
|
87852
87971
|
stdio: "inherit"
|
|
87853
87972
|
});
|
|
@@ -92224,7 +92343,7 @@ import {
|
|
|
92224
92343
|
} from "node:fs";
|
|
92225
92344
|
import { tmpdir as tmpdir5, homedir as homedir50 } from "node:os";
|
|
92226
92345
|
import { dirname as dirname30, join as join86, relative as relative2, resolve as resolve52 } from "node:path";
|
|
92227
|
-
import { spawnSync as
|
|
92346
|
+
import { spawnSync as spawnSync16 } from "node:child_process";
|
|
92228
92347
|
|
|
92229
92348
|
// src/cli/skill-common.ts
|
|
92230
92349
|
var import_yaml24 = __toESM(require_dist(), 1);
|
|
@@ -92517,7 +92636,7 @@ function loadFromDir(dir) {
|
|
|
92517
92636
|
function loadFromTarball(tarPath) {
|
|
92518
92637
|
const isGz = tarPath.endsWith(".gz") || tarPath.endsWith(".tgz");
|
|
92519
92638
|
const listFlags = isGz ? ["-tzf"] : ["-tf"];
|
|
92520
|
-
const list2 =
|
|
92639
|
+
const list2 = spawnSync16("tar", [...listFlags, tarPath], {
|
|
92521
92640
|
encoding: "utf-8",
|
|
92522
92641
|
stdio: ["ignore", "pipe", "pipe"]
|
|
92523
92642
|
});
|
|
@@ -92534,7 +92653,7 @@ function loadFromTarball(tarPath) {
|
|
|
92534
92653
|
const staging = mkdtempSync5(join86(tmpdir5(), "skill-apply-extract-"));
|
|
92535
92654
|
try {
|
|
92536
92655
|
const flags = isGz ? ["-xzf"] : ["-xf"];
|
|
92537
|
-
const r =
|
|
92656
|
+
const r = spawnSync16("tar", [
|
|
92538
92657
|
...flags,
|
|
92539
92658
|
tarPath,
|
|
92540
92659
|
"-C",
|
|
@@ -92609,7 +92728,7 @@ function validatePayload(name, files) {
|
|
|
92609
92728
|
if (errors2.length === 0) {
|
|
92610
92729
|
for (const [path8, content] of Object.entries(files)) {
|
|
92611
92730
|
if (SH_SCRIPT_RE2.test(path8)) {
|
|
92612
|
-
const r =
|
|
92731
|
+
const r = spawnSync16("bash", ["-n"], {
|
|
92613
92732
|
input: content,
|
|
92614
92733
|
encoding: "utf-8"
|
|
92615
92734
|
});
|
|
@@ -92621,7 +92740,7 @@ function validatePayload(name, files) {
|
|
|
92621
92740
|
const tmpPy = join86(tmp, "check.py");
|
|
92622
92741
|
try {
|
|
92623
92742
|
writeFileSync41(tmpPy, content);
|
|
92624
|
-
const r =
|
|
92743
|
+
const r = spawnSync16("python3", ["-m", "py_compile", tmpPy], {
|
|
92625
92744
|
encoding: "utf-8"
|
|
92626
92745
|
});
|
|
92627
92746
|
if (r.status !== 0) {
|
|
@@ -92784,7 +92903,7 @@ function registerSkillCommand(program3) {
|
|
|
92784
92903
|
\u2713 Wrote ${name} to ${currentDir}`));
|
|
92785
92904
|
const applyBin = process.argv[1] ?? "switchroom";
|
|
92786
92905
|
console.log(source_default.gray(`Running \`switchroom apply --non-interactive\`...`));
|
|
92787
|
-
const r =
|
|
92906
|
+
const r = spawnSync16(process.argv0, [applyBin, "apply", "--non-interactive"], { stdio: "inherit" });
|
|
92788
92907
|
if (r.status !== 0) {
|
|
92789
92908
|
console.error(source_default.yellow(`(warning: \`switchroom apply\` exited ${r.status} \u2014 skill is ` + `in the pool but symlinks may not be refreshed. Re-run manually.)`));
|
|
92790
92909
|
}
|
|
@@ -92817,7 +92936,7 @@ import {
|
|
|
92817
92936
|
} from "node:fs";
|
|
92818
92937
|
import { dirname as dirname31, join as join87, relative as relative3, resolve as resolve53 } from "node:path";
|
|
92819
92938
|
import { homedir as homedir51, tmpdir as tmpdir6 } from "node:os";
|
|
92820
|
-
import { spawnSync as
|
|
92939
|
+
import { spawnSync as spawnSync17 } from "node:child_process";
|
|
92821
92940
|
init_helpers();
|
|
92822
92941
|
init_agent_config();
|
|
92823
92942
|
init_source();
|
|
@@ -93018,7 +93137,7 @@ function behavioralValidate(files) {
|
|
|
93018
93137
|
const errors2 = [];
|
|
93019
93138
|
for (const [path8, content] of Object.entries(files)) {
|
|
93020
93139
|
if (SH_SCRIPT_RE.test(path8)) {
|
|
93021
|
-
const r =
|
|
93140
|
+
const r = spawnSync17("bash", ["-n"], { input: content, encoding: "utf-8" });
|
|
93022
93141
|
if (r.status !== 0) {
|
|
93023
93142
|
errors2.push(`${path8} fails \`bash -n\`: ${(r.stderr ?? "").trim()}`);
|
|
93024
93143
|
}
|
|
@@ -93027,7 +93146,7 @@ function behavioralValidate(files) {
|
|
|
93027
93146
|
const tmpPy = join87(tmp, "check.py");
|
|
93028
93147
|
try {
|
|
93029
93148
|
writeFileSync42(tmpPy, content);
|
|
93030
|
-
const r =
|
|
93149
|
+
const r = spawnSync17("python3", ["-m", "py_compile", tmpPy], {
|
|
93031
93150
|
encoding: "utf-8"
|
|
93032
93151
|
});
|
|
93033
93152
|
if (r.status !== 0) {
|
|
@@ -93823,43 +93942,6 @@ import { homedir as homedir54 } from "node:os";
|
|
|
93823
93942
|
import { join as join90 } from "node:path";
|
|
93824
93943
|
import { spawnSync as spawnSync20 } from "node:child_process";
|
|
93825
93944
|
|
|
93826
|
-
// src/cli/deploy-version-guard.ts
|
|
93827
|
-
import { spawnSync as spawnSync18 } from "node:child_process";
|
|
93828
|
-
var defaultRunner3 = (args) => {
|
|
93829
|
-
const r = spawnSync18("docker", args, { encoding: "utf8" });
|
|
93830
|
-
return { ok: r.status === 0, stdout: r.stdout ?? "", stderr: r.stderr ?? "" };
|
|
93831
|
-
};
|
|
93832
|
-
function deployedImageTag(container, run = defaultRunner3) {
|
|
93833
|
-
const r = run(["inspect", "-f", "{{.Config.Image}}", container]);
|
|
93834
|
-
if (!r.ok)
|
|
93835
|
-
return null;
|
|
93836
|
-
const ref = r.stdout.trim();
|
|
93837
|
-
if (!ref)
|
|
93838
|
-
return null;
|
|
93839
|
-
const noDigest = ref.split("@")[0];
|
|
93840
|
-
const colon = noDigest.lastIndexOf(":");
|
|
93841
|
-
if (colon < 0)
|
|
93842
|
-
return null;
|
|
93843
|
-
const tag = noDigest.slice(colon + 1);
|
|
93844
|
-
return tag && !tag.includes("/") ? tag : null;
|
|
93845
|
-
}
|
|
93846
|
-
function checkDowngrade(opts) {
|
|
93847
|
-
const running = deployedImageTag(opts.container, opts.run);
|
|
93848
|
-
if (opts.allowDowngrade)
|
|
93849
|
-
return { skip: false, running };
|
|
93850
|
-
const cmp = compareReleaseTags(opts.targetTag, running);
|
|
93851
|
-
if (cmp !== null && cmp < 0) {
|
|
93852
|
-
return {
|
|
93853
|
-
skip: true,
|
|
93854
|
-
running,
|
|
93855
|
-
message: `Skipping ${opts.container} deploy \u2014 it is already on ${running}, newer than the ` + `requested ${opts.targetTag}.
|
|
93856
|
-
` + ` Refusing to downgrade: this guards against a concurrent rollout/update ` + `reverting a newer build.
|
|
93857
|
-
` + ` To force the downgrade, re-run with --allow-downgrade.`
|
|
93858
|
-
};
|
|
93859
|
-
}
|
|
93860
|
-
return { skip: false, running };
|
|
93861
|
-
}
|
|
93862
|
-
|
|
93863
93945
|
// src/cli/singleton-stale-cleanup.ts
|
|
93864
93946
|
import { spawnSync as spawnSync19 } from "node:child_process";
|
|
93865
93947
|
function makeDockerRunner() {
|
|
@@ -94301,7 +94383,7 @@ The log is created when hostd handles its first privileged-verb request.`));
|
|
|
94301
94383
|
init_source();
|
|
94302
94384
|
init_helpers();
|
|
94303
94385
|
init_operator_uid();
|
|
94304
|
-
import { existsSync as existsSync94, mkdirSync as mkdirSync53, writeFileSync as writeFileSync44, copyFileSync as copyFileSync14 } from "node:fs";
|
|
94386
|
+
import { chownSync as chownSync9, existsSync as existsSync94, mkdirSync as mkdirSync53, writeFileSync as writeFileSync44, copyFileSync as copyFileSync14 } from "node:fs";
|
|
94305
94387
|
import { homedir as homedir55 } from "node:os";
|
|
94306
94388
|
import { join as join91 } from "node:path";
|
|
94307
94389
|
import { spawnSync as spawnSync21 } from "node:child_process";
|
|
@@ -94310,6 +94392,16 @@ function resolveWebImageTag(explicitTag, release) {
|
|
|
94310
94392
|
return explicitTag;
|
|
94311
94393
|
return resolveImageTag(resolveRelease({ root: release }));
|
|
94312
94394
|
}
|
|
94395
|
+
function resolveWebHostHome(env2 = process.env, home2 = homedir55()) {
|
|
94396
|
+
const fromEnv = env2.SWITCHROOM_HOST_HOME?.trim();
|
|
94397
|
+
const resolved = fromEnv && fromEnv.length > 0 ? fromEnv : home2;
|
|
94398
|
+
if (resolved === "/host-home" || resolved.startsWith("/host-home/")) {
|
|
94399
|
+
throw new Error(`switchroom webd install: refusing to generate \u2014 the host home resolved to ` + `"${resolved}", the in-container mount point of the operator home (never a ` + `valid host bind source). Emitting it would make Docker create empty ` + `/host-home dirs on the host and break every webhook forward + secret read.
|
|
94400
|
+
|
|
94401
|
+
` + `Recovery: run \`switchroom webd install\` from the HOST shell, or set ` + `SWITCHROOM_HOST_HOME to the real host home first.`);
|
|
94402
|
+
}
|
|
94403
|
+
return resolved;
|
|
94404
|
+
}
|
|
94313
94405
|
var WEB_COMPOSE_PROJECT = "switchroom-web";
|
|
94314
94406
|
function renderWebComposeFile(opts) {
|
|
94315
94407
|
const { hostHome, imageTag, operatorUid } = opts;
|
|
@@ -94432,8 +94524,15 @@ async function doInstall2(opts, program3) {
|
|
|
94432
94524
|
console.log(source_default.yellow(` \u23ed ${opts.dryRun ? "[dry-run] " : ""}${guard.message}`));
|
|
94433
94525
|
return;
|
|
94434
94526
|
}
|
|
94527
|
+
let hostHome;
|
|
94528
|
+
try {
|
|
94529
|
+
hostHome = resolveWebHostHome();
|
|
94530
|
+
} catch (err2) {
|
|
94531
|
+
console.error(source_default.red(err2.message));
|
|
94532
|
+
process.exit(1);
|
|
94533
|
+
}
|
|
94435
94534
|
const yaml = renderWebComposeFile({
|
|
94436
|
-
hostHome
|
|
94535
|
+
hostHome,
|
|
94437
94536
|
imageTag,
|
|
94438
94537
|
operatorUid
|
|
94439
94538
|
});
|
|
@@ -94447,6 +94546,14 @@ async function doInstall2(opts, program3) {
|
|
|
94447
94546
|
if (bak)
|
|
94448
94547
|
console.log(source_default.dim(` Backed up existing compose to ${bak}`));
|
|
94449
94548
|
writeFileSync44(composePath, yaml, "utf8");
|
|
94549
|
+
try {
|
|
94550
|
+
if (typeof process.geteuid === "function" && process.geteuid() === 0) {
|
|
94551
|
+
chownSync9(dir, operatorUid, operatorUid);
|
|
94552
|
+
chownSync9(composePath, operatorUid, operatorUid);
|
|
94553
|
+
if (bak)
|
|
94554
|
+
chownSync9(bak, operatorUid, operatorUid);
|
|
94555
|
+
}
|
|
94556
|
+
} catch {}
|
|
94450
94557
|
console.log(source_default.green(` \u2713 Wrote ${composePath}`));
|
|
94451
94558
|
console.log(source_default.dim(` running as uid ${operatorUid} (operator), network_mode: host`));
|
|
94452
94559
|
console.log(source_default.dim(` Pulling ghcr.io/switchroom/switchroom-web:${imageTag}\u2026`));
|
|
Binary file
|
|
Binary file
|
package/dist/cli/ui/favicon.ico
CHANGED
|
Binary file
|