run402-mcp
Version:
MCP server for Run402 — full-stack backend infrastructure for AI agents: Postgres, auth, storage, serverless functions and atomic deploys. Paid with x402/MPP. Includes $0.03 image generation.
133 lines • 6.86 kB
JavaScript
/**
* vault-mirror-and-recover — mirror destination config (design D1/D2,
* task 2.1).
*
* Per-vault mirror configuration lives BESIDE the keystore, in its own
* sibling directory (`<keystore root>/mirrors/<repo_id>.json`) — never in
* `run402.config.json` (that file is committed to the repository, and a
* mirror destination is a per-machine operational fact, not something to
* share with every clone) and never containing a raw secret value. The
* credential field names an AWS profile OR selects the ambient environment
* chain (`AWS_ACCESS_KEY_ID`/`AWS_SECRET_ACCESS_KEY`/`AWS_SESSION_TOKEN`) —
* resolved at USE time by {@link ../node/vault-mirror-backend.js}, never
* read or cached here. D1 is the reason this file exists at all: run402
* never holds a credential to the customer's bucket, so the credential must
* live somewhere run402 code never uploads or transmits — this config file,
* read only by the client's own machine.
*/
import { existsSync, readdirSync, unlinkSync } from "node:fs";
import { join } from "node:path";
import { LocalError } from "../errors.js";
import { VAULT_SRC_RE } from "../namespaces/vault.crypto.js";
import { readFileNoFollow, writeFileAtomic0600 } from "./vault-keystore.js";
function fail(code, message, context, details, nextActions) {
throw new LocalError(message, context, { code, details, ...(nextActions ? { next_actions: nextActions } : {}) });
}
export function mirrorsDir(keystore) {
return join(keystore.rootDir, "mirrors");
}
export function mirrorConfigPath(keystore, repoId) {
if (!VAULT_SRC_RE.test(repoId))
fail("VAULT_BAD_REPO_ID", `not a src_ id: ${repoId}`, "resolving vault mirror config path");
return join(mirrorsDir(keystore), `${repoId}.json`);
}
/** Read the mirror config for one vault, or `null` when none is configured. */
export function readMirrorConfig(keystore, repoId) {
const text = readFileNoFollow(mirrorConfigPath(keystore, repoId));
if (!text)
return null;
let parsed;
try {
parsed = JSON.parse(text);
}
catch (e) {
fail("VAULT_MIRROR_CONFIG_CORRUPT", `mirror config for ${repoId} is not valid JSON: ${e.message}`, "reading vault mirror config", { repo_id: repoId });
}
if (parsed.version !== 1 || parsed.repo_id !== repoId || !parsed.destination) {
fail("VAULT_MIRROR_CONFIG_CORRUPT", `mirror config for ${repoId} is not a version-1 vault mirror config`, "reading vault mirror config", { repo_id: repoId });
}
return parsed;
}
/** Every repo id with a mirror configured on this machine. */
export function listMirroredRepoIds(keystore) {
const dir = mirrorsDir(keystore);
if (!existsSync(dir))
return [];
return readdirSync(dir)
.filter((n) => n.endsWith(".json"))
.map((n) => n.slice(0, -5))
.filter((id) => VAULT_SRC_RE.test(id))
.sort();
}
/** Set (create or replace) the mirror destination for one vault. */
export function saveMirrorConfig(keystore, input, now = () => new Date()) {
if (input.destination.kind === "s3" && !input.credential) {
fail("VAULT_MIRROR_CREDENTIAL_REQUIRED", "an s3 mirror destination needs a credential — pass --profile <name> or --ambient", "configuring vault mirror");
}
const existing = readMirrorConfig(keystore, input.repo_id);
// A recorded success is a fact about ONE destination: carry it across an
// idempotent re-save of the same place, reset it when the destination moves
// (the vault_unmirrored finding honestly reopens until the new mirror's
// first successful write/sync).
const sameDestination = existing != null && JSON.stringify(existing.destination) === JSON.stringify(input.destination);
const full = {
version: 1,
repo_id: input.repo_id,
destination: input.destination,
...(input.credential ? { credential: input.credential } : {}),
...(sameDestination && existing.last_success_at ? { last_success_at: existing.last_success_at } : {}),
created_at: existing?.created_at ?? now().toISOString(),
updated_at: now().toISOString(),
};
writeFileAtomic0600(mirrorConfigPath(keystore, input.repo_id), JSON.stringify(full, null, 2));
return full;
}
/**
* Stamp `last_success_at` after a zero-failure mirror write/sync
* (vault-mirror-default) — the local, never-transmitted fact that clears
* the `vault_unmirrored` finding. A no-op when no config exists (a
* test-injected backend can sync without one), or when the config was removed
* mid-sync.
*/
export function recordMirrorSuccess(keystore, repoId, now = () => new Date()) {
const existing = readMirrorConfig(keystore, repoId);
if (!existing)
return null;
const full = { ...existing, last_success_at: now().toISOString() };
writeFileAtomic0600(mirrorConfigPath(keystore, repoId), JSON.stringify(full, null, 2));
return full;
}
/** Remove the mirror config for one vault. Never touches the mirror's own bytes — this only stops future pushes/syncs from targeting it. */
export function removeMirrorConfig(keystore, repoId) {
const path = mirrorConfigPath(keystore, repoId);
if (!existsSync(path))
return { removed: false };
try {
unlinkSync(path);
}
catch (e) {
fail("VAULT_MIRROR_CONFIG_REMOVE_FAILED", `could not remove mirror config for ${repoId}: ${e.message}`, "removing vault mirror config", { repo_id: repoId });
}
return { removed: true };
}
/** Parse `s3://<bucket>/<prefix>` or a plain filesystem path into a {@link VaultMirrorDestination}. */
export function parseMirrorDestinationUrl(url, options = {}) {
if (url.startsWith("s3://")) {
const rest = url.slice("s3://".length);
const slash = rest.indexOf("/");
const bucket = slash === -1 ? rest : rest.slice(0, slash);
const prefix = slash === -1 ? "" : rest.slice(slash + 1).replace(/\/+$/, "");
if (!bucket)
fail("VAULT_MIRROR_DESTINATION_INVALID", `${url} names no bucket`, "parsing vault mirror destination", { url });
const region = options.region ?? process.env.AWS_REGION ?? process.env.AWS_DEFAULT_REGION;
if (!region)
fail("VAULT_MIRROR_REGION_REQUIRED", `an s3 destination needs a region — pass --region or set AWS_REGION`, "parsing vault mirror destination", { url });
return { kind: "s3", bucket, prefix, region, ...(options.endpoint ? { endpoint: options.endpoint } : {}) };
}
return { kind: "directory", path: url };
}
/** Render a destination back to the address form a human typed (for `status`/errors). */
export function formatMirrorDestination(destination) {
return destination.kind === "s3" ? `s3://${destination.bucket}/${destination.prefix}` : destination.path;
}
//# sourceMappingURL=vault-mirror-config.js.map