@marcusrbrown/infra 0.25.0 → 0.25.2
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.js +61 -19
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -6950,7 +6950,7 @@ import { goke } from "goke";
|
|
|
6950
6950
|
// package.json
|
|
6951
6951
|
var package_default = {
|
|
6952
6952
|
name: "@marcusrbrown/infra",
|
|
6953
|
-
version: "0.25.
|
|
6953
|
+
version: "0.25.2",
|
|
6954
6954
|
description: "Infrastructure management CLI \u2014 deploy automation, health checks, and MCP bridge",
|
|
6955
6955
|
keywords: [
|
|
6956
6956
|
"infra",
|
|
@@ -8390,6 +8390,13 @@ function registerSetupCommand(cli, commandLabel) {
|
|
|
8390
8390
|
import { log as log3 } from "@clack/prompts";
|
|
8391
8391
|
import { z as z6 } from "zod";
|
|
8392
8392
|
|
|
8393
|
+
// src/commands/agent/key-layout.ts
|
|
8394
|
+
var KEY_LAYOUT_VERSION = "fro-bot-agent-s3-layout/1";
|
|
8395
|
+
var LEGACY_KEY_LAYOUT_LABELS = new Set(["fro-bot/agent@v0.118.2", "fro-bot/agent@v0.119.2"]);
|
|
8396
|
+
function isKnownKeyLayoutVersion(label) {
|
|
8397
|
+
return label === KEY_LAYOUT_VERSION || LEGACY_KEY_LAYOUT_LABELS.has(label);
|
|
8398
|
+
}
|
|
8399
|
+
|
|
8393
8400
|
// src/commands/agent/workflow-verify.ts
|
|
8394
8401
|
var WORKFLOW_PATH = ".github/workflows/fro-bot.yaml";
|
|
8395
8402
|
var STORAGE_ENVIRONMENT = "fro-bot-storage";
|
|
@@ -8405,9 +8412,6 @@ var CONTENT_EVENTS = new Set([
|
|
|
8405
8412
|
"discussion",
|
|
8406
8413
|
"discussion_comment"
|
|
8407
8414
|
]);
|
|
8408
|
-
var VERIFIED_ACTION_SHA_BY_LAYOUT = {
|
|
8409
|
-
"fro-bot/agent@v0.118.2": "77f2bad7d68ac38279cd0fa28f38b26a0cd15dfb"
|
|
8410
|
-
};
|
|
8411
8415
|
var REQUIRED_S3_INPUTS = {
|
|
8412
8416
|
"s3-backup": undefined,
|
|
8413
8417
|
"s3-bucket": "FRO_BOT_S3_BUCKET",
|
|
@@ -8778,8 +8782,7 @@ function checkStorageAction(jobId, job, manifest, violations) {
|
|
|
8778
8782
|
violations.push(`Storage job '${jobId}' must contain a fro-bot/agent action step.`);
|
|
8779
8783
|
return;
|
|
8780
8784
|
}
|
|
8781
|
-
|
|
8782
|
-
if (!verifiedSha) {
|
|
8785
|
+
if (!isKnownKeyLayoutVersion(manifest.key_layout_version)) {
|
|
8783
8786
|
violations.push(`Unknown key_layout_version '${manifest.key_layout_version}'; refusing to verify fro-bot/agent.`);
|
|
8784
8787
|
}
|
|
8785
8788
|
const firstAgentIndex = steps.findIndex((step) => typeof step.uses === "string" && step.uses.startsWith("fro-bot/agent@"));
|
|
@@ -8807,8 +8810,8 @@ function checkStorageAction(jobId, job, manifest, violations) {
|
|
|
8807
8810
|
for (const [index, step] of agentSteps.entries()) {
|
|
8808
8811
|
const uses = String(step.uses);
|
|
8809
8812
|
const ref = uses.split("@").at(-1) ?? "";
|
|
8810
|
-
if (
|
|
8811
|
-
violations.push(`Storage job '${jobId}'
|
|
8813
|
+
if (!/^[0-9a-f]{40}$/i.test(ref)) {
|
|
8814
|
+
violations.push(`Storage job '${jobId}' fro-bot/agent step ${index + 1} ref '${ref}' is not a full commit SHA; ` + "storage job must pin fro-bot/agent to a full 40-hex commit SHA, not a moveable tag or branch.");
|
|
8812
8815
|
}
|
|
8813
8816
|
if (!isMapping(step.with)) {
|
|
8814
8817
|
violations.push(`Storage job '${jobId}' agent step ${index + 1} is missing its S3 inputs.`);
|
|
@@ -8917,7 +8920,7 @@ function formatStorageWorkflowSnippet() {
|
|
|
8917
8920
|
with:
|
|
8918
8921
|
role-to-assume: \${{ vars.FRO_BOT_S3_ROLE_TO_ASSUME }}
|
|
8919
8922
|
aws-region: \${{ vars.FRO_BOT_S3_REGION }}
|
|
8920
|
-
- uses: fro-bot/agent@
|
|
8923
|
+
- uses: fro-bot/agent@83ed683da24111bd1474eb4bda39715ed5799bc2
|
|
8921
8924
|
with:
|
|
8922
8925
|
s3-backup: true
|
|
8923
8926
|
s3-bucket: \${{ vars.FRO_BOT_S3_BUCKET }}
|
|
@@ -9138,7 +9141,6 @@ var STORAGE_VARIABLE_NAMES = {
|
|
|
9138
9141
|
prefix: "FRO_BOT_S3_PREFIX",
|
|
9139
9142
|
expectedBucketOwner: "FRO_BOT_S3_EXPECTED_BUCKET_OWNER"
|
|
9140
9143
|
};
|
|
9141
|
-
var KNOWN_KEY_LAYOUT_VERSION = "fro-bot/agent@v0.118.2";
|
|
9142
9144
|
var manifestSchema = z6.object({
|
|
9143
9145
|
owner: z6.string().min(1),
|
|
9144
9146
|
repo: z6.string().min(1),
|
|
@@ -9190,8 +9192,8 @@ function assertCanonicalPath(field, value, mustEndWithSlash) {
|
|
|
9190
9192
|
}
|
|
9191
9193
|
}
|
|
9192
9194
|
function validateManifest(manifest) {
|
|
9193
|
-
if (manifest.key_layout_version
|
|
9194
|
-
throw new Error(`Unknown handoff manifest key_layout_version '${manifest.key_layout_version}'; expected ${
|
|
9195
|
+
if (!isKnownKeyLayoutVersion(manifest.key_layout_version)) {
|
|
9196
|
+
throw new Error(`Unknown handoff manifest key_layout_version '${manifest.key_layout_version}'; expected ${KEY_LAYOUT_VERSION}.`);
|
|
9195
9197
|
}
|
|
9196
9198
|
assertRepoFormat(`${manifest.owner}/${manifest.repo}`);
|
|
9197
9199
|
assertCanonicalPath("s3_prefix", manifest.s3_prefix, false);
|
|
@@ -11934,8 +11936,29 @@ function validateGatewayHost(host) {
|
|
|
11934
11936
|
return host;
|
|
11935
11937
|
}
|
|
11936
11938
|
|
|
11939
|
+
// src/commands/gateway/volume.ts
|
|
11940
|
+
var GATEWAY_COMPOSE_PROJECT_NAME = "fro-bot";
|
|
11941
|
+
var MITMPROXY_CERTS_VOLUME = `${GATEWAY_COMPOSE_PROJECT_NAME}_mitmproxy-certs`;
|
|
11942
|
+
var VOLUME_EXISTS_SENTINEL = "EXISTS";
|
|
11943
|
+
var VOLUME_MISSING_SENTINEL = "MISSING";
|
|
11944
|
+
function volumeProbeCommand(volume) {
|
|
11945
|
+
return `docker volume inspect ${volume} >/dev/null 2>&1 && echo ${VOLUME_EXISTS_SENTINEL} || echo ${VOLUME_MISSING_SENTINEL}`;
|
|
11946
|
+
}
|
|
11947
|
+
function checkVolumeProbeResult(volume, result) {
|
|
11948
|
+
if (result.exitCode !== 0) {
|
|
11949
|
+
return `Could not verify Docker volume ${volume} on the gateway host (exit ${result.exitCode}): ${result.stderr.trim() || "unknown error"}`;
|
|
11950
|
+
}
|
|
11951
|
+
const sentinel = result.stdout.trim();
|
|
11952
|
+
if (sentinel === VOLUME_EXISTS_SENTINEL) {
|
|
11953
|
+
return;
|
|
11954
|
+
}
|
|
11955
|
+
if (sentinel === VOLUME_MISSING_SENTINEL) {
|
|
11956
|
+
return `Docker volume ${volume} does not exist on the gateway host; refusing to continue (it would be created empty as a side effect).`;
|
|
11957
|
+
}
|
|
11958
|
+
return `Could not verify Docker volume ${volume} on the gateway host: unrecognized probe output`;
|
|
11959
|
+
}
|
|
11960
|
+
|
|
11937
11961
|
// src/commands/gateway/backup.ts
|
|
11938
|
-
var MITMPROXY_CERTS_VOLUME = "mitmproxy-certs";
|
|
11939
11962
|
var CA_CERT_FILE = "mitmproxy-ca-cert.pem";
|
|
11940
11963
|
var CA_KEY_FILE = "mitmproxy-ca.pem";
|
|
11941
11964
|
var DEFAULT_OUTPUT = "apps/gateway/.local/mitmproxy-ca.tar";
|
|
@@ -11948,7 +11971,7 @@ async function backupGatewayCa(opts, spawn = defaultSpawn3, printErr) {
|
|
|
11948
11971
|
return { ok: false, error: "Only CA backup is currently supported; --no-include-ca is not accepted" };
|
|
11949
11972
|
}
|
|
11950
11973
|
validateGatewayHost(opts.host);
|
|
11951
|
-
const
|
|
11974
|
+
const sshCommand = (remote) => [
|
|
11952
11975
|
"ssh",
|
|
11953
11976
|
"-o",
|
|
11954
11977
|
"BatchMode=yes",
|
|
@@ -11956,13 +11979,28 @@ async function backupGatewayCa(opts, spawn = defaultSpawn3, printErr) {
|
|
|
11956
11979
|
"StrictHostKeyChecking=yes",
|
|
11957
11980
|
...buildKnownHostsArgs(),
|
|
11958
11981
|
`root@${opts.host}`,
|
|
11959
|
-
|
|
11982
|
+
remote
|
|
11960
11983
|
];
|
|
11961
11984
|
const env = {
|
|
11962
11985
|
PATH: process.env.PATH ?? "/usr/bin:/bin",
|
|
11963
11986
|
HOME: process.env.HOME ?? "/tmp",
|
|
11964
11987
|
...process.env.SSH_AUTH_SOCK ? { SSH_AUTH_SOCK: process.env.SSH_AUTH_SOCK } : {}
|
|
11965
11988
|
};
|
|
11989
|
+
const probe = spawn(sshCommand(volumeProbeCommand(MITMPROXY_CERTS_VOLUME)), { env, stdout: "pipe", stderr: "pipe" });
|
|
11990
|
+
const [probeStdout, probeStderr, probeExit] = await Promise.all([
|
|
11991
|
+
new Response(probe.stdout).text(),
|
|
11992
|
+
new Response(probe.stderr).text(),
|
|
11993
|
+
probe.exited
|
|
11994
|
+
]);
|
|
11995
|
+
const probeError = checkVolumeProbeResult(MITMPROXY_CERTS_VOLUME, {
|
|
11996
|
+
stdout: probeStdout,
|
|
11997
|
+
stderr: probeStderr,
|
|
11998
|
+
exitCode: probeExit
|
|
11999
|
+
});
|
|
12000
|
+
if (probeError !== undefined) {
|
|
12001
|
+
return { ok: false, error: probeError };
|
|
12002
|
+
}
|
|
12003
|
+
const sshCmd = sshCommand(`docker run --rm -v ${MITMPROXY_CERTS_VOLUME}:/src:ro alpine tar -cf - -C /src ${CA_CERT_FILE} ${CA_KEY_FILE}`);
|
|
11966
12004
|
const child = spawn(sshCmd, { env, stdout: "pipe", stderr: "pipe" });
|
|
11967
12005
|
const [tarBytes, stderrText, exitCode] = await Promise.all([
|
|
11968
12006
|
new Response(child.stdout).arrayBuffer(),
|
|
@@ -12231,7 +12269,6 @@ function registerGatewayLogs(cli) {
|
|
|
12231
12269
|
|
|
12232
12270
|
// src/commands/gateway/restore.ts
|
|
12233
12271
|
import { z as z25 } from "zod";
|
|
12234
|
-
var MITMPROXY_CERTS_VOLUME2 = "mitmproxy-certs";
|
|
12235
12272
|
var CA_CERT_FILE2 = "mitmproxy-ca-cert.pem";
|
|
12236
12273
|
var CA_KEY_FILE2 = "mitmproxy-ca.pem";
|
|
12237
12274
|
var COMPOSE_PROJECT_DIR4 = "/opt/gateway/deploy";
|
|
@@ -12300,6 +12337,11 @@ async function restoreGatewayCa(opts, spawn = defaultSpawn4) {
|
|
|
12300
12337
|
if (archiveError !== undefined) {
|
|
12301
12338
|
return { ok: false, error: archiveError };
|
|
12302
12339
|
}
|
|
12340
|
+
const probeResult = await spawnText(spawn, sshCommand(opts.host, volumeProbeCommand(MITMPROXY_CERTS_VOLUME)), env);
|
|
12341
|
+
const probeError = checkVolumeProbeResult(MITMPROXY_CERTS_VOLUME, probeResult);
|
|
12342
|
+
if (probeError !== undefined) {
|
|
12343
|
+
return { ok: false, error: probeError };
|
|
12344
|
+
}
|
|
12303
12345
|
const mktempResult = await spawnText(spawn, sshCommand(opts.host, "mktemp -t gateway-ca-restore-XXXXXX.tar"), env);
|
|
12304
12346
|
if (mktempResult.exitCode !== 0) {
|
|
12305
12347
|
return {
|
|
@@ -12336,7 +12378,7 @@ async function restoreGatewayCa(opts, spawn = defaultSpawn4) {
|
|
|
12336
12378
|
};
|
|
12337
12379
|
return primaryError;
|
|
12338
12380
|
}
|
|
12339
|
-
const extractResult = await spawnText(spawn, sshCommand(opts.host, `docker run --rm -v ${
|
|
12381
|
+
const extractResult = await spawnText(spawn, sshCommand(opts.host, `docker run --rm -v ${MITMPROXY_CERTS_VOLUME}:/dst -v ${tmpRemote}:/src.tar:ro alpine sh -c 'tar -xf /src.tar -C /dst'`), env);
|
|
12340
12382
|
if (extractResult.exitCode !== 0) {
|
|
12341
12383
|
primaryError = {
|
|
12342
12384
|
ok: false,
|
|
@@ -12353,8 +12395,8 @@ async function restoreGatewayCa(opts, spawn = defaultSpawn4) {
|
|
|
12353
12395
|
return primaryError;
|
|
12354
12396
|
}
|
|
12355
12397
|
const [certResult, keyResult] = await Promise.all([
|
|
12356
|
-
spawnText(spawn, sshCommand(opts.host, `docker run --rm -v ${
|
|
12357
|
-
spawnText(spawn, sshCommand(opts.host, `docker run --rm -v ${
|
|
12398
|
+
spawnText(spawn, sshCommand(opts.host, `docker run --rm -v ${MITMPROXY_CERTS_VOLUME}:/src:ro alpine cat /src/${CA_CERT_FILE2}`), env),
|
|
12399
|
+
spawnText(spawn, sshCommand(opts.host, `docker run --rm -v ${MITMPROXY_CERTS_VOLUME}:/src:ro alpine cat /src/${CA_KEY_FILE2}`), env)
|
|
12358
12400
|
]);
|
|
12359
12401
|
if (certResult.exitCode !== 0 || keyResult.exitCode !== 0) {
|
|
12360
12402
|
primaryError = {
|