@marcusrbrown/infra 0.24.0 → 0.25.1
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 -18
- package/package.json +2 -2
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.
|
|
6953
|
+
version: "0.25.1",
|
|
6954
6954
|
description: "Infrastructure management CLI \u2014 deploy automation, health checks, and MCP bridge",
|
|
6955
6955
|
keywords: [
|
|
6956
6956
|
"infra",
|
|
@@ -6993,7 +6993,7 @@ var package_default = {
|
|
|
6993
6993
|
},
|
|
6994
6994
|
devDependencies: {
|
|
6995
6995
|
"@marcusrbrown/infra-shared": "workspace:*",
|
|
6996
|
-
"@modelcontextprotocol/sdk": "1.
|
|
6996
|
+
"@modelcontextprotocol/sdk": "1.32.1",
|
|
6997
6997
|
yaml: "2.9.1"
|
|
6998
6998
|
},
|
|
6999
6999
|
engines: {
|
|
@@ -8406,7 +8406,7 @@ var CONTENT_EVENTS = new Set([
|
|
|
8406
8406
|
"discussion_comment"
|
|
8407
8407
|
]);
|
|
8408
8408
|
var VERIFIED_ACTION_SHA_BY_LAYOUT = {
|
|
8409
|
-
"fro-bot/agent@v0.
|
|
8409
|
+
"fro-bot/agent@v0.119.2": "83ed683da24111bd1474eb4bda39715ed5799bc2"
|
|
8410
8410
|
};
|
|
8411
8411
|
var REQUIRED_S3_INPUTS = {
|
|
8412
8412
|
"s3-backup": undefined,
|
|
@@ -8849,9 +8849,16 @@ function hasHandoff(job) {
|
|
|
8849
8849
|
const serialized = stringifyUnknown(job);
|
|
8850
8850
|
return /needs\.[\w-]+\.outputs\./.test(serialized) || /actions\/(?:download-artifact|upload-artifact|cache|cache\/restore|cache\/save)@/.test(serialized);
|
|
8851
8851
|
}
|
|
8852
|
+
var STATIC_AWS_CREDENTIAL_PATTERN = /AWS_ACCESS_KEY_ID|AWS_SECRET_ACCESS_KEY|aws-access-key-id|aws-secret-access-key/;
|
|
8853
|
+
function withoutBlankedCredentialKeys(value) {
|
|
8854
|
+
if (Array.isArray(value))
|
|
8855
|
+
return value.map(withoutBlankedCredentialKeys);
|
|
8856
|
+
if (!isMapping(value))
|
|
8857
|
+
return value;
|
|
8858
|
+
return Object.fromEntries(Object.entries(value).filter(([key, entry]) => !(entry === "" && STATIC_AWS_CREDENTIAL_PATTERN.test(key))).map(([key, entry]) => [key, withoutBlankedCredentialKeys(entry)]));
|
|
8859
|
+
}
|
|
8852
8860
|
function hasStaticAwsCredentials(job) {
|
|
8853
|
-
|
|
8854
|
-
return /AWS_ACCESS_KEY_ID|AWS_SECRET_ACCESS_KEY|aws-access-key-id|aws-secret-access-key/.test(serialized);
|
|
8861
|
+
return STATIC_AWS_CREDENTIAL_PATTERN.test(stringifyUnknown(withoutBlankedCredentialKeys(job)));
|
|
8855
8862
|
}
|
|
8856
8863
|
function ancestors(jobId, analyses) {
|
|
8857
8864
|
const visited = new Set;
|
|
@@ -8910,7 +8917,7 @@ function formatStorageWorkflowSnippet() {
|
|
|
8910
8917
|
with:
|
|
8911
8918
|
role-to-assume: \${{ vars.FRO_BOT_S3_ROLE_TO_ASSUME }}
|
|
8912
8919
|
aws-region: \${{ vars.FRO_BOT_S3_REGION }}
|
|
8913
|
-
- uses: fro-bot/agent@
|
|
8920
|
+
- uses: fro-bot/agent@83ed683da24111bd1474eb4bda39715ed5799bc2
|
|
8914
8921
|
with:
|
|
8915
8922
|
s3-backup: true
|
|
8916
8923
|
s3-bucket: \${{ vars.FRO_BOT_S3_BUCKET }}
|
|
@@ -8946,10 +8953,6 @@ function environmentResult(environmentValue, branchPoliciesValue) {
|
|
|
8946
8953
|
violations.push("GitHub Environment fro-bot-storage is missing or returned an invalid response.");
|
|
8947
8954
|
return { verified: false, violations };
|
|
8948
8955
|
}
|
|
8949
|
-
const protectionRules = Array.isArray(environmentValue.protection_rules) ? environmentValue.protection_rules : [];
|
|
8950
|
-
const hasReviewer = protectionRules.some((rule) => isMapping(rule) && rule.type === "required_reviewers" && Array.isArray(rule.reviewers) && rule.reviewers.length > 0);
|
|
8951
|
-
if (!hasReviewer)
|
|
8952
|
-
violations.push("GitHub Environment fro-bot-storage has no required reviewer protection rule.");
|
|
8953
8956
|
const branchPolicy = environmentValue.deployment_branch_policy;
|
|
8954
8957
|
if (!isMapping(branchPolicy) || branchPolicy.protected_branches !== false || branchPolicy.custom_branch_policies !== true) {
|
|
8955
8958
|
violations.push("GitHub Environment fro-bot-storage does not use a custom deployment-branch policy.");
|
|
@@ -9135,7 +9138,7 @@ var STORAGE_VARIABLE_NAMES = {
|
|
|
9135
9138
|
prefix: "FRO_BOT_S3_PREFIX",
|
|
9136
9139
|
expectedBucketOwner: "FRO_BOT_S3_EXPECTED_BUCKET_OWNER"
|
|
9137
9140
|
};
|
|
9138
|
-
var KNOWN_KEY_LAYOUT_VERSION = "fro-bot/agent@v0.
|
|
9141
|
+
var KNOWN_KEY_LAYOUT_VERSION = "fro-bot/agent@v0.119.2";
|
|
9139
9142
|
var manifestSchema = z6.object({
|
|
9140
9143
|
owner: z6.string().min(1),
|
|
9141
9144
|
repo: z6.string().min(1),
|
|
@@ -11931,8 +11934,29 @@ function validateGatewayHost(host) {
|
|
|
11931
11934
|
return host;
|
|
11932
11935
|
}
|
|
11933
11936
|
|
|
11937
|
+
// src/commands/gateway/volume.ts
|
|
11938
|
+
var GATEWAY_COMPOSE_PROJECT_NAME = "fro-bot";
|
|
11939
|
+
var MITMPROXY_CERTS_VOLUME = `${GATEWAY_COMPOSE_PROJECT_NAME}_mitmproxy-certs`;
|
|
11940
|
+
var VOLUME_EXISTS_SENTINEL = "EXISTS";
|
|
11941
|
+
var VOLUME_MISSING_SENTINEL = "MISSING";
|
|
11942
|
+
function volumeProbeCommand(volume) {
|
|
11943
|
+
return `docker volume inspect ${volume} >/dev/null 2>&1 && echo ${VOLUME_EXISTS_SENTINEL} || echo ${VOLUME_MISSING_SENTINEL}`;
|
|
11944
|
+
}
|
|
11945
|
+
function checkVolumeProbeResult(volume, result) {
|
|
11946
|
+
if (result.exitCode !== 0) {
|
|
11947
|
+
return `Could not verify Docker volume ${volume} on the gateway host (exit ${result.exitCode}): ${result.stderr.trim() || "unknown error"}`;
|
|
11948
|
+
}
|
|
11949
|
+
const sentinel = result.stdout.trim();
|
|
11950
|
+
if (sentinel === VOLUME_EXISTS_SENTINEL) {
|
|
11951
|
+
return;
|
|
11952
|
+
}
|
|
11953
|
+
if (sentinel === VOLUME_MISSING_SENTINEL) {
|
|
11954
|
+
return `Docker volume ${volume} does not exist on the gateway host; refusing to continue (it would be created empty as a side effect).`;
|
|
11955
|
+
}
|
|
11956
|
+
return `Could not verify Docker volume ${volume} on the gateway host: unrecognized probe output`;
|
|
11957
|
+
}
|
|
11958
|
+
|
|
11934
11959
|
// src/commands/gateway/backup.ts
|
|
11935
|
-
var MITMPROXY_CERTS_VOLUME = "mitmproxy-certs";
|
|
11936
11960
|
var CA_CERT_FILE = "mitmproxy-ca-cert.pem";
|
|
11937
11961
|
var CA_KEY_FILE = "mitmproxy-ca.pem";
|
|
11938
11962
|
var DEFAULT_OUTPUT = "apps/gateway/.local/mitmproxy-ca.tar";
|
|
@@ -11945,7 +11969,7 @@ async function backupGatewayCa(opts, spawn = defaultSpawn3, printErr) {
|
|
|
11945
11969
|
return { ok: false, error: "Only CA backup is currently supported; --no-include-ca is not accepted" };
|
|
11946
11970
|
}
|
|
11947
11971
|
validateGatewayHost(opts.host);
|
|
11948
|
-
const
|
|
11972
|
+
const sshCommand = (remote) => [
|
|
11949
11973
|
"ssh",
|
|
11950
11974
|
"-o",
|
|
11951
11975
|
"BatchMode=yes",
|
|
@@ -11953,13 +11977,28 @@ async function backupGatewayCa(opts, spawn = defaultSpawn3, printErr) {
|
|
|
11953
11977
|
"StrictHostKeyChecking=yes",
|
|
11954
11978
|
...buildKnownHostsArgs(),
|
|
11955
11979
|
`root@${opts.host}`,
|
|
11956
|
-
|
|
11980
|
+
remote
|
|
11957
11981
|
];
|
|
11958
11982
|
const env = {
|
|
11959
11983
|
PATH: process.env.PATH ?? "/usr/bin:/bin",
|
|
11960
11984
|
HOME: process.env.HOME ?? "/tmp",
|
|
11961
11985
|
...process.env.SSH_AUTH_SOCK ? { SSH_AUTH_SOCK: process.env.SSH_AUTH_SOCK } : {}
|
|
11962
11986
|
};
|
|
11987
|
+
const probe = spawn(sshCommand(volumeProbeCommand(MITMPROXY_CERTS_VOLUME)), { env, stdout: "pipe", stderr: "pipe" });
|
|
11988
|
+
const [probeStdout, probeStderr, probeExit] = await Promise.all([
|
|
11989
|
+
new Response(probe.stdout).text(),
|
|
11990
|
+
new Response(probe.stderr).text(),
|
|
11991
|
+
probe.exited
|
|
11992
|
+
]);
|
|
11993
|
+
const probeError = checkVolumeProbeResult(MITMPROXY_CERTS_VOLUME, {
|
|
11994
|
+
stdout: probeStdout,
|
|
11995
|
+
stderr: probeStderr,
|
|
11996
|
+
exitCode: probeExit
|
|
11997
|
+
});
|
|
11998
|
+
if (probeError !== undefined) {
|
|
11999
|
+
return { ok: false, error: probeError };
|
|
12000
|
+
}
|
|
12001
|
+
const sshCmd = sshCommand(`docker run --rm -v ${MITMPROXY_CERTS_VOLUME}:/src:ro alpine tar -cf - -C /src ${CA_CERT_FILE} ${CA_KEY_FILE}`);
|
|
11963
12002
|
const child = spawn(sshCmd, { env, stdout: "pipe", stderr: "pipe" });
|
|
11964
12003
|
const [tarBytes, stderrText, exitCode] = await Promise.all([
|
|
11965
12004
|
new Response(child.stdout).arrayBuffer(),
|
|
@@ -12228,7 +12267,6 @@ function registerGatewayLogs(cli) {
|
|
|
12228
12267
|
|
|
12229
12268
|
// src/commands/gateway/restore.ts
|
|
12230
12269
|
import { z as z25 } from "zod";
|
|
12231
|
-
var MITMPROXY_CERTS_VOLUME2 = "mitmproxy-certs";
|
|
12232
12270
|
var CA_CERT_FILE2 = "mitmproxy-ca-cert.pem";
|
|
12233
12271
|
var CA_KEY_FILE2 = "mitmproxy-ca.pem";
|
|
12234
12272
|
var COMPOSE_PROJECT_DIR4 = "/opt/gateway/deploy";
|
|
@@ -12297,6 +12335,11 @@ async function restoreGatewayCa(opts, spawn = defaultSpawn4) {
|
|
|
12297
12335
|
if (archiveError !== undefined) {
|
|
12298
12336
|
return { ok: false, error: archiveError };
|
|
12299
12337
|
}
|
|
12338
|
+
const probeResult = await spawnText(spawn, sshCommand(opts.host, volumeProbeCommand(MITMPROXY_CERTS_VOLUME)), env);
|
|
12339
|
+
const probeError = checkVolumeProbeResult(MITMPROXY_CERTS_VOLUME, probeResult);
|
|
12340
|
+
if (probeError !== undefined) {
|
|
12341
|
+
return { ok: false, error: probeError };
|
|
12342
|
+
}
|
|
12300
12343
|
const mktempResult = await spawnText(spawn, sshCommand(opts.host, "mktemp -t gateway-ca-restore-XXXXXX.tar"), env);
|
|
12301
12344
|
if (mktempResult.exitCode !== 0) {
|
|
12302
12345
|
return {
|
|
@@ -12333,7 +12376,7 @@ async function restoreGatewayCa(opts, spawn = defaultSpawn4) {
|
|
|
12333
12376
|
};
|
|
12334
12377
|
return primaryError;
|
|
12335
12378
|
}
|
|
12336
|
-
const extractResult = await spawnText(spawn, sshCommand(opts.host, `docker run --rm -v ${
|
|
12379
|
+
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);
|
|
12337
12380
|
if (extractResult.exitCode !== 0) {
|
|
12338
12381
|
primaryError = {
|
|
12339
12382
|
ok: false,
|
|
@@ -12350,8 +12393,8 @@ async function restoreGatewayCa(opts, spawn = defaultSpawn4) {
|
|
|
12350
12393
|
return primaryError;
|
|
12351
12394
|
}
|
|
12352
12395
|
const [certResult, keyResult] = await Promise.all([
|
|
12353
|
-
spawnText(spawn, sshCommand(opts.host, `docker run --rm -v ${
|
|
12354
|
-
spawnText(spawn, sshCommand(opts.host, `docker run --rm -v ${
|
|
12396
|
+
spawnText(spawn, sshCommand(opts.host, `docker run --rm -v ${MITMPROXY_CERTS_VOLUME}:/src:ro alpine cat /src/${CA_CERT_FILE2}`), env),
|
|
12397
|
+
spawnText(spawn, sshCommand(opts.host, `docker run --rm -v ${MITMPROXY_CERTS_VOLUME}:/src:ro alpine cat /src/${CA_KEY_FILE2}`), env)
|
|
12355
12398
|
]);
|
|
12356
12399
|
if (certResult.exitCode !== 0 || keyResult.exitCode !== 0) {
|
|
12357
12400
|
primaryError = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@marcusrbrown/infra",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.25.1",
|
|
4
4
|
"description": "Infrastructure management CLI — deploy automation, health checks, and MCP bridge",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"infra",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"@marcusrbrown/infra-shared": "workspace:*",
|
|
46
|
-
"@modelcontextprotocol/sdk": "1.
|
|
46
|
+
"@modelcontextprotocol/sdk": "1.32.1",
|
|
47
47
|
"yaml": "2.9.1"
|
|
48
48
|
},
|
|
49
49
|
"engines": {
|